* test(#3245): failing-first coverage for host runtime detection in init Locks the behavior epic #2313 Phase 5 must produce before any of it exists: init reports the detected host, explicit GSD_RUNTIME and config runtime still outrank detection, non-Codex sessions are untouched, and nothing is ever written to shared defaults (#2297). * enhance(#3245): report the detected host runtime in init init reported agent_runtime: claude inside a Codex session, and resolved agents_dir to the Claude agents root with agents_installed: true — a spuriously healthy triple. Runtime identity was only ever read from GSD_RUNTIME or an explicit runtime in .planning/config.json. Adds a detection rung beneath both explicit sources, in a new pure module. Codex is identified from its own documented session environment (CODEX_SANDBOX / CODEX_SANDBOX_NETWORK_DISABLED), else an explicitly exported CODEX_HOME whose config.toml exists. The default ~/.codex is never probed: that file exists on every machine that has run Codex, so probing it would misreport other runtimes' sessions. resolveRuntime keeps its exact contract and all 71 dependents, including formatGsdSlash command-style emission; only withProjectRoot consumes the new rung. Nothing is written on any path (#2297). Explicit config still wins (#2517). * fix(#3245): make the parity guard real and single-source the marker Four independent review passes found the generative-fix-divergence guard was vacuous: it asserted agreement at the one input where inferPreferredRuntime and detectHostRuntime do not differ, so it could not fail. It now pins the actual divergence point (CODEX_HOME set, config.toml absent) and records that the asymmetry is deliberate. The config.toml marker is now single-sourced from update-context.cts and imported, rather than carried independently by two surfaces. tests/helpers.cjs now scrubs CODEX_SANDBOX and CODEX_SANDBOX_NETWORK_DISABLED: GSD reads them, so an ambient Codex session would otherwise make the non-codex control test fail non-deterministically. Also: detection is throw-safe end to end rather than only around the fs probe; the Windows-join test is replaced with one that can actually fail (trailing-separator, catches hand-rolled concatenation); the #2297 no-write proof now wraps resolveReportedRuntime, the function that ships, across all three ladder outcomes. * chore(#3245): backfill changeset pr number --------- Co-authored-by: sim <sim@local>
4.8 KiB
How to control which host runtime GSD reports
Goal: Make agent_runtime — the runtime GSD reports it is running under, and the one it checks for installed agents — say what you actually want, and know why it says what it says.
Prerequisites: A project with a .planning/ directory. Read the current answer with:
node gsd-tools.cjs init plan-phase 1 --raw
The JSON carries agent_runtime, plus the agents_dir / agents_installed / missing_agents triple derived from it. For the config key itself, see runtime.
The ladder, in order
GSD answers "which runtime am I?" from the first of these that produces a value:
| # | Source | Set it by | Wins over |
|---|---|---|---|
| 1 | GSD_RUNTIME environment variable |
GSD_RUNTIME=opencode in the environment |
everything below |
| 2 | runtime in .planning/config.json |
"runtime": "codex" |
detection and the default |
| 3 | Host detection (added in v1.11) | nothing — it is automatic | the default only |
| 4 | Default | — | — (claude) |
Rungs 1 and 2 are explicit — you stated an intent, and GSD does not second-guess it. Rung 3 only ever runs when both are unset. This is what preserves the behavior of every existing config: if you have ever set runtime, nothing about your setup changes.
What detection actually looks at
Detection answers a narrow question — is this process running inside a Codex session? — and only from signals Codex itself documents.
| Signal | Where it comes from | Why it is trusted |
|---|---|---|
CODEX_SANDBOX is set and non-empty |
Codex injects it into child processes it spawns via Seatbelt | Documented in Codex's own AGENTS.md; present in exactly the processes Codex runs, which is where GSD runs |
CODEX_SANDBOX_NETWORK_DISABLED is set and non-empty |
Codex injects it when running the shell tool with the network sandbox on | same source |
CODEX_HOME is set and $CODEX_HOME/config.toml exists |
you exported it | Exporting CODEX_HOME is you designating a Codex state root; the config.toml check confirms the directory is a real one |
Anything else — no signal — means no detection, and rung 4 applies.
The two questions this page exists for
"I'm in a Codex session and it still says claude"
Work down the ladder:
| Check | What to do |
|---|---|
Is GSD_RUNTIME set to something else? |
echo $GSD_RUNTIME — it outranks everything. Unset it, or set it to codex. |
Does .planning/config.json have a runtime? |
An explicit "runtime": "claude" wins over detection, by design. Change it or remove the key. |
| Is your Codex sandbox off? | With sandbox_mode = "danger-full-access", Codex sets neither sandbox variable, so there is nothing for GSD to detect. This is the most common cause. |
| Still nothing? | Set it explicitly. Detection is a convenience, not a contract — "runtime": "codex" in .planning/config.json is the supported, permanent answer. |
Detection is deliberately conservative: when it cannot tell, it reports the old default rather than guessing. A wrong agent_runtime sends GSD looking for agents in the wrong directory, so silence is the safer failure.
"It says codex and I am not using Codex"
One cause, and it is benign:
CODEX_HOMEis exported in your shell profile, and$CODEX_HOME/config.tomlexists.
GSD treats an explicitly-exported CODEX_HOME as you designating a Codex root. If you keep it exported globally but work in another runtime, pin the runtime for that project:
{ "runtime": "claude" }
in .planning/config.json. Rung 2 outranks detection, so this settles it permanently.
Note what is not a cause: simply having Codex installed. GSD never probes the default ~/.codex/config.toml. That file exists on every machine that has ever run Codex, so treating it as a signal would misreport every other runtime's sessions — which is precisely why the check requires you to have exported CODEX_HOME yourself.
What this does not change
Detection moves the reported runtime and the agent-installation check that hangs off it. It deliberately does not touch:
- Model resolution. Runtime-aware tier resolution still reads the explicit
runtimeconfig key only. A detected-Codex session does not gain or lose model pins — see Runtime-aware profiles and ADR-2313. - Slash-command style. GSD still emits
/gsd-<cmd>unless the runtime was set explicitly; a detected-Codex session does not switch to the$gsd-<cmd>shell-var form. Setruntimeexplicitly if you want that. - Any file. Detection reads environment variables and checks for one file's existence. It never writes
~/.gsd/defaults.json, never edits.planning/config.json, and never shells out.