Skip to content

fix(destroy): name an unreadable host.json in the HOST_TIMEOUT of an unshared group - #331

Merged
ThomasK33 merged 4 commits into
mainfrom
fix/unreadable-host-record-hint
Nov 4, 2026
Merged

ThomasK33 merged 4 commits into
mainfrom
fix/unreadable-host-record-hint

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Why

Follow-up of #327. readHostRecord reads a host.json that it cannot read (for example chmod 000, or a folder in its place) as missing. A session with a token then reads its group as unshared, so destroy --force times out with HOST_TIMEOUT. The message told the user that the CLI runs in another pid namespace or on another machine, which is wrong there.

What changes for users

  • That HOST_TIMEOUT message now names the file and the error, for example "Failed to read the host record at <session>/host.json (EACCES), so this process cannot prove which processes are the session's. Make that file readable and run destroy again." It gives that advice only when the read was refused (EACCES or EPERM). A folder (EISDIR), invalid JSON or an invalid record gets the error alone, because a readable file does not fix it. The error code, exit code and details stay the same.
  • USAGE now says what happens until the file is readable: gc removes such a session once it has ended, but a session whose host died while it ran stays running, because its group reads alive. I checked both with the real CLI (below).

No JSON shape changes: only the free-text error.message of this one case.

What changes for maintainers

  • hostRecord.ts: readHostRecordOrThrow holds the read that readHostRecord did. readHostRecord still returns null on any failure, and the new hostRecordError returns the error when the file exists but its read or its check fails, else null. facts.mjs covers readHostRecordOrThrow.
  • lifecycle.ts: stopSessionProcesses builds the hint of an unshared group with unsharedGroupHint, which asks hostRecordError. It runs only after the wait has timed out.
  • From the fix(destroy): destroy --force ends a session whose program group it cannot read #327 review:
    • SKILL.md: thirteen runs, not eleven, explore more than 20 million distinct states. I measured all 90 runs on the merged model. The 13 runs are current-checkfails (25,554,382), mutant-HostChecksCreateFailed (29,705,764), mutant-HostPollsCreateFailed (45,996,296), mutant-ReusedIdReadsGone (52,902,414), current-win32 (53,200,220), current-no-token (63,127,914), current, current-unseen and mutant-GcFinalCheck (64,052,288 each), current-refused (64,552,948), mutant-ShutdownChecksCommandable (69,117,970), mutant-GcSkipsNonCollectable (69,393,808) and mutant-ReconcileSkipsTerminal (88,731,772).
    • reconcileSession: the comment on the destroy marker said that recordDestroyed writes it only for a terminal manifest or once the host and the group are dead. Since fix(destroy): destroy --force ends a session whose program group it cannot read #327, a destroy --force that cannot read the group writes it once the host is dead, while the group can still read alive. The comment now says so. Its conclusion, that the host has ended or is ending, still holds.
  • The model is unchanged. All guard constants are unchanged. Only the fingerprints of readHostRecord, the new readHostRecordOrThrow, stopSessionProcesses and facts.mjs changed.

Validation

  • Red first: the new cases "times out without a signal to the group when host.json is a folder / when the read of host.json is refused and no probe finds it" in test/unit/host/lifecycle.test.ts put a folder where host.json was (EISDIR, so the case also works as root), or make its read throw EACCES. They fail with the old message and pass now.
  • tsc --noEmit, oxlint, oxfmt --check, check.sh --drift-only, test/unit (2,065 tests), and the lifecycle integration tests for host.json, pid namespaces and unreadable reads (8 tests) pass.
  • Real CLI, isolated AGENT_TTY_HOME:
    • An exited session with chmod 000 host.json: destroy --force exits with HOST_TIMEOUT and "Failed to read the host record at … (EACCES) … Make that file readable and run destroy again.", and gc removes the session. With invalid JSON in host.json, the message says "The host record contains invalid JSON at …" and gives no advice.
    • A running session whose host got SIGKILL, with chmod 000 host.json: inspect shows running, gc skips it ("session host is still alive"), and destroy --force gives the new message.
  • CI runs all 90 model runs.
  • Codex round 1 (5636b14): security clean. Of two code findings, I fixed the advice for a malformed record in a3d2c4a, and answered the race in which host.json changes during the wait on its thread. CI on 5636b14 failed one test, "gc keeps an exited session while a fork chain that the program left runs", in test-integration (4/4). The plain destroy at its end timed out in all 3 attempts. That path never reaches the new hint. The test passed 3 of 3 runs locally and passed on main's CI of 11c0f17. I am watching for it on a3d2c4a.

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

…unshared group

readHostRecord reads a host.json that it cannot read as missing, so a
session with a token reads its group as unshared, and destroy --force timed
out with a hint about another pid namespace or another machine. That hint
was wrong there.

stopSessionProcesses now asks hostRecordUnreadable, which reads host.json
as readHostRecord does but reports a failed read or check. When it does, the
HOST_TIMEOUT message names the file and says to make it readable and run
destroy again. readHostRecord and hostRecordUnreadable share
readHostRecordOrThrow, which facts.mjs now covers.

Also from the #327 follow-ups:
- SKILL.md: thirteen runs, not eleven, explore more than 20 million
  distinct states (measured on the merged model; the largest is
  mutant-ReconcileSkipsTerminal with 88,731,772).
- reconcileSession: the marker comment now says that a destroy --force
  that cannot read the group writes it once the host is dead, while its
  group may still read alive.
- USAGE: until host.json is readable, gc removes an ended session, but a
  session whose host died while it ran stays running.
@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 4, 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-04T19:46:09.563939Z a3d2c4a Manual request
🔒 Security Review ✅ Completed 2026-11-04T19:46:33.385125Z a3d2c4a 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 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: 5636b1419e

ℹ️ 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/host/lifecycle.ts Resolved
Comment thread src/host/lifecycle.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: 5636b1419e

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.

…efused

Codex round 1 on #331: a host.json that is a folder, invalid JSON or an
invalid record stays unusable when it is readable, so the advice to make it
readable was wrong there. hostRecordUnreadable becomes hostRecordError,
which returns the error. The HOST_TIMEOUT message now names that error
("Failed to read the host record at <path> (EISDIR)", or "The host record
contains invalid JSON at <path>") and says to make the file readable only
for EACCES or EPERM.

New unit case: a read of host.json that EACCES refuses gets the advice, and
a folder (EISDIR) does not.
@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 Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: a3d2c4a229

ℹ️ 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: a3d2c4a229

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.

…ecord-hint

# Conflicts:
#	.agents/skills/verify-session-lifecycle/SKILL.md
#	.agents/skills/verify-session-lifecycle/coverage.json
@ThomasK33
ThomasK33 added this pull request to the merge queue Nov 4, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Nov 4, 2026
…ecord-hint

# Conflicts:
#	.agents/skills/verify-session-lifecycle/coverage.json
@ThomasK33
ThomasK33 added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit e0ec4bc Nov 4, 2026
25 checks passed
@ThomasK33
ThomasK33 deleted the fix/unreadable-host-record-hint branch November 4, 2026 23:21
@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.

1 participant