Skip to content

Let a command see the session it is running for, and retire the alias that stood in for it - #51

Merged
weilei0120 merged 2 commits into
mainfrom
fix/claw-session-id-reaches-child
Sep 17, 2026
Merged

weilei0120 merged 2 commits into
mainfrom
fix/claw-session-id-reaches-child

Conversation

@zoroyihan7

@zoroyihan7 zoroyihan7 commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

CLAW_SESSION_ID is set on the sandbox and then filtered out before any model-issued command can read it. Inside a sandbox, echo $CLAW_SESSION_ID is empty.

Two mechanisms remove it, and neither is aimed at it:

  • The child environment is an allowlist — WORKSPACE_FACING in hands/src/runtime/child-privilege.ts, built up rather than filtered down so no future secret arrives by default. The session id was simply never added.
  • The other route in, the per-request env file, refuses every BOOTSTRAP_OWNED name so a request cannot overrule what the process was launched with. CLAW_SESSION_ID is on that list.

Why nobody noticed

The same value already reached the child under a second name. HYPERLOOM_SESSION_ID is set on the adjacent line of ensure-hands.ts and is not bootstrap-owned, so it passes. The name every consumer actually reads did not.

In AMD-AGI/Hyperloom the run manifest (inference_optimizer/session/manifest.py), the LLM attribution (common/llm_attribution.py), the optimizer bootstrap (inference_optimizer/cli/bootstrap.py) and the specialist subprocess env allowlist (orchestrator/specialists/subprocess_.py) all read CLAW_SESSION_ID. On a real run, the manifest recorded:

claw_session_id = None

So the session correlation those paths exist to provide had never worked — the session breakdown always stored null and trace grouping always fell back. Other downstream tooling reads the same name and silently degrades to a generic session id.

It also broke a documented contract in the other direction: Hyperloom's optimizer skill told an agent to branch on whether $CLAW_SESSION_ID is set to choose how it detaches a long run. That branch could never take its sandbox side. (Fixed separately in AMD-AGI/Hyperloom#1400, by keying on the bash tool instead.)

The change

Two commits.

fix(hands) — one name added to WORKSPACE_FACING, plus a regression test.

WORKSPACE_FACING is the right lever rather than an exemption in BOOTSTRAP_OWNED, and not because the latter would not work — user-supplied env is already denied the whole CLAW_* family by isClawInternalEnv, and ensure-hands assigns this key after spreading the caller's env, so it could not be spoofed either way. It is that the launch command is the authoritative source: it carries the value directly, so the child gets it even on a path that writes no per-request env file at all, where APPLIED_ENV_KEYS is empty and the other route yields nothing.

It costs no secrecy: the identical value is already exposed under the other name, it is an identifier rather than a credential, and the credential the list exists to withhold — AUTH_CLAW_TOKEN — stays omitted.

chore(brain) — retire HYPERLOOM_SESSION_ID: the injection in ensure-hands.ts and the user-env.ts deny entry that reserved the name.

The alias was never a second contract. Its one reader outside this repository resolved its session from that name and from no other, while falling back to a CLAW_* name for every other field it parses — the shape of a consumer that tried CLAW_SESSION_ID, found it empty, and used the name that worked. That reader now consults CLAW_SESSION_ID as well, so nothing is left depending on the alias.

INFERENCE_OPTIMIZER_SESSION_LAYOUT on the following line has no reader I could find either, but it carries configuration rather than a duplicate identifier and is also injected by an unrelated path, so it is left alone here.

Verification

Unit: the claw/packages/hands suite, 223 tests, 222 pass. The one failure (a zombie is not work…) needs python3, which the runtime image does not carry; it fails the same way on unmodified main. The new regression test sits alongside the existing test that scans the whole child environment for the token's value — that one still passes, which is the point: the session id is visible and the credential is not.

End to end, on a cluster running this branch, driving a real GPU workload through a sandbox:

  • CLAW_SESSION_ID reads back inside the sandbox as the session's own id; HYPERLOOM_SESSION_ID is gone.
  • An agent given no hint about detaching launched a 30-minute job with run_in_background=true, and the job outlived the agent turn.
  • The run manifest recorded a real claw_session_id instead of None — the correlation working for the first time.
  • Keepalive held the sandbox while the job ran (idle_handle_kept_background_work running=1), flipped to idle within one probe interval of the job exiting, and reclaimed the sandbox shortly after. Both directions of the gate behave.

@zoroyihan7
zoroyihan7 requested a review from a team as a code owner September 17, 2026 04:34
CLAW_SESSION_ID was set on the sandbox and then filtered out before any
model-issued command could read it, so a run could not name the session it
belonged to.

Two mechanisms removed it, and neither was aimed at it. The child environment is
an allowlist -- WORKSPACE_FACING, built up rather than filtered down so that no
future secret arrives by default -- and the session id was simply never added to
it. The other route in, the per-request env file, refuses every BOOTSTRAP_OWNED
name so that a request cannot overrule what this process was launched with;
CLAW_SESSION_ID is on that list for the good reason that a request must not
rewrite its own session id, and the side effect was that the file could not
carry it either.

What made this invisible is that the same value already reached the child under
a second name. HYPERLOOM_SESSION_ID is set on the adjacent line and is not
bootstrap-owned, so it passes -- and the name every consumer actually reads did
not. Hyperloom's manifest and its LLM attribution both read CLAW_SESSION_ID; on
a real run against this cluster, manifest.json recorded
`claw_session_id: null`, so Claw-session correlation has never worked.

Adding the name to WORKSPACE_FACING costs no secrecy: the identical value is
already exposed under the other name, it is an identifier rather than a
credential, and the credential this list exists to withhold -- AUTH_CLAW_TOKEN
-- stays omitted. Nor does it become user-writable: isClawInternalEnv already
denies the whole CLAW_* prefix in user env.

The alias stays for now, and a scan of the sibling repositories is what stopped
it being deleted: the Hyperloom TUI resolves its session from
HYPERLOOM_SESSION_ID and from no other name. A comment records that, and records
what it looks like -- every other field in that file falls back to a CLAW_* name
and this one does not, which is the shape of a consumer that found
CLAW_SESSION_ID empty. The alias is a workaround for this bug, not a second
contract, and it can go once that consumer reads the platform's own name.

Test added for the visibility, alongside the existing one that scans the whole
child environment for the token's value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@zoroyihan7
zoroyihan7 force-pushed the fix/claw-session-id-reaches-child branch from 03bac77 to 304f447 Compare September 17, 2026 04:48
It existed because the platform's own name for the session could not be read
from inside a sandbox. The parent commit fixes that, so the alias is carrying
nothing the line above it does not.

Both halves go: the injection in ensure-hands.ts, and the user-env deny entry
that reserved the name. Nothing in this repository reads it, and the one
consumer outside it -- the Hyperloom TUI, which resolved its session from this
name and no other -- reads CLAW_SESSION_ID as of AMD-AGI/Hyperloom-Web's
fix/tui-session-id-from-claw.

Merge order matters, which is why this is a commit of its own rather than part
of the fix. Landing it before that TUI change ships leaves an older TUI unable
to find the session it is running inside; it degrades rather than crashes --
initializeSession falls through to createSession() and the run gets a fresh
session instead of the one it is sitting in -- but it is a regression, and it is
avoidable by waiting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@zoroyihan7 zoroyihan7 changed the title Let a command see the session it is running for Let a command see the session it is running for, and retire the alias that stood in for it Sep 17, 2026
@weilei0120
weilei0120 merged commit e93e9c2 into main Sep 17, 2026
101 checks passed
@weilei0120
weilei0120 deleted the fix/claw-session-id-reaches-child branch September 17, 2026 05:10
zoroyihan7 added a commit to AMD-AGI/Hyperloom that referenced this pull request Sep 17, 2026
… the run

The rule said: under Claw (`$CLAW_SESSION_ID` set) use the bash tool's
`run_in_background=true`, everywhere else `setsid nohup ... &`. The intent was
right and the test was unreachable -- `$CLAW_SESSION_ID` never reached the shell
a model's command runs in, so every agent following this fell through to
`setsid nohup`, which is the form the rest of this document exists to prevent,
failing the way it warns: invisibly, from inside the run. That is fixed in
AMD-AGI/PrimusClaw#51, so the test now answers.

It is not sufficient on its own, though. Being on the platform does not say the
deployment serves background shells: with them switched off the bash tool has no
`run_in_background` parameter at all, and `setsid nohup` is then correct even
there. So the rule takes both -- the session id for "something is reclaiming
this sandbox on what it can see", the parameter for "and there is a tool call to
hand the block to".

Keying on the parameter alone was considered and is wrong in the other
direction: agent harnesses off-platform have background modes of their own, and
pointing a local run at one regresses exactly what `setsid` is there for, since
nothing off-platform reclaims a sandbox and such a shell may not outlive the
connection that started it.

Every site that stated the rule moves together: the packaged SKILL.md (the
definition, the launch block and the robustness monitor), operations.md (the
detach rule, the pid reconciliation and the monitor), the three example skills,
and the two pre-release demo prompts. The prompts keep the literal `setsid
nohup`, which test_pre_release_stall_liveness asserts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants