Skip to content

fix(host): a create that gives up leaves a create-failed marker, and its host stops on it - #312

Merged
ThomasK33 merged 15 commits into
mainfrom
fix/264-stop-marker
Nov 4, 2026
Merged

ThomasK33 merged 15 commits into
mainfrom
fix/264-stop-marker

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Why

ThomasK33 asked to work every deferred follow-up of the first autopilot round until none is left. This PR fixes item 1 of #264, the last open item after #294 fixed items 2 to 5. It is option A of the approved proposal: a stop file on every platform.

On Windows, ChildProcess.kill('SIGTERM') ends the host at once, without its shutdown. So a create that gives up there sends SIGTERM only once the manifest names the program, and the next reconcile then kills the program and records failed. Before the host's start-up write, create sent nothing, and a host that started late listened later and served a session whose create had failed. That is the #209 bug in its most likely case.

What changed

  • create (src/cli/commands/create.ts): when the readiness wait fails, the catch first writes an empty marker, <session>/create-failed, with the new writeCreateFailedMarker (src/storage/manifests.ts, which shares the new writeEmptyMarker with writeDestroyMarker, path from createFailedMarkerPath in src/storage/sessionPaths.ts). Only then does it read the manifest for the platform rule and stop the host as before. A failed marker write is logged at debug level and changes neither the error nor the stop.
  • The host (runHost in src/host/hostMain.ts) checks for the marker twice:
    • right before its SIGTERM check, before the spawn. It returns without a program when it finds the marker, as after an early SIGTERM.
    • right after its start-up manifest write lands, before it listens. It then calls initiateShutdown, which stops the program and records destroyed.
  • The order closes the race. The host's start-up write comes before its check, and create's marker write comes before its manifest read. So either the host finds the marker, or create reads the program's pid and sends the SIGTERM.
  • create writes the marker on every platform. On Linux and macOS the SIGTERM also stops the host, so there the marker is a second path to the same shutdown.
  • Lifecycle check (.agents/skills/verify-session-lifecycle):
    • A new guard, HostChecksCreateFailed, which facts.mjs reads: the marker write right before the manifest read in the give-up catch, the check right before the SIGTERM skip, and the check right after the start-up write and before the listen. writeCreateFailedMarker is covered, and the pre-spawn check is a runHost part.
    • A new constant, Win32, for the environment: a SIGTERM ends the host at once, and create sends it only when the manifest names the program.
    • The model: a variable cmark, the marker write in CreateGivesUp, CreateStopsHostOnWindows (with Win32, the manifest read and the host crash), the check in HostOpensLogAndSpawns, and the new step HostChecksMarker.
    • R17 now also holds while the host has yet to check for the marker.
    • New runs: current-win32 (no token, Win32, R1 to R14 and R17 to R19) passes; mutant-HostChecksCreateFailed-win32 breaks R17; mutant-HostChecksCreateFailed passes (redundant off Windows).
  • Docs: USAGE (the Windows paragraph), SKILL.md (guard section, runs, rule R17, Scope), src/storage/README.md, src/host/README.md, src/cli/commands/README.md.

Behavior changes for CLI and JSON users

  • No JSON shape, error code or exit code changes.
  • A create that fails after the session was allocated leaves an empty internal file, create-failed, in the session folder. gc removes it with the folder.
  • On Windows, a host that started late after a failed create now ends the session itself, and records destroyed, instead of serving it.

Not verified

Real Windows behavior is not verified: the marker's atomic rename on Windows, and TerminateProcess ending the host before the marker check. CI has no Windows job. The Windows path runs on Linux with a preload in the create process only (test/fixtures/fake-win32-create.ts): process.platform reads win32, and ChildProcess.kill sends SIGKILL, as TerminateProcess does.

Validation

  • Red first. At the test-only commit (59b6533e, which I ran as 9c9ab1ef before I rebuilt the branch on main), the new integration case a create that gives up on Windows, where SIGTERM ends the host at once failed: the host served the session, and its program's group stayed alive past the deadline. The Linux cases with and without a token passed. All three pass on the head.

  • Live, with the CLI (node --import tsx src/cli/main.ts), isolated AGENT_TTY_HOME and XDG_STATE_HOME, create faking Windows (fake-win32-create.ts), no session token (denied-autogroup.ts), and the host's start-up write held 10 s (REPLAY_HOLD='_host:rename:/session.json:1:10000'):

    8 s after create exits 6 (HOST_UNREACHABLE) main 374937f9 head
    create-failed marker absent present
    session status running destroyed
    program group alive gone
    host alive, and inspect reaches it gone
  • create unit tests: the marker is written once, with the session's marker path, before the last manifest read, and the host is stopped. A failed marker write keeps the HOST_UNREACHABLE error with details.sessionId and still stops the host. A create that succeeds writes no marker. 23/23.

  • facts.mjs mutation checks: the marker write moved after the manifest read reads HostChecksCreateFailed FALSE; the check after the start-up write removed reads FALSE; the check before the spawn removed makes facts.mjs throw (check.sh exit 2), as for any missing runHost part.

  • Lifecycle check (check.sh --update-coverage) on the head: all 74 runs as expected. current 27,970,364 distinct states, current-no-token 27,080,736, current-win32 14,815,680. mutant-HostChecksCreateFailed-win32 breaks R17 (FailedCreateStopped) in 6 steps. Coverage synced in 880419de.

  • Gates on the head: typecheck, lint and format pass. Unit 1940/1940. lifecycle and cli integration 140/140 (vitest run --maxWorkers=1 --retry=2). CI runs the full suite.

  • Receipts: win32-giveup.sh, fake-win32-create.ts and results.txt in the trail folder of this autopilot round.

  • Merge of main 4357e94f (fix(host): the marked repair also removes the old socket of an older host #305, test: keep the lifecycle tests stable on a slow runner and cut fixed waits #311 and others; merge 66468559, coverage 90adb5e9), no Codex round (round 1 on 880419de was clean in both lanes). Only coverage.json conflicted. check.sh: all 74 runs as expected, same state counts. facts.mjs drift 0. Unit 1944/1944. Typecheck, lint and format pass. lifecycle and cli integration: 138/141 under load 68; the 3 that timed out at 5 s (doctor node-runtime on Node 22.0.0 and 27.0.0, uses --home instead of AGENT_TTY_HOME) pass alone and are outside this change.

  • Round 2, after the verifier's notes on 880419de:

    • writeDestroyMarker and writeCreateFailedMarker share one helper, writeEmptyMarker (f03f5510). test/unit/storage/markers.test.ts pins both writers first (4a65660f). ReconcileRepairsDestroyed read FALSE after that change, so the first check.sh failed. facts.mjs now reads the guard through the helper (75e1e300). A mkdir in the helper, or a writeDestroyMarker that does not call it, reads FALSE.
    • A new integration case, a create that gives up on Windows before its host spawns leaves no program, holds the host's event-log open until a fake-Windows create has given up. That is between the host's manifest read and its spawn. replay-hold.mjs can now hold open for this, because a held readFile does not hold a read that fails before create's write. The case checks that create exits 6 with details.sessionId, that the host exits, that no program.json appears and that inspect reads failed. Without the check before the spawn, it fails: the host spawns the program.
    • A new test/unit/host/hostMarker.test.ts runs runHost in the test process. Case 1 puts the marker in place before the spawn: no program, and the manifest is unchanged. Case 2 writes the marker during the start-up write, through a writeProgramRecord hook: the host never listens, and it records destroyed with the program group gone. Without the check before the spawn, case 1 fails. Without the check after the start-up write, case 2 fails.
    • SKILL.md: ten runs explore more than 20 million distinct states each (fe67124f).
    • check.sh --update-coverage: all 74 runs as expected, same state counts (current 27,970,364). No guard reads FALSE. Coverage synced in e12cabee. facts.mjs drift 0.
    • Gates: typecheck, lint and format pass. Unit 1954/1954. lifecycle and cli integration 142/142 (vitest run --maxWorkers=1 --retry=2). A trial merge with main fb100de4 merges cleanly, with facts.mjs drift 0, unit 1981/1981 and typecheck passing.
  • Merge of main 96e432ea (fix(host): record the host at create, read a token without host.json as unshared, freeze again #303, fix(storage): free a dead breaker's lock at once, keep a stopped waiter alive, skip the sweep on a free lock #299 and others; merge 7a017bc2, sync bdeffb29). fix(host): record the host at create, read a token without host.json as unshared, freeze again #303 changed the lifecycle skill too, so SKILL.md, check.sh and coverage.json conflicted. The guard list now holds both new guards, HostChecksCreateFailed and fix(host): record the host at create, read a token without host.json as unshared, freeze again #303's CreateRecordsHost: 33 guards, none reads FALSE. SKILL.md counts 75 runs. check.sh --update-coverage: all 75 runs as expected, current 27,013,946 distinct states, current-no-token 26,822,518, current-win32 14,684,648. Ten runs still explore more than 20 million states each. facts.mjs drift 0. Unit 1994/1994. Typecheck, lint and format pass. lifecycle and cli integration 143/143. A trial merge with main c7ef603a is clean, with drift 0.

Closes #264


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: xhigh

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-04T04:30:06.413894Z bdeffb2 Manual request
🔒 Security Review ✅ Completed 2026-11-04T04:30:30.821972Z bdeffb2 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: 880419de13

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 880419de13

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

# Conflicts:
#	.agents/skills/verify-session-lifecycle/coverage.json
The integration case holds the host's event-log open, between its
manifest read and its spawn, until a fake-Windows create has given up.
Without the check before the spawn it fails: the host spawns the
program. replay-hold.mjs learns to hold open for it, because a held
readFile does not hold a read that fails before create's write.

The unit cases run runHost in the test process. One finds the marker
before the spawn, the other gets it during the start-up write.
f03f551 moved the write of the destroy marker into writeEmptyMarker,
so ReconcileRepairsDestroyed read FALSE and check.sh failed. The guard
now reads that writeDestroyMarker calls the helper, and that the helper
renames the empty file into place with no mkdir.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: e12cabee88

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: e12cabee88

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# Conflicts:
#	.agents/skills/verify-session-lifecycle/SKILL.md
#	.agents/skills/verify-session-lifecycle/coverage.json
#	.agents/skills/verify-session-lifecycle/scripts/check.sh
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bdeffb2993

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cli/commands/create.ts Resolved
@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: bdeffb2993

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33
ThomasK33 added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit 6a052fc Nov 4, 2026
17 of 18 checks passed
@ThomasK33
ThomasK33 deleted the fix/264-stop-marker branch November 4, 2026 04:55
@github-actions github-actions Bot mentioned this pull request Nov 4, 2026
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.

create give-up: follow-ups after #247 (Windows stop channel, stopped host, timing docs, other loop errors)

1 participant