Skip to content

fix(destroy): destroy --force ends a session whose program group it cannot read - #327

Merged
ThomasK33 merged 20 commits into
mainfrom
fix/destroy-force-unreadable-group
Nov 4, 2026
Merged

ThomasK33 merged 20 commits into
mainfrom
fix/destroy-force-unreadable-group

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Why

The rule: gc never keeps a session that agent-tty gives no way to remove. Two cases broke it after #302.

  1. A read of the program's group fails with an errno that a retry does not change, for example program.json that refuses the read (EACCES), or a /proc scan that fails (EIO). Then every later check fails too. gc kept such a session forever, and destroy and destroy --force failed on the same read. That also covers a session whose host died: gc's reconcile reads the group, so gc reported "failed to reconcile session" forever.
  2. The model had no refused reads, so nothing checked that gc removes a session it cannot check in the host's pid space (unknown). The code has done that since fix(gc): keep a session whose program group still runs, and model-check the lifecycle in CI #302.

Follow-up of #326. This PR now targets main, and it also holds the advisor follow-ups of #326:

  • gc's frozen check freezes and judges twice (FROZEN_JUDGEMENTS). The second freeze covers a SIGCONT from another gc or a destroy. When both frozen judgements read gone, the judgement stays inconclusive, and provenProgramGroupRuns judges again, up to ten times, as on main. So a gc sends at most 10 x 2 SIGSTOP/SIGCONT pairs, and a live chain reads ended only when a SIGCONT from elsewhere comes before all 20 frozen judgements. An earlier version of this PR read the group ended after the first two, for speed. Codex and the maintainer rejected that as a regression from fix(gc): freeze a fork chain behind its zombies before gc reads it ended #326. Under a subreaper that never reaps, with one zombie of the group and about 2,300 processes in /proc, an ended group now takes about 3.5 s at a load of 90, against about 2.5 s on main.
  • New unit tests: a frozen group whose live member is in another autogroup or session reads ended and gets SIGCONT, and a /proc/self of another pid gets no SIGSTOP.
  • USAGE limit 5: gc keeps the session only when a frozen check proves the group. It also states the cost of an ended group and the remaining race: a SIGCONT from another gc or destroy before all 20 frozen checks.

What changes for users

  • destroy --force of a session whose host is gone records it as destroyed also when the read of its program group, or the first check of the group, fails twice, 100 ms apart, with EACCES, EPERM, EIO, EISDIR, ENOTDIR, ELOOP or ENAMETOOLONG. A second read that passes stops the group as usual. The session can be terminal, or running with a host that is proven dead. In that case destroy --force stops the host alone, waits until the host has ended, and records destroyed. A process that the program left can outlive it. USAGE says so.
  • The forced path never applies to a read that fails after that first check, to other errnos (EMFILE, ENFILE, ENOMEM, EAGAIN, EINTR and the rest), to HOST_TIMEOUT, to a group that reads alive, or to a host that does not end. A plain destroy still fails on the read.
  • A host.json that cannot be read counts as missing, so the group reads out of view: destroy --force fails with HOST_TIMEOUT, and gc removes the session, as in another pid namespace. This was already so before this PR.
  • gc's skip reasons name the way out when the read fails that way: "failed to check the session's program group (destroy --force removes it): …" and "failed to reconcile session (destroy --force removes it): …". After a failed reconcile, gc names destroy --force only when the read and first check that destroy --force runs (forceSkipsGroup) fail too, so an EACCES from the socket unlink gets no hint.

No JSON shape changes: skippedSessions[].reason is free text.

What changes for maintainers

  • isUnreadableGroupError (programGroup.ts) accepts an errno in UNREADABLE_ERRNOS (EACCES, EPERM, EIO, EISDIR, ENOTDIR, ELOOP, ENAMETOOLONG): on a non-CliError, or as the cause of a STORAGE_READ_ERROR of program.json. It accepts neither HOST_TIMEOUT, an abort, an assertion, a STORAGE_READ_ERROR without a cause, nor EMFILE, ENFILE, ENOMEM, EAGAIN or EINTR.
  • forceSkipsGroup (programGroup.ts) runs readProgramGroup and one isProgramGroupAlive, and returns whether that throws an error that isUnreadableGroupError accepts. gc uses it for the reconcile hint.
  • destroySession runs the group read of a terminal session, and both rounds of --force, through its local stopUnlessUnreadable. That function reads the group and judges it once inside a try, then stops it and waits after the try. With --force, an unreadable read or first judgement (readAndJudge) runs again after DESTROY_POLL_INTERVAL_MS (100 ms, abortable), and only a second unreadable failure makes it stop the host alone instead. An errno after that first judgement (the witness re-read or a scan in the wait) fails the destroy, so a group that read alive never gets recorded destroyed without its SIGKILL. The model has no read that succeeds and then fails, because such a read only fails a destroy. recordDestroyed keeps its invariant: the host is dead and the group is null.
  • Model:
    • The guard ForceSkipsUnreadableGroup, and the environment constants CheckFails (every read of program.json by destroy, list and gc fails while the record exists) and Refused (their /proc refuses the program's autogroup).
    • The ghost dblind marks the destroy marker or the destroyed record that a blind destroy --force wrote. R11, R16 and R20 leave it out, and R20 also leaves out Refused.
    • New runs: current-checkfails and current-refused (pass), witness-WitnessUncheckedForceDestroys-checkfails and witness-WitnessRefusedGcRemovesLeftover-refused (violated), and mutant-ForceSkipsUnreadableGroup-checkfails (pass: without the guard the witness is never reached). That makes 84 runs and thirty-seven guards.
  • facts.mjs reads the new guard from stopUnlessUnreadable, statement by statement, so a comment does not change a fact. It also reads the restructured stopLeftoversThenRecordDestroyed and --force steps, and it hashes isUnreadableGroupError. The guard-value drift check from fix(gc): keep a session whose program group still runs, and model-check the lifecycle in CI #302 flagged this refactor at once: DestroyRecordsDestroyed, DestroyStopsLeftovers and UnsharedGroupReadsUnknown read FALSE before the facts matched the new code.

Validation

  • Red first: the integration tests "destroy --force ends an exited session whose program.json it cannot read" and "destroy --force ends a session whose host died and whose program.json it cannot read" (chmod 000 on program.json, skipped under root) fail on the base without the change, and they pass with it. The unit tests for isUnreadableGroupError and for gc's reason also fail without the change.
  • Fact mutations:
    • A catch that always rethrows turns only ForceSkipsUnreadableGroup FALSE.
    • A catch that swallows every error with --force, or a classifier that accepts any string code, also turns DestroyRecordsDestroyed and DestroyStopsLeftovers FALSE.
  • check.sh --update-coverage at 4e9707e: 83 of 84 runs as expected. current-checkfails broke R16: a list reconcile repaired the blind destroy's marker to destroyed before dblind was set. 878e73b sets dblind at the marker write.
    • On the fixed model, current-checkfails passes with 12,444,472 distinct states, its witness is reached, and its mutant passes with 6,452,252.
    • The other runs never assign the blind kind, so their results stand. current-refused has 29,409,236 distinct states. current and current-no-token stay at 29,309,460 and 29,186,546.
    • The record comes from facts.mjs at the head, with every fact value unchanged, and CI runs every run again.
  • tsc --noEmit, oxlint, oxfmt --check, the unit tests of programGroup, gc and lifecycle (261 tests), and the gc and unreadable-group integration tests (7 tests) pass after the merge of fix(gc): freeze a fork chain behind its zombies before gc reads it ended #326's head ace0873. After the follow-ups, the non-reaping, programGroup and gc suites pass (176 tests).
  • facts.mjs checks judgeFrozenGroup statement by statement and needs a positive integer FROZEN_JUDGEMENTS. Setting the limit to 0, or removing the SIGCONT, turns GcFreezesUnseenGroup FALSE. A comment-only edit does not.
  • CI on 286a388 (run 41814075563): 21 checks green. The four lifecycle-model shares took 31.4, 23.1, 20.8 and 11.4 min of TLC, and 22 + 21 + 21 + 20 = 84 runs gave the expected results.
  • Round 3 fixes (cf79cee, from the clean advisor on 286a388): the narrower try, with the unit test "destroy --force fails, and records nothing, when a read fails after the group read owned" (fails on 286a388); UNREADABLE_ERRNOS, with tests that expect false for EMFILE, ENFILE, ENOMEM, EAGAIN, EINTR and a cause-less STORAGE_READ_ERROR; a unit test that reaches the /proc/self guard inside judgeFrozenGroup (fails when the guard is removed); USAGE limit 5, the host README and SKILL. The model and check.sh are unchanged, and all 39 facts.mjs constants equal the last full check.
  • Codex round 3 fixes (c1ef1d5): the reconcile hint through forceSkipsGroup, with the unit test "names destroy --force for a failed reconcile only when the read of the group fails too" (fails on cf79cee), and the host.json claim. A real CLI run with chmod 000 host.json on an exited session gave gc "removed" and destroy --force HOST_TIMEOUT. Only the gcSessions and defaultDependencies fingerprints changed.
  • At c1ef1d5: tsc --noEmit, oxlint, oxfmt --check, check.sh --drift-only, test/unit (2,048 tests), and the integration files gc, non-reaping-init, socket-directory (22 tests) and the lifecycle gc and unreadable tests pass. The whole lifecycle integration file passed 74 of 75 at load 48: "destroy ends a fork chain that no scan of /proc shows" failed once and passed 4 of 4 alone. That test uses a plain destroy of a running session, which this PR does not change.
  • Merge of main with fix: on Windows, stop a late host after a failed create in more cases, and name the session in text mode #324 (897307f, coverage at 53c0fe1): check.sh GUARDS and the model hold both sides (dblind next to fix: on Windows, stop a late host after a failed create in more cases, and name the session in text mode #324's lostRepair, diedBeforeRepair and polled). That makes 90 runs and thirty-nine guards. 77 of the 90 runs finished locally as expected before the host rebooted. CI on 53c0fe1 ran all 90 in 8 shares (13 to 44 min), and every run gave its expected result. current has 64,052,288 distinct states and current-no-token 63,127,914, as on main. test/unit (2,064 tests), tsc, lint, format and check.sh --drift-only pass.
  • Codex: round 1 (9f2b1c7) and round 2 (c666628) are clean in both lanes. Round 3 (cf79cee): security clean, three code findings, all fixed: two in c1ef1d5, and the FROZEN_JUDGEMENTS regression in bb4132c. Round 4 (c1ef1d5): security clean, two code findings, both fixed in 24e275f (a one-off EIO, and the wording of USAGE limit 5). Round 5 (53c0fe1): clean in both lanes.
  • 24e275f: the second read, 100 ms later. The new unit tests "destroy --force kills the group when only the first read of the group fails" (fails on bb4132c) and "destroy --force stops at an abort between the two reads of the group" (fails when the delay ignores the signal). Removing the second read or the delay turns ForceSkipsUnreadableGroup, DestroyRecordsDestroyed and DestroyStopsLeftovers FALSE. test/unit (2,051 tests), the integration files gc, non-reaping-init, socket-directory (22 tests), the lifecycle gc and unreadable tests (7), tsc, lint, format and check.sh --drift-only pass. Only the destroySession and facts.mjs fingerprints changed, and all 39 constants equal the last full check.
  • bb4132c: judgeFrozenGroup returns null after both frozen judgements read gone. The new unit test "keeps the session when a live member escapes both frozen judgements of one pass" and the updated "reads a group that shows only zombies, frozen twice in each of ten judgements, ended" fail with the old return false, and facts.mjs turns GcFreezesUnseenGroup FALSE for it. test/unit (2,049 tests), the non-reaping-init, gc and lifecycle gc integration tests, tsc, lint, format and check.sh --drift-only pass. Only the judgeFrozenGroup and facts.mjs fingerprints changed. Every guard constant still equals the last full check.

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

… it ended

Codex on #302: under a PID 1 that never reaps, a fork chain leaves its
zombies in view while its live member escapes both scans, so gc read
the group ended and removed program.json while the chain ran.
judgeFrozenGroup now sends SIGSTOP, as stopProgramGroup does, judges the
stopped group, and sends SIGCONT in a finally. The integration test runs
the chain under the subreaper that never reaps.
The model gets the guard GcFreezesUnseenGroup and the environment
constant ZombiesInView. With zombies in view the probe alone proves
nothing, and gc sees the chain only through the freeze.
mutant-GcFreezesUnseenGroup-unseen-zombies must violate R20. With every
guard, ZombiesInView changes nothing, so current-unseen covers the pass
run. ProvesGroupOwnership lets judgeFrozenGroup send SIGSTOP and SIGCONT,
and the raw kill stays in stopProgramGroup. USAGE limits 3 and 5 name the
two signals.
check.sh --update-coverage at 1c1bd1c6 gave 79/79 runs as expected in
1,121 s. current has 29,309,460 distinct states and current-no-token
29,186,546.
…annot read

A read of program.json, host.json or /proc that fails with a system
error, such as EACCES or EIO, failed every later check, so gc kept the
session forever and destroy failed on the same read. destroy --force of
a session whose host is gone now stops what it can without the group
and records destroyed. A group that reads alive and a host that does not
end (HOST_TIMEOUT) still fail it. gc's skip reasons name destroy --force
for such a read.
…nder refused reads

CheckFails: while program.json names the program, every read of it by
destroy, list and gc fails. gc and its reconcile skip the session, plain
destroy fails, and with ForceSkipsUnreadableGroup destroy --force records
destroyed after it stopped the host alone. The ghost dblind marks such a
record, which R11, R16 and R20 leave out, and
WitnessUncheckedForceDestroys shows it. Refused: /proc of the CLI refuses
the program's autogroup, so the group reads alive while it runs, gets no
kill, and gc reads it not proven. R20 leaves Refused out, and
WitnessRefusedGcRemovesLeftover shows that removal. facts.mjs reads the
new guard and the stopUnlessUnreadable steps of destroySession.
…sed-read runs

USAGE says when gc names destroy --force and that a process the program
left can outlive it. SKILL.md gets the ForceSkipsUnreadableGroup
section, the five new runs, R11, R16 and R20 with their exclusions, and
the Refused scope. facts.mjs hashes isUnreadableGroupError.
Codex on #326: SIGSTOP does not nest, so another gc or a destroy can send
SIGCONT before this gc judges the frozen group, and a fork chain then
read gone and lost program.json. judgeFrozenGroup now reads a frozen
group that still reads gone as inconclusive, so provenProgramGroupRuns
freezes it again, and only GC_JUDGEMENTS such judgements read it ended.
The model has one gc and does not change, so the record comes from
facts.mjs at this commit.
… a readable group

check.sh --update-coverage at 4e9707e gave 83/84 runs as expected: only
current-checkfails broke R16, through the destroy marker. 878e73b fixes
that, and current-checkfails, its witness and its mutant passed alone on
the fixed model (12,444,472 distinct states for current-checkfails). The
other runs never assign the blind kind, and every fact value is the same,
so this record comes from facts.mjs at this commit. CI runs every run
again.
@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-04T17:13:26.802179Z 53c0fe1 Manual request
🔒 Security Review ✅ Completed 2026-11-04T17:13:38.628290Z 53c0fe1 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. Keep it up!

Reviewed commit: 9f2b1c7a4f

ℹ️ 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: 9f2b1c7a4f

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.

…ded in one pass

Advisor on #326: a zombie-only ended session under a PID 1 that never
reaps took ten passes of six /proc scans and ten SIGSTOP/SIGCONT pairs:
about 2.5 s with 2,264 processes in /proc. judgeFrozenGroup now freezes
and judges at most twice (FROZEN_JUDGEMENTS). The second freeze covers
a SIGCONT from another gc or a destroy. When that judgement reads gone
too, nothing live was in view to freeze, and the group reads ended: about
300 ms there, and 50 ms with 258 processes.

Also from the advisor: unit tests for a frozen group whose live member is
in another autogroup or session (ended, and resumed) and for a /proc/self
of another pid (no SIGSTOP), and USAGE limit 5 says that gc keeps the
session only when the frozen check proves the group.
@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. Breezy!

Reviewed commit: 9f2b1c7a4f

ℹ️ 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: 9f2b1c7a4f

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.

…eadable-group

# Conflicts:
#	.agents/skills/verify-session-lifecycle/SKILL.md
#	.agents/skills/verify-session-lifecycle/coverage.json
#	.agents/skills/verify-session-lifecycle/scripts/check.sh
#	.agents/skills/verify-session-lifecycle/scripts/facts.mjs
#	docs/USAGE.md
#	src/host/programGroup.ts
#	test/unit/host/programGroup.test.ts
@ThomasK33
ThomasK33 changed the base branch from fix/gc-freeze-unseen-group to main November 4, 2026 10:24
destroy --force fell back to stopping the host alone for any system error
inside stopSessionProcesses, so a group that had already read alive and then
hit an errno on the witness re-read or in the wait got no SIGKILL and was
recorded destroyed. The fallback now covers only the read of the group and
its first judgement. A failure after that verdict fails the destroy.

isUnreadableGroupError now accepts a listed set of errnos that a retry does
not change (EACCES, EPERM, EIO, EISDIR, ENOTDIR, ELOOP, ENAMETOOLONG), also as
the cause of a STORAGE_READ_ERROR. EMFILE, ENFILE, ENOMEM, EAGAIN and EINTR
no longer skip the group.

Also: a unit test that reaches the /proc/self guard inside judgeFrozenGroup,
a USAGE limit for a SIGCONT from another gc or destroy before both frozen
judgements, the forced fallback in src/host/README.md, and the SKILL and
facts.mjs updates. The model is unchanged and every guard constant matches
the last full check.
@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: cf79ceeabb

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 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: cf79ceeabb

ℹ️ 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/programGroup.ts Resolved
Comment thread src/host/lifecycle.ts Resolved
Comment thread src/cli/commands/gc.ts Resolved
…oup read fails

Codex round 3 on #327: a reconcile also writes the manifest and removes the
socket, so an EACCES from the socket unlink got the hint "destroy --force
removes it" although destroy --force fails the same way. gc now asks
forceSkipsGroup, the read and first judgement that destroy --force runs, and
names destroy --force only when that read fails with an error that
isUnreadableGroupError accepts.

Also from Codex: isUnreadableGroupError no longer claims host.json reads.
readHostRecord reads a failed read as missing, so the group reads unshared,
destroy --force fails with HOST_TIMEOUT and gc removes the session. USAGE,
the host README and SKILL say so. Only the gcSessions and
defaultDependencies fingerprints changed, and every guard constant still
matches the last full check.
@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: c1ef1d504d

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.

Codex round 3 on #327 (P1) and the maintainer: reading a group ended after
two frozen judgements that read gone was a regression from #326, which
retried up to GC_JUDGEMENTS times. A SIGCONT from another gc or a destroy
before both judgements let gc remove the folder of a live fork chain.

judgeFrozenGroup now returns null (inconclusive) after both frozen
judgements read gone, so provenProgramGroupRuns judges the group again. It
keeps the two freezes per judgement, so a gc sends at most 10 x 2
SIGSTOP/SIGCONT pairs. An ended zombie-only group with about 2,300
processes in /proc now takes about 3.5 s at a load of 90, against about
2.5 s for #326's ten single freezes.

New unit test: a live member that escapes both frozen judgements of one pass
keeps the session (fails with the old return). facts.mjs expects the null
return, and a false return turns GcFreezesUnseenGroup FALSE. USAGE limit 5
and SKILL state the cost and the remaining race (all 20 frozen judgements).

@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: c1ef1d504d

ℹ️ 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/programGroup.ts Resolved
Comment thread docs/USAGE.md Resolved
…stroy --force skips it

Codex round 4 on #327 (P2): EIO, as on a network file system or from a
one-off /proc read, need not repeat. destroy --force then skipped the group
of a terminal session, recorded it destroyed, and the next gc deleted
program.json while a leftover could still run.

stopUnlessUnreadable now reads and judges the group again after
DESTROY_POLL_INTERVAL_MS (100 ms, abortable) when the first read fails with
an error that isUnreadableGroupError accepts. It skips the group only when
that read fails the same way. A second read that passes stops the group as
usual. The model is unchanged: CheckFails fails every read.

New unit tests: EIO on the first read and a good second read gets the group
its SIGKILL (fails on the previous head), and an abort between the two reads
stops the destroy after one read (fails when the delay ignores the signal).
facts.mjs reads the new statements, and a catch that skips the second read
or the delay turns ForceSkipsUnreadableGroup FALSE.

Also (P3): USAGE limit 5 says "up to twice in each of up to ten checks",
because a frozen unknown judgement sends one pair.
…eadable-group

# Conflicts:
#	.agents/skills/verify-session-lifecycle/SKILL.md
#	.agents/skills/verify-session-lifecycle/coverage.json
#	.agents/skills/verify-session-lifecycle/model/SessionLifecycle.tla
#	.agents/skills/verify-session-lifecycle/scripts/check.sh
check.sh --update-coverage rewrote coverage.json from facts.mjs at the merge
of main (#324) into #327. All 41 constants hold, and the 39 guards read TRUE.
77 of the 90 TLC runs finished locally as expected before the host rebooted.
CI runs all 90.
@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. Nice work!

Reviewed commit: 53c0fe19da

ℹ️ 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: 53c0fe19da

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 11c0f17 Nov 4, 2026
26 checks passed
@ThomasK33
ThomasK33 deleted the fix/destroy-force-unreadable-group branch November 4, 2026 18:39
@github-actions github-actions Bot mentioned this pull request Nov 4, 2026
ThomasK33 added a commit that referenced this pull request Nov 4, 2026
The times of 996c875 were measured with the model of #324. #327 adds
runs, and a share still holds 10 to 12 runs, of which at most 2 expect
pass.
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