Skip to content

fix(gc): keep a session whose program group still runs, and model-check the lifecycle in CI - #302

Queued
ThomasK33 wants to merge 20 commits into
mainfrom
fix/gc-leftover-model-ci
Queued

ThomasK33 wants to merge 20 commits into
mainfrom
fix/gc-leftover-model-ci

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Why

gc deleted the folder of an exited session while a process that the program left still ran. The folder holds program.json, the only record that proves that process is the session's, so after gc no destroy could find or stop it. The lifecycle model (scripts/check.sh of the verify-session-lifecycle skill) also never ran in CI, so a change to the covered code could merge with a model that no longer describes it.

Follow-up of #291 (verifier note N3) and #283. Refs #203.

What changes for users

  • gc keeps a session that is not destroyed while its program.json autogroup record proves that a process of the program's group runs. It reports the session's program group still runs (destroy ends it) in skippedSessions, also with --dry-run. A failed check skips the session too.
  • gc also keeps it while kill(-pgid, 0) finds the group id and no scan of /proc shows a member, live or zombie, such as a fast fork chain. gc sends no SIGSTOP, so without this probe such a chain read as gone.
  • gc keeps a session only while it proves the group, so that destroy can end it. When it cannot check the group, it removes the session as main does: in another pid namespace than the one that host.json names (for example after a container restart that keeps the agent-tty home), and when /proc refuses the boot id or a member's autogroup, or hides other users' processes. Else such a session would stay forever, because no destroy can prove its group gone.
  • A fork chain can give an inconclusive judgement: a member ends between its stat and its autogroup read, or shows up only after the judge's scans. gc then judges again, at most ten times, and removes the session only when the judgement stays inconclusive.
  • destroy then ends the leftover, and the next gc removes the session.
  • A session without an autogroup record (macOS, Windows, old sessions) is removed as before.

No JSON shape changes: skippedSessions[].reason is an existing free-text field.

docs/USAGE.md also adds one case to the CLIs that cannot prove they share the host's pid space: a CLI whose /proc belongs to another pid namespace (/proc/self names another pid than its own). It counts the host as running, so its destroy --force can fail with HOST_TIMEOUT (verifier follow-up of #303).

What changes for maintainers

  • The model gets the guards GcKeepsLiveGroup and GcProbesUnseenGroup, the gc step GcChecksGroup, rule R20 RemovedSessionStopped, and the runs mutant-GcKeepsLiveGroup, mutant-GcProbesUnseenGroup-unseen and witness-WitnessUnsharedGcRemovesLeftover-otherspace. R20 covers a gc in the host's pid space. In another pid space gc reads the group not proven, and the witness shows that such a gc removes an exited session while a leftover runs. With fix(host): record the host at create, read a token without host.json as unshared, freeze again #303 and fix(host): a create that gives up leaves a create-failed marker, and its host stops on it #312 that makes 78 runs and thirty-five guards.
  • facts.mjs reads the new guards from gcSessions, defaultDependencies, provenProgramGroupRuns, judgeGroupForGc and probeFindsGroup.
  • check.sh gains --drift-only. It compares the facts and the covered code with coverage.json and needs no Java.
  • coverage.json now also records the facts: the value of each guard and the two status sets. --drift-only prints fact <name> changed: was true, now false when one differs. Before, the facts were not recorded. The facts come from the source text with its comments, but the hashes leave comments out. So one new comment inside a guarded block turned a guard FALSE, every hash stayed the same, and the drift check passed (details below).
  • check.sh now starts a new TLC run as soon as one ends (wait -n). Before, each batch of CHECK_JOBS runs waited for its slowest run. Bash 3.2 on macOS keeps the batch behavior.
  • check.sh gains CHECK_SHARD=<i>/<n>. It takes every n-th run that expects pass and every n-th of the rest.
  • CI gets the lifecycle-model job (four shares), which the Linux quality-gates job needs. Share 1 calls check.sh --drift-only. The TLC runs start only when coverage.json differs from the base of the pull request, the merge group or the pushed commit. Each change to the model, its scripts, the covered code or a fact needs such a sync.
  • Comments in the model and facts.mjs say that RecordsProgramSync also guards "a token only beside host.json", and that the model leaves out a host whose capture fails (verifier follow-up of fix(host): record the host at create, read a token without host.json as unshared, freeze again #303). The model has no rule that such a token breaks, so I added no guard.

The guard-value drift gap

At b0c2b8d I added one comment line in the repair block of reconcileSession:

       existsSync(destroyMarkerPath(sessionDirectory))
     ) {
+      // The destroy marker outranks the status that this reconcile read.
       await writeManifest(manifestFile, {
check before dd7a3a7 after dd7a3a7
facts.json ReconcileRepairsDestroyed=false ReconcileRepairsDestroyed=false
check.sh --drift-only Covered code matches the sync record., exit 0 STALE … fact ReconcileRepairsDestroyed changed: was true, now false, exit 3

Exit 3 fails the drift step of every lifecycle-model share. A new coverage.json with the FALSE fact differs from the base, so CI then runs TLC, and the current run fails.

Known limit (open review thread)

Under a non-reaping PID 1, a fast fork chain can show only zombies to gc's scans while a live successor escapes both scans. gc then reads the group as stopped and removes the session. b4e1b83 behaved the same, and main removes every such session. Only a freeze (SIGSTOP) would show the successor, and gc sends no signals. The maintainer decides whether a follow-up adds that.

Validation

  • Red first: faa14b11 adds the unit test keeps a terminal session whose proven program group still runs and the integration test gc keeps an exited session while a process that the program left runs. Both fail on main (removedSessionCount: 1).
  • check.sh --update-coverage with CHECK_JOBS=26 at dcbf0c8 (after the merge of main at 6a052fc): 78/78 runs as expected in 3,304 s. current has 29,309,460 distinct states and current-no-token has 29,186,546. The later commits change no model input and no fact value, so a31fe89 and f263f56 write the record from facts.mjs at their heads. CI runs TLC on it again.
  • CI on f263f56 (run 41793617184): only share 1 ran the drift step. The four lifecycle-model shares took 18.8, 10.4, 12.3 and 14.8 min of TLC, and 20 + 19 + 19 + 20 = 78 runs gave the expected results. At dcbf0c8, before the record sync, share 1 failed with STALE as designed. Before the main merges, one job took 63 min 14 s for 73 runs (dispatch run 41737109486).
  • Fact mutations: I removed the skip, stubbed the dependency, and stubbed provenProgramGroupRuns. Each one flips GcKeepsLiveGroup to FALSE.
  • Red first for the restart fix: the integration test gc removes an exited session whose host.json names another pid namespace and three unit cases (unshared, no autogroup, hidepid) fail on b4e1b83. The unit test judges again after a member that ended before its autogroup read fails on dcbf0c8. A unit test covers the gc skip when the group check throws.
  • Fact mutations on the new code: without the unshared check, with unknown read as running, or with an inconclusive end read as running, both gc guards read FALSE. Without the probe, only GcProbesUnseenGroup reads FALSE. GC_JUDGEMENTS = 0 turns both FALSE, and any other value changes its recorded hash.
  • tsc --noEmit, oxlint, oxfmt --check, mise run workflow-lint and the targeted unit tests pass.
  • Fork-chain flake: the gc integration tests now let /bin/sh exit only once program.json exists, and skip when it holds no token. On 4 pinned CPUs with two busy loops, both fork-chain tests passed 8 of 8 runs at a31fe89's code.
  • Live, container restart (host.json pidNamespace edited to pid:[1] on an exited session whose leftover runs):
Step b4e1b83 this branch
gc skipped: the session's program group still runs (destroy ends it) removes the session
destroy --force HOST_TIMEOUT (session already removed)
  • Live, built CLI with an isolated AGENT_TTY_HOME, main vs this branch (sh -c 'trap "" HUP; sleep 300 & exit 0'):
Step main this branch
gc of the exited session removed, leftover runs skipped: the session's program group still runs (destroy ends it)
destroy SESSION_NOT_FOUND, leftover runs ok, leftover gone
second gc removes nothing removes the session

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

gc deleted program.json with the folder of an exited session while a
process that the program left still ran, so nothing could prove or stop
that process later. gc now skips such a session until destroy ends the
group. The lifecycle model gets the GcKeepsLiveGroup guard, rule R20
RemovedSessionStopped and the mutant run that breaks it. check.sh gains
--drift-only and a job pool.
The lifecycle-model job runs check.sh --drift-only on every run, and the
whole check when coverage.json differs from the base. coverage.json syncs
the model with gc's group check.
@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-04T07:42:49.145116Z f263f56 Manual request
🔒 Security Review ✅ Completed 2026-11-04T07:41:46.977499Z f263f56 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 Security Review

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

Reviewed commit: abc2982364

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

Copy link
Copy Markdown
Member Author

@codex 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: 9a3716504b

ℹ️ 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
Codex on #302: gc sends no SIGSTOP, so a fork chain that the program
left read gone and gc removed program.json while it ran. A group id that
kill(-pgid, 0) finds while no scan shows a member, live or zombie, now
keeps the session. The model gets GcProbesUnseenGroup, R20 covers the
Unseen runs, and mutant-GcProbesUnseenGroup-unseen must violate it.
# 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: 8b4deb2d0d

ℹ️ 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 docs/USAGE.md 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: 8b4deb2d0d

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.

…eps a group it cannot check

Codex on #302: an exited session whose group reads unknown stays after
gc, and USAGE said gc could remove it.
One job of 4 vCPUs took 63 min for the 73 TLC runs. check.sh gains
CHECK_SHARD=<i>/<n>, which takes every n-th run that expects pass and
every n-th of the rest, and the lifecycle-model job runs four shares.
coverage.json now records the facts (every guard and status set) that
facts.mjs read at the sync, and check.sh --drift-only compares them. The
facts come from the source text with its comments while the hashes leave
comments out, so one new comment in reconcileSession's repair block turned
ReconcileRepairsDestroyed FALSE and the drift check still passed.
# Conflicts:
#	.agents/skills/verify-session-lifecycle/SKILL.md
#	.agents/skills/verify-session-lifecycle/coverage.json
#	.agents/skills/verify-session-lifecycle/scripts/check.sh
#	docs/USAGE.md
#	test/integration/lifecycle.test.ts
…in merge

check.sh --update-coverage at c9dd5be gave 74/74 runs as expected.
The record now holds the facts. current has 26,798,236 distinct states
and current-no-token 26,648,756.
@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. What shall we delve into next?

Reviewed commit: b4e1b8341f

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

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/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
A group that gc cannot check proved nothing but kept its session
forever. After a container restart that keeps the home, host.json names
a pid namespace that no longer exists, the group reads unshared, and no
destroy can prove it gone. provenProgramGroupRuns now counts only a live
member that carries the token, or the probe of a fork chain that no scan
shows. In another pid space, or behind a refused read of /proc, gc
removes the session as main did. The model reads the group not proven in
another pid space, R20 covers a gc in the host's pid space, and
witness-WitnessUnsharedGcRemovesLeftover-otherspace shows the trade-off.

The gc integration tests now let the program exit only once program.json
is in place, and skip when the host captured no token: /bin/sh could exit
before the capture under load. A unit test covers the gc skip when the
group check throws. SKILL.md gives the measured time of a whole run.
GcRemoves clears rec in the same step, so the witness asks for the
state that R20 leaves out instead: a gc in another pid space removed
the session while a process that the program left runs.
@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: dcbf0c8e91

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

ℹ️ 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 .github/workflows/ci.yml Resolved
CI at dcbf0c8: gc removed a session whose fork chain ran. A member can
end between its stat and its autogroup read (unknown), or show up only
after the judge's scans, and both now read false. judgeGroupForGc reads
them as inconclusive, and provenProgramGroupRuns judges again, at most
ten times. A judgement that stays inconclusive, as behind a refused
read of /proc, still proves nothing, so gc removes that session.
check.sh --update-coverage at dcbf0c8 gave 78/78 runs as expected in
3,304 s. The model and check.sh are unchanged since, and facts.mjs reads
the same values, so this record comes from facts.mjs at this commit.
current has 29,309,460 distinct states and current-no-token 29,186,546.
@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: a31fe8973c

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

ℹ️ 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/programGroup.ts Resolved
Codex on #302: GC_JUDGEMENTS = 0 would skip every judgement, so gc
would keep no session, and no fact or hash changed. facts.mjs now reads
GcKeepsLiveGroup FALSE unless the limit is a positive integer literal,
and coverage.json hashes the declaration. The model, check.sh and every
fact value are unchanged since the 78/78 check at dcbf0c8.
@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: f263f56d6e

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

ℹ️ 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
@ThomasK33
ThomasK33 added this pull request to the merge queue Nov 4, 2026
Any commits made after this event will not be merged.
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