* feat(#3243): sync installed codex .toml model/effort to the passive posture Implements ADR-2313 D7, and owns the Codex .toml typed IR that Phase 1's review assigned to this phase. The IR exists for a structural reason, not tidiness: this phase has to PARSE these files, and a parser kept bug-compatible with a separate renderer is the generative-fix-divergence shape this epic already dealt with once for the model predicate. So Phase 2's parsing MOVES here rather than being copied — agent-install-check now imports it, and its test file passing unchanged is the proof the extraction altered nothing. The load-bearing property is byte-identical round-trip: render(parse(x)) === x. Without it a sync silently reformats a user's file — line endings, key order, BOM, trailing newline — turning a two-line repair into a whole-file diff in their dotfile repo. The IR keeps original lines and removes targeted ones rather than reconstructing from parsed fields, which is what makes that property hold. It also reconciles a real contradiction between Phase 2 and ADR-2313. An unterminated developer_instructions block: the reader excludes the rest of the file, deliberately failing toward a false positive, because misreading prose as a pin only wastes a user's time. The writer must refuse, because proceeding on a malformed document rewrites it. A false positive is the safe direction for a reader and the dangerous one for a writer. So the parse reports the fact and the two consumers branch on it — one parse, one truth, two policies, instead of two parsers that agree today. The sync leaves a legal real-Codex pin and its coupled effort untouched, reported skipped rather than synced; strips a stale Anthropic or tier model and an orphaned effort; keeps dry-run as the default; refuses any file whose parse fails; and skips symlinks exactly as the Claude path already did. The Claude path itself is byte-identical. PARSE_REASON.NO_HEADER from the ADR's illustrative snippet is deliberately not implemented — a missing header is legal, not an error, so it would be a dead enum member that the enum-lock test then pins. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(#3243): preserve per-line endings and make the codex write atomic Two findings from an isolated review, both in the write path. BLOCKER: mixed line endings broke the byte-identical round-trip. `eol` was a single whole-file flag and split(/\r?\n/) discarded each line's own terminator, so render re-joined with ONE style and normalized every line — even with zero strips performed. A file with one CRLF line and the rest LF came back fully converted. That falsified the A14 guarantee, violated the design's "must not silently rewrite every line", and made the CONTEXT.md glossary claim wrong. It was untested because A12 and B15 only cover PURE CRLF; no mixed-ending fixture existed anywhere. Fixed by keeping each line's terminator alongside its content, so render is a plain concatenation and a strip removes only the target line and its own terminator. `eol` survives as informational metadata that render never reads. Seven fixtures added for the paths nothing exercised: mixed endings unmodified and with a strip, a lone \r, a file ending on the block's closing ''' with no newline, multiple trailing newlines, a BOM-only file, and an empty file. MINOR, but it contradicted this phase's own contract: the write was in-place open-truncate, so a failure between truncate and completion leaves a truncated .toml — exactly what ADR-2313 says must never happen. The Codex path now writes a sibling temp file and renames over the target, which is atomic on one filesystem, with cleanup on failure. It uses the repo's existing retryRenameSync rather than a hand-rolled rename, and deliberately NOT platformWriteSync, whose normalizeContent would mangle the very CRLF and trailing-newline bytes the round-trip property exists to preserve. The Claude path keeps its in-place write untouched. It has the same shape, but changing it is not this phase's business and its tests must stay byte-identical. B20 previously mocked writeFileSync to throw BEFORE touching anything, so it proved nothing about a mid-write failure — its passing comment was true only because of how the mock was built. It now performs a real truncated write wherever writeFileSync is called, catching both the naive direct-to-target path and the new temp path, and asserts the target is byte-identical afterwards with no stray temp file left. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(#3243): preserve the trailing-newline state when stripping a last line Caught by B17, one of this phase's own tests — the suite working, not a test problem. Content is reconstructed as the concatenation of lines[k] + terminators[k], so a file with no trailing newline has '' as its last terminator. removeLine spliced out both arrays at the same index, which is right for a middle line but wrong for the last one: it dropped the empty terminator and left the PREVIOUS line's newline in place. A file ending `...\nmodel = "sonnet"` with no trailing newline came back as `...\n`, gaining a newline the user never wrote. The new last line now inherits the removed line's terminator, so a removal leaves the file exactly as if that line had never been written. Removing the only line yields an empty file rather than a stray terminator. Both stripModel and stripReasoningEffort funnel through the one removeLine, confirmed rather than assumed, so a single fix covers both — including the row-B7 shape where a stale model and its orphaned effort are removed in sequence and the second removal targets the last line. Two of the four new cases are honestly not red-first and say so in their comments: removing a last line that HAS a trailing newline only exposes the bug under mixed EOL, since uniform files coincidentally have equal terminators on both sides; and removing the only line already degenerated correctly through Array.slice. They are kept as guards for the new branch rather than dressed up as catches. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(#3243): document the codex repair path and close the loop How-to: the Codex-400 entry added in Phase 2 told users to re-run the installer, because that was the only repair available then. It now leads with `effort sync` and keeps the reinstall as the alternative, with the reason to prefer one — a reinstall regenerates the agent files wholesale, so anyone who hand-edited theirs loses those edits. Detect, preview, apply is now one continuous path in one place. Reference: docs/COMMANDS.md had no `effort sync` entry at all — the same gap `validate agents` had in Phase 2, found the same way. The entry documents BOTH runtimes, because the command genuinely forks on runtime and describing only the new half would misdescribe it. The write flag is `--apply`. The design doc and test matrix both said `--no-dry-run` throughout, which does not exist — verified against the actual arg parser in gsd-tools.cjs before writing. Documenting a flag that does not exist is worse than documenting nothing, because it fails at the moment someone needs it. Both surfaces state that only the targeted lines are removed and every other byte is preserved. That is a user-visible guarantee rather than an implementation note: it is the difference between a two-line diff and a reformatted file in someone's dotfile repo, it is what the IR's round-trip property exists to deliver, and writing it down makes it a contract a future change has to break knowingly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(#3243): inherit the trailing-newline state, not the line ending style My previous rule was subtly wrong and this phase's own test caught it. "The new last line inherits the removed line's terminator" copies the removed line's STYLE as well as its presence. A26 uses mixed endings on purpose — line one terminated \r\n, the model line terminated \n — so inheriting silently rewrote line one's ending to \n. That is precisely the defect class the mixed-EOL blocker fix existed to eliminate, reintroduced one layer down by the fix for it. The correct rule inherits the EMPTINESS only. If the removed line had no terminator, the new last line loses its own, preserving "this file has no trailing newline". Otherwise the new last line keeps its own terminator: it is already a newline, and already the right style for that line. A26's assertion moved too, and that deserves saying plainly rather than burying: it previously encoded my wrong rule. Changing a test to match the implementation is usually the mistake, so it was checked from first principles instead — a file whose first line ends \r\n and whose last line ends \n, with that last line removed entirely, must be the first line with its own \r\n intact. The new expectation is what the user's file should actually look like; the old one was wrong. A29 adds the interaction nothing covered: the compounding case (strip a stale model, then its orphaned effort, the second removal landing on the last line) with non-uniform endings either side. The two fixes meet there and nothing exercised the meeting point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(#3243): drop the phantom trailing line from the IR representation Root cause, not another patch on the removal rule. Three consecutive fixes there each surfaced the next issue, which was the signal that the data model was wrong. splitPreservingTerminators left a phantom empty final entry for any file ending in a newline: "a\nb\n" became lines ['a','b','']. So for the common case the real last content line was NOT the last array element, removeLine's isLastLine check never matched it, and every rule I gave was reasoning about the wrong element. What hid it: render was already a plain concatenation, so a phantom empty line with an empty terminator contributes nothing to the output. A14's byte-identical round-trip could never have caught it — the defect is byte-neutral until a removal shifts the index arithmetic under it. That is worth recording, because "the round-trip test is green" was exactly the reassurance that kept the search pointed elsewhere. The representation is now 1:1 — terminators[i] follows lines[i] and may be '' — with no phantom, verified across empty, no-trailing-newline, trailing-newline, blank-line and mixed-CRLF inputs. render stays a plain concat and needs no special cases. With the phantom gone the removal rule is correct as stated and finally applies to the genuinely last element. Consumers checked rather than assumed: the block-range detector and header scanner are agnostic to array shape, and Phase 2's reader uses its own independent split, so tests/agent-install-check.test.cjs is untouched and still passes unchanged. One test expectation was wrong and is corrected rather than quietly adjusted: A18 asserted a 7-element terminators array whose trailing '' was the phantom itself. It now asserts the six real terminators, which is what the invariant lines.length === terminators.length requires. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(#3243): backfill changeset pr number (#3296) --------- Co-authored-by: sim <sim@local> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
13 KiB
How to recover and troubleshoot
Goal: Identify and fix common problems — from lost context and corrupted state to installation failures and permission errors — using a conditional recipe structure.
Prerequisites: GSD Core is installed. For install problems specifically, see Install on your runtime.
Context and session problems
If you have lost track of where you are
/gsd-progress
Reads all state files and tells you exactly where you are and what to do next.
To automatically advance to the correct next step:
/gsd-progress --next
If you are starting a new session and need to restore context
/gsd-resume-work
Restores your full session context from the last handoff, including current phase, planning decisions, and where work stopped.
If quality is dropping during a long session
Clear your context window between major commands:
/clear
Then restore state:
/gsd-resume-work
GSD is designed around fresh contexts. Every subagent already gets a clean 200k window. The main session degrades over time — clearing it and resuming is the correct remedy, not pushing on.
If you want to save context before stopping
/gsd-pause-work
Creates .planning/HANDOFF.json with your current position. Add --report to also write a post-session summary to .planning/reports/:
/gsd-pause-work --report
Planning integrity problems
If .planning/ integrity is uncertain
/gsd-health
Reports status across errors, warnings, and informational notes:
| Status | Meaning |
|---|---|
HEALTHY |
All expected artefacts present and well-formed |
DEGRADED |
Warnings that should be addressed but work can continue |
BROKEN |
Critical errors that will block execution |
Common auto-repairable issues (errors E004, E005; warnings W003, W008):
/gsd-health --repair
This recreates missing STATE.md, resets a corrupt config.json to defaults, and adds any missing configuration keys. It will not overwrite PROJECT.md or ROADMAP.md.
If STATE.md references a phase that does not exist
This produces warning W002. Use the state CLI to diagnose and repair:
node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" state validate
Preview what a sync would change without writing:
node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" state sync --verify
Apply the sync:
node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" state sync
These commands reconstruct STATE.md from actual project state on disk. They replace manual STATE.md editing.
If you see "Project already initialised"
.planning/PROJECT.md already exists. /gsd-new-project is a safety check. If you genuinely want to start over, delete the .planning/ directory first:
rm -rf .planning/
Then re-run /gsd-new-project.
If context-window utilisation is high
/gsd-health --context
Probes the context-window utilisation guard. Warns at 60 %, critical at 70 %. If you are above the warning threshold, run /clear followed by /gsd-resume-work before starting the next major command.
Execution problems
If an executor gets "Permission denied" on Bash commands
GSD's gsd-executor subagents need write-capable Bash access. Add the required patterns to ~/.claude/settings.json under permissions.allow. At minimum:
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git merge:*)",
"Bash(git checkout:*)"
For stack-specific patterns (Rails, Python, Node, Rust), see the full table in docs/USER-GUIDE.md under "Executor Subagent Gets Permission denied".
Per-project alternative: add the same block to .claude/settings.local.json in your project root.
If execution fails or produces stubs
Check whether the plan is too ambitious. Plans should have two or three tasks at most. If tasks are too large they exceed what a single context window can produce reliably. Re-plan the phase with smaller scope:
/gsd-plan-phase 1
For systematic diagnosis of what went wrong, see Debug a failed execution.
If you see "FATAL: worktree base mismatch" or the exit-42 warning
This happens when your current branch is ahead of the repository's default branch (for example, an unmerged milestone or feature branch). Claude Code forks executor worktrees from origin/HEAD, not your HEAD, so plan files that exist only on your branch are absent inside the worktree.
Since the fix landed, GSD automatically degrades to sequential execution on the main working tree and prints a one-line warning — the phase will complete without any action from you. To restore parallel execution permanently, run:
node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" worktree set-baseref
For a full explanation and all available options, see Fix the worktree base-mismatch (exit 42) error.
If parallel execution causes build lock errors or pre-commit hook failures
This is caused by multiple agents triggering build tools simultaneously. GSD handles this automatically since v1.26. If you are on an older version, or still seeing contention, disable parallel execution:
/gsd-settings
Set parallelization.enabled to false.
If a subagent appears to fail but commits were made
Check git log before concluding something broke:
git log --oneline -10
A known Claude Code classification bug can report failure while work succeeded. GSD's orchestrators spot-check actual output, but if you see a mismatch, the commits are the ground truth.
Plan and phase problems
If plans seem wrong or misaligned with your intent
Run /gsd-discuss-phase N before planning. Most plan quality issues come from assumptions that CONTEXT.md would have prevented:
/gsd-discuss-phase 1
To see what assumptions GSD is currently making without starting a full session:
/gsd-discuss-phase 3 --assumptions
If you need to change something after execution
Do not re-run /gsd-execute-phase. Use /gsd-quick for targeted fixes:
/gsd-quick "Fix the login button not responding on mobile Safari"
Or use /gsd-verify-work N to systematically identify and fix issues through UAT.
If a command appears frozen at "Spawning…"
Wait. GSD subagents run in a separate context window. Their work is invisible to the parent session while in progress. The liveness note on the spawn line confirms this is expected. Research and planning agents routinely take 1–5 minutes; verification agents can take longer on large phases.
Do not interrupt the session. Killing it discards in-progress subagent work.
If it has been more than 10 minutes, check whether the agent task still shows as active in the Claude Code sidebar.
Workflow state problems
If the workflow seems corrupted or state is inconsistent
/gsd-forensics
Or with a description:
/gsd-forensics "Phase 3 execution stalled after wave 1"
/gsd-forensics runs a post-mortem investigation: git history anomalies, artefact integrity, STATE.md consistency, uncommitted work, and orphaned worktrees. It writes a report to .planning/forensics/ and surfaces recommended remediation steps. It is read-only and never modifies your project files.
If you need to roll back a phase or plan
/gsd-undo --phase 03 # Roll back all commits for phase 3
/gsd-undo --plan 03-02 # Roll back commits for plan 02 of phase 3
/gsd-undo --last 5 # Pick interactively from the 5 most recent GSD commits
/gsd-undo checks dependent phases before reverting and always shows a confirmation gate.
Install and update problems
If GSD is not recognised after install
Restart your runtime. GSD installs slash commands into your runtime's command directory (for example ~/.claude/commands/gsd/). Most runtimes discover new commands only at startup.
If the problem persists, verify the install:
npx @opengsd/gsd-core@latest --claude --local
For runtime-specific install paths and troubleshooting, see Install on your runtime.
If Codex agents fail to spawn with a 400 about an unsupported model
Symptom — a typed agent (gsd-planner, gsd-executor, …) fails to start and the whole
plan/execute flow falls back to a generic agent:
400 invalid_request_error: "The 'sonnet' model is not supported when using Codex with a ChatGPT account."
This means an installed ~/.codex/agents/<agent>.toml still pins a model your Codex session cannot
serve. Installs made before the passive model posture landed embedded a per-tier model; a
ChatGPT-account session exposes only its own model, so the pin fails the request outright
(ADR-2313).
Check which agents are affected:
node gsd-tools.cjs validate agents
The codex_posture section reports one violation per offending agent, naming the file and the
offending value. Two things it flags:
anthropic_flavored_model— the.tomlpins a GSD tier alias (opus,sonnet,haiku,fable) or aclaude-*id. Codex rejects all of these.orphaned_reasoning_effort— amodel_reasoning_effortwith nomodel, which leaves the model following your Codex session while the effort follows GSD.
An empty violations list means every regular .toml in the directory is posture-clean, so the 400
is coming from somewhere else — check that your Codex session itself is healthy.
One thing the check deliberately does not inspect: an agent file that is a symlink is skipped rather than followed, matching how the effort sync treats them. If you symlink your agent configs, verify those targets by hand.
Fix — repair in place, without a reinstall. Preview what would change:
node gsd-tools.cjs effort sync
That is a dry run; it writes nothing. When the reported changes look right, apply them:
node gsd-tools.cjs effort sync --apply
It removes only the offending model / model_reasoning_effort lines. Everything else — your line
endings, comments, key order, and any keys you added by hand — is preserved byte-for-byte, so the
result is a two-line diff rather than a reformatted file. An explicit real-Codex pin is left alone,
and a file it cannot parse is refused and reported rather than partially rewritten.
Or re-run the installer, which rewrites the agent files wholesale. Current versions write no model at all, so agents inherit the session model:
npx @opengsd/gsd-core@latest --codex --global
Prefer the sync if you have hand-edited your .toml files — a reinstall regenerates them.
If you are on an API-key account and genuinely want a pinned model, name a real Codex model id per agent instead — see How to configure model profiles.
The check is read-only. It reports what is wrong and does not edit your files.
If an update overwrote your local changes
Since v1.17, the installer backs up locally modified files to gsd-local-patches/. Reapply your changes:
/gsd-update --reapply
If you cannot update via npm
If npx @opengsd/gsd-core fails due to npm outages or network restrictions, see docs/manual-update.md for a step-by-step manual update procedure that works without npm access.
For routine updates, see Update GSD.
Cost problems
If model costs are too high
Switch to the budget profile:
/gsd-config --profile budget
Disable research and plan-check agents via settings if the domain is familiar:
/gsd-settings
Also audit which MCP servers are enabled. Every enabled MCP server injects its tool schema into every turn. Browser and platform-specific tools can cost 20k+ tokens each. Disable any that the current phase does not need in .claude/settings.json:
{
"disabledMcpjsonServers": ["playwright", "mac-tools"]
}
Recovery quick reference
| Problem | Solution |
|---|---|
| Lost context or new session | /gsd-resume-work or /gsd-progress |
| Don't know what step is next | /gsd-progress --next |
| Phase went wrong | /gsd-undo --phase NN, then re-plan |
| Something broke | /gsd-debug "description" (add --diagnose for analysis without fixes) |
| STATE.md out of sync | state validate then state sync |
.planning/ integrity uncertain |
/gsd-health, then /gsd-health --repair |
| Workflow state seems corrupted | /gsd-forensics |
| Quick targeted fix | /gsd-quick |
| Plan doesn't match your vision | /gsd-discuss-phase N then re-plan |
| Costs running high | /gsd-config --profile budget and /gsd-settings to toggle agents off |
| Update broke local changes | /gsd-update --reapply |
| Want session summary | /gsd-pause-work --report |
| Parallel execution build errors | Update GSD or set parallelization.enabled: false |
| Worktree base mismatch / exit 42 | Auto-degraded to sequential (no action needed); run worktree set-baseref to restore parallelism |