Set FORGE_CHANNEL on the tmux session, not the ttyd unit #24

Merged
pedromcaraujo merged 1 commit from fix-forge-channel-env into main 2026-08-03 14:14:04 +01:00

Closes #22.

The bug

FORGE_CHANNEL names the wrong channel in every session but one.

The channels share one long-lived tmux server, spawned by whichever ttyd
starts first. new-session -A carries only tmux's update-environment
allowlist across, so a unit-level environment.FORGE_CHANNEL reaches the ttyd
process but never the tmux session. What a pane inherits is the server's
environment — the first channel's value, for everybody.

Live on nix-dev, all four sampled channels:

$ tmux show-environment -t forge FORGE_CHANNEL
FORGE_CHANNEL=          <- unset in EVERY real session

...yet a process inside session forge prints FORGE_CHANNEL=vault. The stale
value arrives through the process tree, not through tmux at all. It has never
worked as documented; ttyd.nix still advertised the broken contract.

The fix

Per-session -e on new-session — the only spelling that's right for all
channels, since it's evaluated per session rather than inherited:

exec tmux new-session -A -s <name> \
  -e 'FORGE_CHANNEL=<name>' \
  -e 'CLAUDE_CHANNEL=<name>' \
  bash -c '...'

Removed the unit-level environment block (with a comment saying why, so
nobody re-adds it) and corrected the module header.

Why fix rather than close

No consumer exists today — Pedro's statusline is driven entirely by Claude
Code's own stdin JSON (model, cwd, context window) and never reads it, and
there's no other reference in either repo. But this is a public module, the
contract is documented, and a variable that silently reports the wrong answer
is a worse thing to leave lying around than either a working one or none at
all. The fix is two lines.

Test plan

  • nix flake check.

  • Inspected the generated launcher for two channels — correct -e per
    channel, properly quoted via escapeShellArg.

  • End-to-end against the same already-poisoned shared server the real
    channels run on (its process env carries FORGE_CHANNEL=vault):

    session chana   FORGE_CHANNEL=chana
    session chanb   FORGE_CHANNEL=chanb
    
    Correct both in `show-environment` **and** as read by a process inside
    the pane — confirming the `-e` value beats the inherited one.
    

Caveat

Like the session command, this applies when a session is created. -A onto a
live session leaves its environment alone, so existing channels keep the stale
value until recycled (tmux kill-session -t <ch> then reconnect).

Closes #22. ## The bug `FORGE_CHANNEL` names the wrong channel in every session but one. The channels share **one** long-lived tmux server, spawned by whichever ttyd starts first. `new-session -A` carries only tmux's `update-environment` allowlist across, so a unit-level `environment.FORGE_CHANNEL` reaches the ttyd process but never the tmux session. What a pane inherits is the *server's* environment — the first channel's value, for everybody. Live on nix-dev, all four sampled channels: $ tmux show-environment -t forge FORGE_CHANNEL FORGE_CHANNEL= <- unset in EVERY real session ...yet a process inside session `forge` prints `FORGE_CHANNEL=vault`. The stale value arrives through the process tree, not through tmux at all. It has never worked as documented; `ttyd.nix` still advertised the broken contract. ## The fix Per-session `-e` on `new-session` — the only spelling that's right for all channels, since it's evaluated per session rather than inherited: ```nix exec tmux new-session -A -s <name> \ -e 'FORGE_CHANNEL=<name>' \ -e 'CLAUDE_CHANNEL=<name>' \ bash -c '...' ``` Removed the unit-level `environment` block (with a comment saying why, so nobody re-adds it) and corrected the module header. ## Why fix rather than close No consumer exists today — Pedro's statusline is driven entirely by Claude Code's own stdin JSON (`model`, `cwd`, context window) and never reads it, and there's no other reference in either repo. But this is a public module, the contract is documented, and a variable that silently reports the wrong answer is a worse thing to leave lying around than either a working one or none at all. The fix is two lines. ## Test plan - [x] `nix flake check`. - [x] Inspected the generated launcher for two channels — correct `-e` per channel, properly quoted via `escapeShellArg`. - [x] End-to-end against the **same already-poisoned shared server** the real channels run on (its process env carries `FORGE_CHANNEL=vault`): session chana FORGE_CHANNEL=chana session chanb FORGE_CHANNEL=chanb Correct both in `show-environment` **and** as read by a process inside the pane — confirming the `-e` value beats the inherited one. ## Caveat Like the session command, this applies when a session is *created*. `-A` onto a live session leaves its environment alone, so existing channels keep the stale value until recycled (`tmux kill-session -t <ch>` then reconnect).
forge: set FORGE_CHANNEL on the session, not the unit
All checks were successful
CI / check (pull_request) Successful in 7s
5dbb21728d
It named the wrong channel in every session but one. The channels share a
single long-lived tmux server, spawned by whichever ttyd starts first, and
`new-session -A` carries only tmux's update-environment allowlist across — so
a unit-level FORGE_CHANNEL reaches ttyd but never the session. What a pane
actually inherits is the *server's* environment, i.e. the first channel's
value.

Live on nix-dev: a pane in session `forge` reports FORGE_CHANNEL=vault, and
`tmux show-environment` shows the variable unset in every real session — the
stale value arrives through the process tree, not through tmux at all.

Fix it with per-session `-e` on new-session, which is the only spelling that
is right for all of them. Verified against the same shared, already-poisoned
server: two sessions created back to back each report their own name, both in
`show-environment` and in a process inside the pane, where the `-e` value
correctly beats the inherited one.

Like the session command itself this applies when a session is *created*;
`-A` onto a live session leaves its environment alone, so existing channels
keep the old value until recycled.

Closes #22
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pedromcaraujo/forge!24
No description provided.