Refuse to archive a thread that holds an open PR, and close a landed PR's issue (GST-2188) (#4244)

* Test that a Captain cannot archive a thread still holding open work (GST-2188)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Refuse to archive a thread that holds an open PR and close a landed PR's issue (GST-2188)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Clear the archive check's handoff file on a failed read and say plainly that long PR lists cannot be checked

Review-Finding: p1/specialist-batched#2 reader=testing
Review-Finding: p1/specialist-batched#3 reader=state-machines
Review-Finding: p1/red-team#1 reader=red-team
Review-Finding: p1/red-team#3 reader=red-team
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Refuse to archive on an issue list with no rows, and match tracker codes in any case when closing a landed PR's issues

Review-Finding: p3/outside-voice#1 reader=outside-voice
Review-Finding: p3/outside-voice#2 reader=outside-voice
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Say in the skills guide that a rotated thread is archived only once none of its PRs is open

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Shorten the runaway-stopper archive step so the skill fits its context budget after the rebase

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-09 21:50:58 +00:00
1 parent 72cbb49ddf
commit 83bfdc5177
16 files changed
+656 -16

No files matched your search

+2 -1
View File
@@ -972,7 +972,8 @@ sets the defaults that keep it from coming back.
thread keeps its PR until it merges or closes and checks in with its Captain once past 1B), a subagent past 100M (stop), empty wakes past 25% of the
last 40 turns (warm-wait check-ins are left out), and review rounds past 2 per PR. Captains are reported, never rotated.
- **Stop.** A live subagent past 100M is stopped with its reason. A standing thread is rotated at a clean point through
a handoff file and a successor brief (`runaway-stopper/templates/`), then archived by its own Captain. A thread
a handoff file and a successor brief (`runaway-stopper/templates/`), then archived by its own Captain once none of
its PRs is open (R91), through `gstack-archive-check thread`. A thread
mid-/ship or mid-merge gets `DEFER:` and nothing happens until that work finishes.
- **Prevent.** One wait on all pending operations, quiet wakes, compact program returns, one /ship subagent per
PR head (metering its own row with a 100M default cap, `BUDGET_DEFAULT_CAP:`), two review rounds, and a fresh
+52
View File
@@ -4623,6 +4623,58 @@ body and its receipt would disagree. Nothing was written.
body (or rerun `gstack-issue-links --out` if the issue set changed), then rerun
the receipt write.
## The pre-archive check and issue close on land
Codes printed by `bin/gstack-archive-check` (crew/docs/captain.md#no-orphaned-work). Each
ends an error line in parentheses. In every case nothing was archived and no issue was closed.
<a id="archive-reads-stale"></a>
### `ARCHIVE_READS_STALE`
**Meaning.** The `read_at` in the reads file is more than 15 minutes old, or more than 2
minutes in the future. Old reads can miss a PR or issue the thread picked up since.
**Fix.** Rerun the whole archive program: it reads `tools.thread.get` and
`tools.issue.read` again and stamps `read_at` with the program's `now()`.
<a id="archive-reads-unparsed"></a>
### `ARCHIVE_READS_UNPARSED`
**Meaning.** A part of the reads (named in the line: `reads`, `read_at`, `thread` or
`issues`) is missing, empty, truncated or holds something the check does not know: no
`States:` header in the issue text, no `pull_requests` array, `pull_requests_total`
larger than the list, an issue list with neither a row nor `No issues match.`, an
unrecognized line or a state the header does not name. An empty
read looks like "no work", so the check refuses instead of guessing.
**Fix.** Write the tool results raw, unedited. When the issue list has more pages, read
them all first, then rerun. A truncated PR list has no fix: `tools.thread.get` lists at
most 20 PRs and cannot page through the rest, so a thread that has held more than 20 PRs
cannot be proven clean and stays unarchived.
<a id="archive-thread-mismatch"></a>
### `ARCHIVE_THREAD_MISMATCH`
**Meaning.** The reads are for another thread than `--thread` names.
**Fix.** Read the thread you mean to archive and pass the same code.
<a id="archive-pr-not-merged"></a>
### `ARCHIVE_PR_NOT_MERGED`
**Meaning.** `gstack-archive-check landed` ran on a PR GitHub does not report as merged.
**Fix.** Run it after `gstack-ship-queue land` prints `MERGED`.
<a id="archive-gh-read-failed"></a>
### `ARCHIVE_GH_READ_FAILED`
**Meaning.** `gh pr view <n> --repo <repo>` failed, printed no JSON, or answered for a
different PR than the one asked for. A failed read is never clean.
**Fix.** Check `gh auth status` and the repository, then rerun, at most twice. If it
still fails, leave the thread unarchived (or the issue open) and tell the Captain.
## Eval plans, lane ownership, evidence verification, the owner brief
Codes printed by `bin/gstack-eval-plan`, `bin/gstack-lane-check`,