Skip to content

feat: rig-managed tmux focus-events + shell env vars (COLORTERM=truecolor) - #205

Open
alex-mextner wants to merge 2 commits into
mainfrom
feat/tmux-focus-events
Open

feat: rig-managed tmux focus-events + shell env vars (COLORTERM=truecolor)#205
alex-mextner wants to merge 2 commits into
mainfrom
feat/tmux-focus-events

Conversation

@alex-mextner

@alex-mextner alex-mextner commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary

Two related, independently-committed additions to rig's declarative config, both requested by Alex to replace hand-edited dotfiles with rig-managed provisioning:

1. tmux.focus_events (Closes #204) — models tmux's focus-events option as a proper rig-managed boolean, following the exact established pattern of every other modeled tmux boolean. render_rig_conf() emits an explicit set -g focus-events on/off (never omitted), placed first in the generated file since it carries no ordering dependency on the resurrect/continuum/tpm machinery.

2. env: block — shell environment variables (Closes #206) — a brand-new capability: rig owns a generated ~/.config/rig/env/rig.env.sh (one export KEY=value per configured var) and ensures a source line is present in rc_path (default ~/.zshenv — the only zsh startup file sourced unconditionally in every mode, including non-interactive shells). Mirrors riglib.tmux's "generated file + spliced import line" idiom, but deliberately position-tolerant (not end-anchored like tmux) since a plain exported var has none of tmux's continuum-ordering hazard. Shell-injection hardening throughout: var keys validated as POSIX identifiers (both at config-validate time and again at render time), values shlex.quoted, the import line itself shlex.quoted rather than hand-placed quotes.

Follow-up filed: #207 — a pre-existing, system-wide characteristic (shared by env/tmux/gitignore/spotlight) that a repo's own committed rig.yaml can declare a GLOBAL-labeled block with no layer enforcement. Confirmed not a regression from this PR; deferred as an architecture decision, not something either of these two features could fix in isolation.

Test plan

  • uv run pytest -q tests/ (full suite) — 2386 passed, 18 skipped (pre-existing codex-update probe-timing flakiness under load, confirmed present on unmodified main too — unrelated to this PR)
  • uv run ruff check on every touched file — clean (one pre-existing, unrelated F401 in plan.py, confirmed present on main before this PR)
  • bash tests/smoke.sh --fast — OK
  • uv run python -m riglib schema --write — regenerated schema/rig.schema.json, committed
  • review diff --staged — multiple rounds across both commits (Fable/Opus; codex hit its usage limit early). The env feature alone went through 11 review rounds as the design evolved (shell-injection hardening, apply/status predicate-sharing, a position-tolerance redesign after review caught an end-anchored version silently reverting user-relocated lines forever, byte-preserving CRLF/trailing-blank handling, path-resolution duplication cleanup). Final round: "I could not find a blocking correctness bug... the shipped logic is sound."
  • Applied live on the requesting machine via rig apply commit --only tmux / --only env (surgical, scoped — not a full rig apply) — verified tmux show-options -g focus-events reports on in a live session, and zsh -l -c 'echo $COLORTERM' prints truecolor

🤖 Generated with Claude Code

https://claude.ai/code/session_01PZGonUbk2sDjqYaaH5zRrM

Closes #204

Alex wants tmux's `focus-events` option on so terminal focus in/out
events reach programs running inside panes (needed by some editors/
tools) -- provisioned through rig, not a hand-edited dotfile.

Adds `tmux.focus_events.enabled` (default true) following the exact
established pattern for every other modeled tmux boolean: a Block in
config_schema.py (+ regenerated schema/rig.schema.json), a manual
validator entry in config.py's _TMUX_TOP_KEYS/_TMUX_SUBKEYS, a
TmuxPlan field + build_tmux() kwarg in tmux.py, an explicit on/off
render line (never omit-when-false, so the generated tail overrides
a preserved inline value from a migrated conf -- same contract as
@continuum-restore/@continuum-boot/@resurrect-capture-pane-contents),
and pass-through in plan.py's provision_tmux options + runner.py's
tmux_plan_from_action. Placed first in the generated file, before the
plugin declarations -- a plain terminal-capability toggle independent
of the resurrect/continuum/tpm ordering guarantee documented on
render_rig_conf.

Docs: docs/config-schema.md gets the yaml example + table row.

Tests: validate accept/reject (typo guard), render default-on,
render explicit-off, the pre-upgrade Action-replay default (mirrors
the existing pane_titles coverage), and an end-to-end test through
the FULL plan.py -> runner.py pass-through chain for the disabled
case (the other tests either call tmux.build_tmux directly, bypassing
that chain, or only assert the default, so a broken pass-through
would have rendered 'on' regardless and slipped past every other
test undetected).

Full suite (`uv run pytest -q tests/`): 2317 passed, 18 skipped.
Reviewed with `review diff --staged` (Fable, then Opus after a small
follow-up edit); findings addressed (missing end-to-end disabled-path
test; docs/docstring framing inconsistency on line placement being
"load-bearing" vs "readability only" -- resolved to the latter,
matching the actual code; verified TmuxPlan's only constructor is
build_tmux, so the new required field has no other call site to break).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PZGonUbk2sDjqYaaH5zRrM
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Closes #206
Follow-up filed: #207 (GLOBAL-labeled blocks have no repo-layer enforcement)

Alex wants COLORTERM=truecolor set machine-wide for richer terminal
colors, rolled out through rig -- not a hand-edited .zshenv/.zshrc.
No existing rig mechanism managed generic shell env vars (verified: no
source-file line in .zshrc/.zprofile, no schema block, no module), so
this adds one, mirroring riglib.tmux's "own a generated file, splice
one import line" idiom.

New module riglib/shell_env.py (pure, stdlib-only): ShellEnvPlan +
build_shell_env resolve an `env:` config block; render_env_file()
writes one `export KEY=value` per var (keys validated as POSIX shell
identifiers -- both at config.validate time and, defense in depth,
again at render time, since keys are interpolated UNQUOTED; values
are shlex-quoted); desired_rc_text() is a POSITION-TOLERANT splice --
deliberately NOT end-anchored like tmux's import line (tmux's ordering
guarantee is a real hazard from continuum; a plain exported var has
none), so a user-relocated import line is left exactly where they put
it, while a coexisting STALE copy (an old generated_dir) is still
always dropped. import_line() is shlex.quote'd (not hand-placed
quotes -- an adversarial generated_dir, reachable per review from a
repo's own committed rig.yaml, could otherwise break out of a
hand-quoted string).

Wiring: config_schema.py/config.py (validation, including var-key
identifier + newline/empty-path rejection), plan.py (_build_env +
the shared env_options_from_config used by both the planner and the
disabled-scan below), actions/runner.py (_do_provision_env -- both
artifacts go through fsutil.write_file for consistent on_conflict/
backup/error handling), drift.py (_check_env sharing apply's exact
predicate so status can never disagree with apply, plus
check_disabled_env -- a still-installed leftover after `enabled:
false`, checking BOTH the generated file and a lingering rc_path
source line independently), areas.py/layers.py (status-summary
registration), cli.py (wires the disabled-scan).

Default rc_path is ~/.zshenv: zsh's only startup file sourced
unconditionally in every mode (login or not, interactive or not), so
a var reaches even `zsh -l -c '...'` / mosh / SSH non-interactive
shells -- confirmed against Alex's own ~/.zshenv, which already
carries a Homebrew-PATH comment making the same point.

Docs: new docs/config-schema.md#env section (position-tolerance,
teardown limitations, trust-model note).

Tests: tests/test_shell_env.py (82 tests) -- validation, pure
rendering, CRLF/trailing-blank-line preservation, position-tolerance,
stale-line handling (including stale-plus-current coexistence), the
full plan->apply->drift round trip, shell-injection cases (bad keys,
adversarial values, an adversarial generated_dir), on_conflict variance,
non-UTF-8 file handling, and the disabled-block orphan scan (both
directions). Full suite: `uv run pytest -q tests/` -- 2386 passed, 18
skipped (pre-existing, unrelated codex-update probe-timing flakiness
under load, confirmed present on unmodified main too).

Reviewed with `review diff --staged` across 11 rounds (Fable/Opus,
codex hit its usage limit early and sat out most rounds) as the
design evolved -- shell-injection hardening (unquoted var keys,
quoted values, the import-line path itself), the apply/status
predicate-sharing fix, position-tolerance (a design change from an
initial end-anchored version, after review found it silently
reverted a user's deliberate line placement forever), byte-preserving
CRLF/trailing-blank handling, error-handling parity between the
generated file and rc_path, path-resolution duplication cleanup
(env_options_from_config shared by plan.py and cli.py; a single
canonical ENV_VAR_KEY_PATTERN in shell_env.py, imported by config.py
instead of duplicated), and newline/empty-path validation. Final
round: "I could not find a blocking correctness bug... the shipped
logic is sound." Remaining known limitations are documented inline
and in docs/config-schema.md (the GLOBAL-vs-repo-layer trust question
is deliberately deferred to #207, shared by env's siblings tmux/
gitignore/spotlight, not something #206 could fix in isolation
without redesigning the whole layer-provenance model).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PZGonUbk2sDjqYaaH5zRrM
@alex-mextner alex-mextner changed the title feat(tmux): model focus-events as a rig-managed tmux option feat: rig-managed tmux focus-events + shell env vars (COLORTERM=truecolor) Aug 5, 2026
@alex-mextner
alex-mextner force-pushed the feat/tmux-focus-events branch 2 times, most recently from a362425 to 1f6afe9 Compare August 7, 2026 08:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

env: add rig-managed shell environment variables (COLORTERM=truecolor) tmux: add rig-managed focus-events option

1 participant