Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,13 @@ A **Self-merge** blocked by a required review, or a merge it tried that failed w
A CI-fix **Repair** that found nothing on the branch to fix, for example because the failure is not the branch's but was not recognised as an **Inherited failure**, since the **Base branch** commit had no finished result for that check: once the Repair ends and the **Base branch** is merged in again, the head of the **Issue branch** is still the commit whose CI just failed. That head gets its one **Check re-run** for the **Delivery**; if its own checks are red again, or they cannot be re-run, the **Run** is a **Failed run** with the cause `CI red on <short sha> and the Repair found nothing to fix on the branch`, rather than spending its remaining Repairs. Own checks found red on that head in any later round, including after a refused **Self-merge**, fail with the same cause without another CI-fix Repair or Check re-run. Inherited-only failures keep their usual handling, and a different head may receive its own CI-fix Repair and Check re-run.

**Check re-run**:
The one time per full head commit across one **Delivery** that thirdshift asks GitHub to re-run the branch's own failed checks and watches CI on that head again: when a CI-fix **Repair** has left the head unchanged, and before the **Run** fails as a **Declined CI fix**. The allowance is consumed before the request and stays consumed through green CI, **Inherited failures**, a **Base fix**, upstream moves, later rounds and refused **Self-merge** reconciliation, even after visiting another head and returning. Later watches still obtain fresh CI facts; own checks red on a consumed head mean a Declined CI fix without another CI-fix Repair. A different head has its own allowance, and a separately started **Continuation** begins a new Delivery with fresh allowances. It lets a check that failed for a reason that does not repeat, such as a flaky test, pass. If GitHub refuses a request, the outcome comes from fresh checks on that same head: green continues the Run, pending is watched, and its own checks still red mean a Declined CI fix. Checks that cannot be read or disappear after known failures cannot establish success. What the Repair concluded is never read. It needs every one of those checks to be a GitHub Actions job, starts no Repair and counts against no budget, and never re-runs a check for being an Inherited failure.
The one time per full head commit across one **Delivery** that thirdshift asks GitHub to re-run the branch's own failed checks and watches CI on that head again: when a CI-fix **Repair** has left the head unchanged, and before the **Run** fails as a **Declined CI fix**. The allowance is consumed before the request and stays consumed through green CI, **Inherited failures**, a **Base fix**, upstream moves, later rounds and refused **Self-merge** reconciliation, even after visiting another head and returning. Later watches still obtain fresh CI facts; own checks red on a consumed head mean a Declined CI fix without another CI-fix Repair. A different head has its own allowance, and a separately started **Continuation** begins a new Delivery with fresh allowances. It lets a check that failed for a reason that does not repeat, such as a flaky test, pass. If GitHub refuses a request, the outcome comes from fresh checks on that same head: green continues the Run, pending is watched, and its own checks still red mean a Declined CI fix. Checks that cannot be read or disappear after known failures cannot establish success. What the Repair concluded is never read. It needs every one of those checks to be a GitHub Actions job, starts no Repair and counts against no budget, and never re-runs a check for being an Inherited failure. It is separate from the re-run of **Abandoned jobs**, which comes before any Repair.
_Avoid_: retry, re-trigger, CI re-run

**Abandoned job**:
A GitHub Actions job among the branch's own failed checks that GitHub ended as a failure while none of its steps failed and at least one had not completed, as when its runner is lost: its code never ran to completion. It is told from the job's and its steps' fields alone, never annotation or log text. When every one of the branch's own failed checks on a head is one, thirdshift re-runs them with no **Repair**, once per full head commit across one **Delivery**, and watches CI on that head again; red again, or a re-run refused or never seen, the head gets a CI-fix Repair as for any red CI. It comes before, and is separate from, that head's **Check re-run**, which a head may also get after the Repair, and like it counts against no budget. Mixed with ordinary failures of the branch's own, it is handed to the CI-fix Repair with them.
_Avoid_: lost runner, infrastructure failure

**Inherited failure**:
A failed check on the head of the **Issue branch** that also failed, under the same check name, on the **Base branch** commit that head last merged in. It is not the branch's to fix: no **Repair** is started for it, and a **Run** whose only red checks are Inherited failures is a **Failed run** with the cause `CI red on <check>, which also fails on <base> at <short sha>; fix <base> first`. A check with no finished result on that Base branch commit is not one. When asked to, by a flag or the **User config**, the Run starts a **Base fix** before failing. A Run that fails on one with no Base fix taken links each check where it fails on the Base branch, after its cause, and, if neither a flag nor the User config decided against a Base fix, offers one: the command that starts the Run again with one allowed, and the User config setting that allows one for every Run.
_Avoid_: base-red check, flaky check, shared failure
Expand Down
19 changes: 19 additions & 0 deletions docs/adr/0020-an-abandoned-job-is-re-run-before-any-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# An Abandoned job is re-run before any Repair

A GitHub Actions job whose runner GitHub lost ends as `failure` with a step still in progress and none failed. Its code never ran to completion, so a CI-fix **Repair** given it has nothing to fix: one did nothing for 81 s and 420,084 input tokens on Spec PR #842, spending one of the Delivery's 5 Repairs (#908). Now such a job is an **Abandoned job**. When every one of the branch's own failed checks on a head is one, thirdshift asks GitHub to re-run the failed jobs of their workflow runs, with no Repair, and watches CI on that head again, once per head for the complete Delivery. Green, the Run carries on as for any green head; red, it carries on as for any red CI.

What makes a job abandoned is read from the jobs API's job and step fields alone: the job's conclusion is `failure`, no step's is `failure`, and at least one step is not completed. Annotation text such as "The hosted runner lost communication with the server" and job logs are never read, as with ADR 0008 and ADR 0010.

## Considered Options

- **Re-run before any Repair**, the option ADR 0010 rejected. Still rejected: that adds a CI cycle to every real failure to save a Repair on the rare flaky one, and whether a failure is flaky is a judgement. An Abandoned job is not a judgement. GitHub reports that the code never finished, so there is nothing for a Repair to read, and the extra CI cycle is spent only where a Repair could do nothing.
- **Fold it into the Check re-run.** Rejected: the Check re-run follows a Repair that left the head unchanged, so it cannot save that Repair. The two stay separate, each with its own allowance per head, and a head may get both, in that order.
- **Read the annotation or the log for the runner-lost message.** Rejected: it is prose GitHub may reword, where the step states are fields.
- **Re-run abandoned jobs mixed with ordinary failures.** Rejected: the ordinary failures need a CI-fix Repair anyway, and the Repair's new head gets fresh CI.

## Consequences

- Steps are read once per watch that ends red, and only when every one of the branch's own failed checks is a GitHub Actions job concluded `failure`, stopping at the first that is not abandoned, since only all of them being abandoned changes anything. Inherited failures, commit statuses and other apps' check runs are never read or re-run for this. A job whose steps cannot be read is not abandoned: the Run says so and carries on as for any red CI, rather than failing where it would have started a Repair.
- The request and the watch after it are the Check re-run's: one request per workflow run, and the watch waits up to the grace period for the old check run ids to leave the listing before it trusts what it sees. If a request is refused, or no new attempt appears in time, the red CI first watched is handled as before, with a CI-fix Repair.
- A head whose re-run came to an Abandoned job again, or any later watch of that head, gets a CI-fix Repair as before, and after it, if the head is unchanged, its Check re-run. A head whose Check re-run has been consumed gets no Abandoned job re-run.
- The re-run starts no Repair and counts against neither the Repair cap nor the upstream-move budget. The Command log names it: `<check> lost its runner on <short sha>; re-running it`.
2 changes: 1 addition & 1 deletion docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ From a clone of the issue's repository, `thirdshift <Issue URL>`:
3. Creates a git worktree next to your clone, named `<repo>-<Issue branch>`, so your own checkout is never touched.
4. Runs a headless agent session in that worktree on the **Harness**, Claude Code unless you [choose another](#harness-model-and-effort), with the **Factory skills** in [`skills/`](../skills) linked into its `.claude/skills/`, or `.agents/skills/` on the other Harnesses, as `thirdshift-<skill>` and kept out of git by a `.git/info/exclude` entry ([ADR-0012](adr/0012-factory-skills-linked-into-the-worktree-codex-unsandboxed.md)), started with one of the **Session prompts** in [`prompts/`](../prompts). The agent implements the issue, reviews its work against the Base branch, addresses the **Standards findings** and **Spec findings** it agrees with, and opens a ready-for-review pull request that lists every **Unaddressed finding** with a reason and closes the issue. With a [Security review](#security-review) asked for, a second session then reviews the change for vulnerabilities.
5. Takes over deterministically: pushes the Issue branch, skipping your repo's git hooks since the session runs the tests and CI gates the pull request, checks through `gh` that the pull request exists, is open and targets the Base branch, and marks it ready for review (`gh pr ready`) if the agent left it as a draft.
6. Keeps the pull request mergeable and green: merges the Base branch in and watches CI, starting a **Repair** session for a merge conflict or failing checks, at most 5 per Run. A failed check that also failed, under the same name, on the Base branch commit the head last merged in is an **Inherited failure**, not the branch's to fix: the CI-fix Repair is given only the branch's own failures, with the Inherited failures listed as not to fix. If every failed check is an Inherited failure, no Repair starts: the Run merges the Base branch again if it has moved since, and otherwise ends as a [Failed run](#failed-runs) that says to fix the Base branch first, unless it was given `base-fix` or the [User config](#user-config) sets `base.fix`, when it starts a [Base fix](#base-fix) first. A check that is pending, passed or missing on that Base branch commit is the branch's own, and thirdshift never triggers or waits for the Base branch's CI. If a CI-fix Repair leaves the head unchanged, with no commit of its own and no Base branch move to merge, thirdshift does a **Check re-run**, once for that head: it asks GitHub to re-run the branch's own failed checks (`gh run rerun <run-id> --failed`, once per workflow run), says so on stderr as `re-running the failed checks on <short sha>: <check>[, <check>…]`, and watches CI on that head again, from the new attempt on. A check that failed for a reason that does not repeat, such as a flaky test or a runner fault, then passes, and the Run goes on as for any green head. If CI is red again, the Run ends as a Failed run, with no second Repair or re-run for that head. It ends the same way, with nothing re-run, if one of those checks can't be re-run (a commit status, or a check run that is not a GitHub Actions job), since the head could not go green. When GitHub refuses a re-run, stderr shows the refusal and thirdshift reads all checks on that head again: a Repair may already have re-run them. Green continues the Run, pending checks are watched, and the branch's own checks still red fail as before, without another Repair or request for that head. Checks that cannot be read or disappear after known failures cannot establish green CI. Any earlier successful workflow request still waits for its new attempt before the whole head is assessed. What the Repair concluded is never read, Inherited failures are never re-run on their own account, and a Check re-run is no Repair, so it counts against no budget ([ADR-0010](adr/0010-one-check-re-run-before-a-declined-ci-fix.md)). If the Base branch moves while CI runs, it merges it again and goes round, at most 5 times per Run. In a Merge run, each round also takes in **Foreign commits** first: see [Foreign commits in a Merge run](#foreign-commits-in-a-merge-run).
6. Keeps the pull request mergeable and green: merges the Base branch in and watches CI, starting a **Repair** session for a merge conflict or failing checks, at most 5 per Run. A failed check that also failed, under the same name, on the Base branch commit the head last merged in is an **Inherited failure**, not the branch's to fix: the CI-fix Repair is given only the branch's own failures, with the Inherited failures listed as not to fix. If every failed check is an Inherited failure, no Repair starts: the Run merges the Base branch again if it has moved since, and otherwise ends as a [Failed run](#failed-runs) that says to fix the Base branch first, unless it was given `base-fix` or the [User config](#user-config) sets `base.fix`, when it starts a [Base fix](#base-fix) first. A check that is pending, passed or missing on that Base branch commit is the branch's own, and thirdshift never triggers or waits for the Base branch's CI. If a CI-fix Repair leaves the head unchanged, with no commit of its own and no Base branch move to merge, thirdshift does a **Check re-run**, once for that head: it asks GitHub to re-run the branch's own failed checks (`gh run rerun <run-id> --failed`, once per workflow run), says so on stderr as `re-running the failed checks on <short sha>: <check>[, <check>…]`, and watches CI on that head again, from the new attempt on. A check that failed for a reason that does not repeat, such as a flaky test or a runner fault, then passes, and the Run goes on as for any green head. If CI is red again, the Run ends as a Failed run, with no second Repair or re-run for that head. It ends the same way, with nothing re-run, if one of those checks can't be re-run (a commit status, or a check run that is not a GitHub Actions job), since the head could not go green. When GitHub refuses a re-run, stderr shows the refusal and thirdshift reads all checks on that head again: a Repair may already have re-run them. Green continues the Run, pending checks are watched, and the branch's own checks still red fail as before, without another Repair or request for that head. Checks that cannot be read or disappear after known failures cannot establish green CI. Any earlier successful workflow request still waits for its new attempt before the whole head is assessed. What the Repair concluded is never read, Inherited failures are never re-run on their own account, and a Check re-run is no Repair, so it counts against no budget ([ADR-0010](adr/0010-one-check-re-run-before-a-declined-ci-fix.md)). Before any Repair, a head whose own failed checks are all **Abandoned jobs**, GitHub Actions jobs GitHub ended as failed with no failed step and a step not completed, as when a runner is lost, has them re-run once, saying `<check> lost its runner on <short sha>; re-running it`, and CI watched again the same way: green goes on, and red again, or a refused or unseen re-run, gets the CI-fix Repair as before. This too is no Repair and counts against no budget ([ADR-0020](adr/0020-an-abandoned-job-is-re-run-before-any-repair.md)). If the Base branch moves while CI runs, it merges it again and goes round, at most 5 times per Run. In a Merge run, each round also takes in **Foreign commits** first: see [Foreign commits in a Merge run](#foreign-commits-in-a-merge-run).
7. In a **Merge run** (`thirdshift merge <Issue URL>`), does the **Self-merge**: once the pull request is open, ready for review, mergeable and green, thirdshift merges it into the Base branch with a merge commit, on exactly the head commit whose CI it watched (`gh pr merge --merge --match-head-commit <sha>`). Just before each attempt, it reads the PR's `reviewDecision`: `REVIEW_REQUIRED` or `CHANGES_REQUESTED` ends the Run as **review required**, a policy refusal, without attempting the merge or taking another Repair round. An empty or `APPROVED` decision, or a failed read, keeps the existing merge behavior. It never uses GitHub's auto-merge ([ADR-0004](adr/0004-self-merge-by-thirdshift-not-github-auto-merge.md)). A merge that fails goes back round step 6, within the same budgets, and is tried again on the new green head. If that round finds nothing to fix, the refusal is a **policy refusal**, such as merge commits being disallowed or a review being required. After the merge, thirdshift deletes the Issue branch on `origin`, and closes the issue if it is still open, with the comment `Closed by #<pr>, merged into <base> by a thirdshift Merge run.` GitHub closes it on its own only for a merge into the repository's default branch, and then thirdshift leaves it alone. Once the issue is closed, either way, thirdshift takes `in-progress` off it, [removing the Claim](#when-the-claim-ends).
8. Cleans up: removes the worktree, the local Issue branch and the temporary directory the Factory skills were written out to, whether the Run succeeded or not. The one exception is a **Failed run** whose work could not be pushed: see below.

Expand Down
Loading
Loading