Skip to content

fix(monitor): forward explicit env settings to the tmux loop pane (#378) - #385

Merged
frankbria merged 3 commits into
mainfrom
fix/issue-378-monitor-env-forwarding
Oct 9, 2026
Merged

frankbria merged 3 commits into
mainfrom
fix/issue-378-monitor-env-forwarding

Conversation

@frankbria

@frankbria frankbria commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes #378. ralph --monitor starts the loop by typing a command into a tmux pane. When a tmux server is already running, that pane gets the server's environment, not the environment of the shell that ran ralph --monitor. Until now only RALPH_DIR and CLI-flag-backed settings were put on the pane command, so env-only settings never reached the loop. That covers the CB_* thresholds, MAX_CONSECUTIVE_*, CLAUDE_MODEL, OPTIONAL_SECTIONS and more. In the pane, .ralphrc or the default won.

setup_tmux_session now prepends KEY=$(printf '%q' value) to the loop pane's command for every non-empty _env_* snapshot:

  • The list comes from "${!_env_@}", so a newly snapshotted key is forwarded without anyone maintaining a list.
  • The function runs before main() loads .ralphrc, so a non-empty snapshot is always the user's own value. Empty or unset keys aren't forwarded, so .ralphrc still applies in the pane.
  • CLI flags are still appended after the prefix, and the child applies CLI over env over .ralphrc ([P2.2] Environment does not override .ralphrc for CB_* thresholds, MAX_CONSECUTIVE_*, CLAUDE_MIN_VERSION #369), so an explicit flag keeps winning.
  • Only the loop pane gets the prefix. ralph_monitor.sh reads none of these keys.

Acceptance Criteria

  • With a running tmux server, KEY=value ralph --monitor runs the loop with value for every env-only .ralphrc key. A table-driven mock test sets every _env_* key parsed from ralph_loop.sh (54) and asserts each one is forwarded.
  • Values with spaces or shell metacharacters are %q-quoted. A test runs the logged pane command with bash against a fake ralph. Values containing ;, $(…), backticks, quotes, \, |, <, >, a tab and a newline arrive byte-exact, and no injection side effect happens.
  • Keys not set in the environment are not forwarded.

Test Plan

  • TDD: all 3 new tests failed before the fix
  • npm test: 1364/1364 pass
  • shellcheck: same finding counts as main
  • Mutation check: 3 of 3 killed (no %q, no empty-value check, prefix not applied)
  • Internal code review (advisory): no Critical or Major findings. Two suggestions applied (comment on the pane-shell assumption, stale SYNC_* comment). Two rebutted: a real-child precedence test would duplicate the [P2.2] Environment does not override .ralphrc for CB_* thresholds, MAX_CONSECUTIVE_*, CLAUDE_MIN_VERSION #369 tests plus the demo below, and the mock tmux is exported (export -f).
  • Cross-family review: codex, no actionable findings. opencode/GLM (the first choice) stalled (180 s with no output, an outage signature), so per the reviewer chain I fell back to codex.
  • Real-tmux check: an isolated tmux server was started without the settings. The real setup_tmux_session was then run with them set. The loop pane's actual process received CB_NO_PROGRESS_THRESHOLD=10 and CLAUDE_MODEL='claude-x; touch PWNED' byte-exact. On main both arrive unset.

Known Limitations / Intentionally Deferred

  • The %q prefix assumes a bash-compatible pane shell, as the RALPH_DIR prefix already did. With a non-bash default-shell (fish, dash), the KEY=value cmd form or $'…' quoting may not work.
  • A variable the user happens to export as _env_FOO would be forwarded as FOO. Nothing in ralph uses that name pattern outside the snapshots.
  • Forwarded values appear in the pane shell's history. None of the snapshotted keys are credentials (API keys are not .ralphrc keys).

Closes #378

A running tmux server gives new panes its own global environment, so
every env-only setting (CB_* thresholds, MAX_CONSECUTIVE_*, CLAUDE_MODEL,
OPTIONAL_SECTIONS, ...) was lost in --monitor mode and .ralphrc won in
the pane. setup_tmux_session now prepends KEY=%q-value for each
non-empty _env_* snapshot to the loop pane command. The list is derived
from the snapshots, so new keys are forwarded automatically. It runs
before main() loads .ralphrc, so every forwarded value is the user's own;
CLI flags are still appended after and win in the child.
…ale SYNC_* comment (#378)

Internal review: the %q prefix shares RALPH_DIR's bash-compatible pane
shell assumption, and env SYNC_* now rides in the env prefix rather than
being dropped.
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Claude finished @frankbria's task in 7m 0s —— View job


GLM precision bug-hunting review — PR #385

  • Gather PR context (gh pr view, gh pr diff)
  • Check existing PR comments for duplicate findings
  • Read changed code + surrounding context in ralph_loop.sh
  • Verify each suspicious hunk against real callers/callees
  • Post confirmed inline findings — none survived verification
  • Post summary

✅ GLM review: no defects found.

severity file:line finding
— — none confirmed

What was verified before concluding (every verdict against code read in this checkout, not the diff alone):

  • Snapshot purity — all 54 _env_* assignments live only at script top level (ralph_loop.sh:15-39, 74-108), before defaults and lib sourcing; no _env_*= assignments exist in any function or sourced lib, so "${!_env_@}" in setup_tmux_session enumerates exactly the snapshots. setup_tmux_session (ralph_loop.sh:3503) runs before main → load_ralphrc (:2594), so a non-empty snapshot is always the user's own env value — the comment's claim holds.
  • Precedence chain — env prefix (user env) + appended CLI flags; the pane child re-applies _cli_* over .ralphrc (:2599-2614) and env over .ralphrc ([P2.2] Environment does not override .ralphrc for CB_* thresholds, MAX_CONSECUTIVE_*, CLAUDE_MIN_VERSION #369 restore), so CLI > env > file order is preserved in the pane. Checked the combined cases (env+flag for the same key, env+.ralphrc conflict): the child's effective value always matches the parent's.
  • Quoting — printf '%q' empirically escapes !, ~, spaces, leading - on this bash; ;, $(…), backticks, tab/newline become $'…'/backslash forms that parse byte-exact; %q never emits a raw newline, so the tmux send-keys command stays one input line.
  • docker + SYNC_* comment rewrite — verified against the plain-run validation (:2662-2679): only _cli_SYNC_* + docker is a hard error; env-supplied SYNC_* with docker is ignored by a plain run, and the forwarded env is ignored by the docker child identically, so --monitor still matches plain-run behavior.
  • Blast radius — ralph_monitor.sh reads none of the 54 forwarded keys (grep clean), so pane-0-only forwarding is correct; no credential-bearing keys are forwarded; setup_tmux_session still ends in tmux attach-session + exit 0, so the parent doesn't double-run main.

One theoretical edge was investigated and not reported as a finding, for the record: on macOS's bash 3.2, if its printf '%q' leaves ! unescaped (bash 5 escapes it), a !-containing env value would hit interactive history expansion in the pane shell (event not found → loop pane idles). The premise (bash 3.2 %q behavior) can't be verified in this environment and the trigger is an exotic value, so it doesn't meet the confirmed-defect bar. The PR's own known-limitations section already covers the bash-compatible-pane-shell assumption.

@frankbria

Copy link
Copy Markdown
Owner Author

Cross-family review — codex (pre-PR pass at b6745d2; the only later commit, dca1cb4, touches CLAUDE.md only)

opencode/GLM (the first choice) stalled with no output for 180 s, which is an outage signature, so per the reviewer chain this pass used codex.

The change safely forwards snapshotted non-empty environment settings into the tmux loop pane and adds focused regression coverage. I did not identify any actionable correctness, security, or maintainability issues introduced by this diff.

Verdict: no actionable findings. codex also ran a small probe confirming that "${!_env_@}" enumerates the snapshots and that the empty-value check skips unset ones.

@frankbria

Copy link
Copy Markdown
Owner Author

Demo verification (Showboat, showboat verify passes)

I used a real tmux server, started first and without the settings, to play the server that is already running. The real setup_tmux_session then ran with CB_NO_PROGRESS_THRESHOLD=10 CLAUDE_MODEL='claude-x; touch PWNED' in the caller's environment; MAX_CONSECUTIVE_TEST_LOOPS was not set. The loop pane ran a fake ralph that recorded what its process received.

Criterion this branch main Status
Env-only settings reach the loop despite a running server CB_NO_PROGRESS_THRESHOLD=[10], CLAUDE_MODEL=[claude-x; touch PWNED] both <unset> VERIFIED
%q quoting, no injection value arrives byte-exact; no PWNED file created (nothing forwarded) VERIFIED
Unset keys not forwarded MAX_CONSECUTIVE_TEST_LOOPS=[<unset>] <unset> VERIFIED
Every key (table-driven) mock test covers all 54 _env_* keys parsed from ralph_loop.sh; quoting test checks ;, $(…), backticks, quotes, \, |, <, >, tab and newline values byte-exact — VERIFIED

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

Code Review: fix(monitor) forward explicit env settings to the tmux loop pane

Overview

This PR closes a genuine gap: when a tmux server is already running, new pane processes inherit the server environment rather than the calling shell's, so env-only settings (circuit breaker thresholds, CLAUDE_MODEL, etc.) silently reverted to defaults or .ralphrc values inside the loop. The fix is clean and well-targeted.

Implementation

Core mechanic is idiomatic and self-maintaining. Using the indirect expansion pattern to enumerate snapshots means a newly snapshotted key is forwarded automatically without maintaining a separate list. This is the right approach.

printf '%q' is the correct tool here. It produces bash-safe quoted output that survives re-evaluation in the pane shell. The injection test validates this end-to-end with metacharacters, whitespace, and command substitution syntax - all arriving byte-exact.

Precedence chain is preserved. The loop_env prefix is inserted between env_prefix (RALPH_DIR) and the ralph command, and CLI flags still append after it. The child's own load_ralphrc() and env* restore then applies, giving: CLI > env prefix > .ralphrc - consistent with the non-monitor path.

Concerns / Observations

No. 1 - SYNC_* values ride the env prefix even with --sandbox docker - verify child behavior is safe.

When SANDBOX_PROVIDER=docker and the user set SYNC_INCLUDE in their environment, the new loop_env prefix now forwards SYNC_INCLUDE=value to the child process. The child's _env_SYNC_INCLUDE restore would then set SYNC_INCLUDE in the child. The comment update says the docker child 'ignores it just like a plain run does' - this appears correct since the docker sandbox sync validation only fires on the CLI flags path - but it is worth confirming no new warning or error surfaces in the child when SYNC_INCLUDE is non-empty with docker provider.

The comment change is an improvement over the previous wording ('merely not forwarded'), so no code change is needed - just flagging it as worth a smoke test.

No. 2 - Test helper _pane0_cmd uses a glob strip that could mismatch for unusual session names.

The shortest-match glob correctly strips the 'tmux send-keys -t session ' prefix for all practical tmux session names. Fine for a test helper - just noting it as a limit.

No. 3 - The injection test PWNED-file checks are relative to CWD.

The check correctly verifies no injection happened. Since the mock ralph writes to OUT_DIR, this will not produce false negatives. Minor observation only.

Test Coverage

Three focused tests match the three acceptance criteria from the PR:

  • Forwards every env-snapshotted key: All 54+ env* keys forwarded; monitor pane untouched
  • Quotes values byte-exact with no injection: Semicolons, dollar-parens, backticks, quotes, tabs, newlines
  • Forwards only keys set in environment: Empty and absent snapshots leave .ralphrc in charge

The table-driven key enumeration (reading from the real ralph_loop.sh) is the right approach - avoids the hand-copy drift problem documented in the test standards.

CLAUDE.md Update

The added sentence accurately describes the new behavior and is placed in the right section. No issues.

Verdict

This is a clean, well-tested fix for a real operational bug. The implementation approach (snapshot enumeration + printf '%q' quoting) is maintainable and secure. The one item worth a quick check is whether SYNC_* appearing in the docker child environment triggers any new log noise, but it is not blocking.

Looks good to merge.

@frankbria
frankbria merged commit bed9312 into main Oct 9, 2026
5 checks passed
@frankbria
frankbria deleted the fix/issue-378-monitor-env-forwarding branch October 9, 2026 23:09
@frankbria

Copy link
Copy Markdown
Owner Author

Post-merge disposition: nothing filed. Dropped with justification: (1) the non-bash pane shell limitation. It predates this PR (the RALPH_DIR prefix has it too) and only matters for values that need $'…' quoting (control characters) under dash or fish. (2) A stray user-exported _env_FOO; ralph uses no such name outside the snapshots. (3) The unverified bash 3.2 %q/! history-expansion edge, which needs an exotic value. Lessons: 1 entry appended to tasks/lessons.md (gh pr edit fails silently on the classic-Projects GraphQL error, so use the REST PATCH).

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.

[P2.6] --monitor drops env-only settings when a tmux server is already running

1 participant