Repository navigation
skills(ground-claims): read a past run's code at that run's commit - #1319
Conversation
tend-agent
left a comment
There was a problem hiding this comment.
The git show <sha>:<path> half of the recipe is unavailable in the sessions that most need it, and where it fails it fails into the same wrong answer the rule exists to prevent.
fetch-depth: 0 fetches everything reachable from a ref, and nothing else. This repo squash-merges and deletes the branch, so a merged PR's head commit is reachable from no ref and is simply absent from the checkout — of the eight most recently merged PRs here, git cat-file -e <headRefOid> finds none of them. Default-branch runs are fine, which is ci-fix's case; a run on a PR branch is not, which is what review-runs, the notification drain, and a review session explaining an earlier run are usually looking at. review-reviewers never has the object at all — its checkout is a different repository from the runs it reads.
The failure mode is the part that matters. The absent object does not announce itself as absent:
$ git show dfdb610ef17e80e2c9839e7b244cc06e38f950ef:README.md
fatal: path 'README.md' exists on disk, but not in 'dfdb610ef17e80e2c9839e7b244cc06e38f950ef'That message reads as "the file was not there at that commit". Piped — which is the idiom #1315 itself uses as its proof, git show "$s:scripts/verify_site.py" | grep -c 'rolling_out\|Retry-After' — the fatal goes to stderr and stdout carries 0, exit 0. A session reads 0 and concludes the guard did not exist when that run executed. That is the hallucination this rule was written to stop, now produced by the rule's own recipe, and with the same false confidence: a clean exit and a plausible number.
Fetching at the SHA has none of these cases, and covers the local-commit one too:
gh api "repos/{owner}/{repo}/contents/<path>?ref=$SHA" -H 'Accept: application/vnd.github.raw'I'm pushing that as the recipe. post-to-github's cross-reference and the choice of ground-claims over ci-fix as the home both look right to me and I haven't touched them.
How the object absence was checked
$ for n in $(gh pr list --state merged --limit 8 --json number --jq '.[].number'); do
sha=$(gh pr view $n --json headRefOid --jq .headRefOid)
git cat-file -e "$sha" 2>/dev/null && echo "$n local" || echo "$n absent"
done
1310 absent
1309 absent
1308 absent
1307 absent
1306 absent
1305 absent
1303 absent
1301 absentThe same SHA fetches fine over the API, which is why the replacement recipe holds where git show does not:
$ gh api "repos/{owner}/{repo}/contents/README.md?ref=dfdb610ef17e80e2c9839e7b244cc06e38f950ef" \
-H 'Accept: application/vnd.github.raw' | head -1
<h1><img src="assets/logo-512.png" alt="tend logo" width="50" align="absmiddle">Tend</h1>check_body_links.py is unaffected either way — it resolves a SHA through repos/{slug}/commits/{sha}, and a run head resolves there whether or not the branch still exists.
`git show <sha>:<path>` only resolves a commit the checkout holds, and `fetch-depth: 0` holds only what a ref reaches — the head of a squash-merged or deleted PR branch is absent, which is most of the runs a session explains. It fails into the rule's own failure mode: the `fatal:` goes to stderr while a `| grep -c` downstream prints `0`, reading as "the guard was absent at that run". The contents API at `?ref=<sha>` resolves in every case, including a run in another repository.
tend-agent
left a comment
There was a problem hiding this comment.
Two things in the new recipe, both the shape the last commit set out to fix — a path that returns a plausible answer rather than failing.
An empty $SHA reads the default branch. If the run lookup fails and leaves $SHA empty, ref= with no value serves the repository's default branch and exits 0:
$ gh api "repos/max-sixty/tend/contents/README.md?ref=" -H 'Accept: application/vnd.github.raw' | head -1
<h1><img src="assets/logo-512.png" alt="tend logo" width="50" align="absmiddle">Tend</h1>That is the reading the rule exists to prevent, arriving with a clean exit and content that looks right. /tend-ci-runner:review's posting recipe already guards the same shape — "Read the sha first and bail if it isn't there" — and chaining the contents call off the run lookup's exit status covers it here.
| grep -c exits 1, not 0. grep -c returns 1 when it selects no lines, so the pipeline's status is 1 whether the object was missing or the guard was genuinely absent:
$ git show <absent-sha>:README.md 2>/dev/null | grep -c Tend
0
$ echo $?
1The argument survives — 0 on stdout is indistinguishable from a real no-match — but the exit status as stated is wrong, in the section telling the reader not to assert unrun tool behavior. Pushing both fixes.
One judgment call left alone: head_sha is the PR's head commit, not the merge commit a pull_request run checks out (refs/pull/N/merge), so for a PR-triggered run the file at that SHA can differ from the tree that actually executed when base moved after the branch point. Saying so costs a sentence in a skill that is meant to be brief, and for the code a diagnosis usually asks about the two read the same — so it seems a maintainer's call rather than mine.
Regenerates the nine `tend-*` workflows against tend 0.2.13 (from 0.2.11). The committed diff is only the pinned action ref and the `uv` setup version (`0.12.13` → `0.12.17`); everything below rides in the action and the plugin it loads. ## What changes for this repo **A cancelled gating check no longer reads as green.** `poll_pr_checks.py` now treats only `SUCCESS`, `NEUTRAL`, and `SKIPPED` as green and exits 2 — unverified — on anything else ([max-sixty/tend#1323](max-sixty/tend#1323)). Cancelled runs are routine here: main serializes the complete suite, so a newer commit replaces the one waiting for its turn, and `running-tend` already carries a rule for reading that shape. Under 0.2.11 a session polling its own push could report such a check as verified; it now has to say it could not verify. **CI sessions ignore the bot's claude.ai account.** Skills, plugins, and MCP connectors enabled on the `leaf-agent` account no longer load into sessions that push and post as it ([max-sixty/tend#1328](max-sixty/tend#1328)). Sessions run with this repo's skills and tend's own, and nothing else. **`/tmp` is writable inside the sandbox**, as scratch private to the run ([max-sixty/tend#1324](max-sixty/tend#1324)). A tool that hard-codes a `/tmp` path no longer needs an override; `$TMPDIR` stays the documented place to write. **The sandbox launch retries a transient bridge-socket failure**, up to three attempts ([max-sixty/tend#1327](max-sixty/tend#1327)). SRT gives socat 600 ms to bind, and a slow runner previously lost the whole job before the agent started. Three plugin-side fixes also land: `review-runs` can now cancel a run parked in GitHub's `waiting` state that holds its concurrency group without ever concluding ([#1320](max-sixty/tend#1320)); `ground-claims` reads a past run's code at that run's commit rather than at the current default branch ([#1319](max-sixty/tend#1319)); and `run-tend` corrects its list of instruction paths restored to the base version on a PR ([#1311](max-sixty/tend#1311)). ## No configuration change needed 0.2.12 moved the agent into the job's own checkout through a copy-on-write view, which would have changed where dependency setup belongs. 0.2.13 reverts it ([#1339](max-sixty/tend#1339)): `setup:` and `sandbox_setup:` behave as they did at 0.2.11, so `.config/tend.yaml` is unchanged here. This repo pins no `model:`, so the harness default applies as before. Full comparison: [0.2.11...0.2.13](max-sixty/tend@0.2.11...0.2.13). Co-authored-by: leaf-agent <318509791+leaf-agent@users.noreply.github.com>
A session diagnosing a past CI failure reads the repository from its checkout, which is at the current default branch — so an explanation of why a run failed can cite a branch, guard, or helper that did not exist when that run executed. Nothing downstream catches it: the SHA is real and the permalink resolves, so
check_body_links.pypasses and the wrong cause ships as a confident diagnosis.ground-claimsnow carries the rule under Recurring hallucination shapes: resolve the run'shead_sha, read the file at that SHA, and pin the body's links to it. The read goes through the contents API rather thangit show <sha>:<path>, because the checkout holds only what a ref reaches — the head of a squash-merged or deleted PR branch is absent from it, which is most of the runs a session gets asked to explain, and a run in another repository is never there at all.git showalso fails into the rule's own failure mode rather than out of it: thefatal:goes to stderr while a| grep -cdownstream prints0, indistinguishable from a genuine no-match, which reads as "the guard was absent at that run". The contents call is chained off the run lookup's exit status for the same reason: an empty$SHAmakesref=serve the default branch, with a clean exit and plausible content.post-to-github'sgit rev-parse HEADline is the instruction that supplied the wrong SHA, so it names the exception and points at that section rather than restating the rule.Text only.
uv run pytest -k skillpasses, includingtest_skill_reference_citations_resolve, which is what validates the new cross-reference, and the recipe was run end-to-end against a run in this repo before it shipped.Closes #1315