Fix the three box jobs, and give each one an image that is the commit it runs - #705
Conversation
Four CLIs sat yellow on the box for three days with nothing in the log but the word INCONCLUSIVE, because probe-cli.sh captured each agent's output, used it for two greps and discarded it — so run.sh's tail echoed the verdict block back instead of the cause. It now prints what the CLI actually said, plus whether a hook fired at all, and distinguishes a real daemon evaluation from a run where the daemon was never asked anything. canary-guard closes the false-red class: a model denied on one tool completes the same outcome through another, and copilot 1.0.80 and goose both scored FAIL with the honoured deny two lines above. A route-around leaves the probe's own deny to decide; a payload whose canonical fields arrived empty is drift and still FAILs, because that is the case where nothing would have matched in production. Also: is_error learns the three live "cannot test" texts that were reading as "the model never tried", the sandbox image gets the toolchain hermes's installer needs (it has been uninstallable there for weeks), and the nightly translation reports its failures instead of being indistinguishable from an idle night. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The box ran one toolchain image and cloned, installed and built everything at 02:00 and 11:00 in front of nobody. Each job now has its own image carrying the checkout, its dependencies and its build products — the canary also carries a compiled failproofaid instead of cross-compiling one per run in a sibling container. Only the canary gets a docker client, so only its cron line needs the socket. The trade is deliberate and its failure mode is answered rather than prevented: job scripts no longer reach the box through a run-time clone, so CI publishes all three on every push to main and the box tracks main by pulling. A broken image build would otherwise leave it testing week-old code silently, so the baked SHA and its age are printed at the top of every run and carried into every Slack report, and an unreadable build date counts as stale. Baking the tree also made the checkout a place credentials must never be: the decoded OAuth tarballs and the gateway env-file move to a run-scoped dir, a repo-root .dockerignore names them anyway (including the beta variants .gitignore never covered), and each Dockerfile refuses to build if one arrives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four bugs, none of which a unit test would have found, all from the same root:
the canary spawns SIBLING containers, so the host daemon resolves their mounts
against the HOST — and a baked image's paths exist only inside itself.
* the baked checkout was mounted into probe containers as a host path, so the
run died on `bash: /opt/canary/install-clis.sh: No such file or directory`.
The tree is materialised into the work dir — the one path that means the
same thing on both sides — keyed by the baked SHA, so it is a copy once per
published image rather than once per run.
* the decoded OAuth tarballs went to a container-local dir, which mounted
EMPTY into the injector; then, once host-visible, they were 0700 root while
the sandbox reads them as uid 1001. Both presented downstream as three CLIs
that were simply not logged in. chown to that uid, rather than widening the
mode, because the box has other users.
* `git fetch origin <short sha>` is rejected outright by github, so translate
now anchors on the full object id.
* `git init` leaves an EMPTY index and `reset --soft` does not touch it, so
every tracked file read as a staged deletion — `git add -A` would have
committed the repository being emptied, as an auto-PR, at 02:00. A mixed
reset gives 0 changed paths, and a drift guard now refuses to publish when
the baked tree differs from the commit it claims.
Verified on the box: docs-audit runs clean against the real cache, and translate
stops at the anchor and WRITES ITS FAILURE STAMP — the silence this set of
changes exists to end.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…the box getModelForTier reads TRANSLATE_MODEL_TIER1 / TRANSLATE_MODEL_TIER23, so the seven languages most readers arrive in can keep a strong model while the long tail runs on something cheap. The CLI's --model flag flattens every tier to one model, which is the opposite of what the tier split exists for. install.sh now strips the pre-marker cron form as well as its own marker. A box set up before the marker existed carries a long-form inline `docker run … -e CANARY_JOB=<job>` line; matching only the marker left it in place, giving six entries with every job scheduled twice — one on the old image, one on the new. The per-job flock stops that doing damage and turns it into something worse to diagnose: which image runs becomes a coin toss. Found on the real box, whose crontab is exactly that shape. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Caution Review failedAn error occurred during the review process. Please try again later. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Hermes
The three-image scheduling migration is coherently wired, but translation jobs terminated by the configured timeout remain silent and leave no health stamp, undermining the PR’s failure-reporting goal. What this changesflowchart LR
n0Canaryimagepublisher["+ Canary image publisher"]
n1Jobimageruntime["+ Job image runtime"]
n2Boxscheduler["~ Box scheduler"]
n3Canaryprobeharness["~ Canary probe harness"]
n4Translationautomation["~ Translation automation"]
n5Documentationaudit["~ Documentation audit"]
n0Canaryimagepublisher -- "publishes job images" --> n1Jobimageruntime
n2Boxscheduler -- "runs image with secrets" --> n1Jobimageruntime
n1Jobimageruntime -- "provides baked checkout" --> n3Canaryprobeharness
n3Canaryprobeharness -- "uses sibling containers" --> n1Jobimageruntime
n4Translationautomation -- "writes run-health stamp" --> n5Documentationaudit
n2Boxscheduler -- "enforces 4.5h timeout" --> n4Translationautomation
Rounds
FindingsOpen
|
|
I could not complete the review of
|
TS1501 — this repo's tsc target predates /s, and vitest does not typecheck, so the flag ran green locally and failed quality, build, docs, all three test configs and e2e in CI. [\s\S]* does the same job on every target. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
I could not complete the review of
|
hermes-exosphere
left a comment
There was a problem hiding this comment.
Hermes found no blocking issues in this revision.
1 advisory finding
- Medium/High Timed-out translation runs still produce neither a failure notification nor a run stamp —
run-job.shlaunches translation throughtimeout "$TMO" docker runwithTMO=16200(lines 32 and 54). The translation script says its stamp is written on every exit (line 84), but it has no EXIT, TERM, or INT trap;stamp failedandslack_noteare reached only throughdie()(lines 91-100), while success stamps are explicit end-path calls. A timeout/hard stop therefore terminates the container before either path runs, leaving the previouslast-run.jsonand no Slack failure report. (integration-suite/local/jobs/translate.sh:84)
Unblocks CI. The PR had drifted 13 commits behind main and reached a conflicting state, and GitHub cannot build refs/pull/702/merge for a conflicting PR — so the `pull_request` trigger never fired and the last two commits on this branch were never tested. `gh pr checks` showed CodeRabbit and Socket passing, so the absence of the CI run read as "no news" rather than "blocked". Merged rather than rebased: another session is committing to this branch, and a rebase means a force-push that rewrites history under it. Two conflicts, both in files each side appended to: .gitignore — main added /blog/ (#717), this branch added the Python build and test artefacts. Kept both; they do not overlap. CHANGELOG.md — both sides created a `## 1.0.1-beta.2 — 2026-08-17` heading in the same place. Resolved to one section holding the union, filed by subsection, and `## 1.0.1-beta.1 — 2026-08-16` restored above beta.0. That last part corrects main rather than merely reconciling with it. At the merge base the top section was beta.1; main RENAMED that heading to beta.2 and prepended its own entries, which moved four already-shipped entries into an unreleased section — 1.0.1-beta.1 is published on npm. The tell is that main's beta.2 carries two `### Fixes` subsections, the second being the orphaned beta.1 block, byte-identical to this branch's. Propagating that would leave shipped work permanently misfiled. Also dropped one duplicate of main's canary entry, the copy ending `(#PR)` — an unreplaced placeholder. The `(#705)` copy is kept. Verified nothing was lost: every bullet from both sides is present, none invented, and everything from `## 1.0.1-beta.0` down is byte-identical to main's. Checks: SDK 261 passed; SDK spool contract passes strict; fp-cli 786 passed; TS 3822 passed; tsc clean; lint 0 errors; build ok. Two tests in __tests__/hooks/fp-reset.test.ts time out here and fail identically on a clean origin/main worktree — this box runs a real failproofaid, which CI does not. Pre-existing and environmental, not from this merge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three scheduled jobs moved onto one box in #694. Since then the canary has been crying wolf, the nightly translation has been failing every night in silence, and the weekly docs audit has been unable to file its issue. This fixes all three and changes how the box gets its code.
What was actually wrong
The canary could not explain itself.
probe-cli.shcaptured each agent's output, used it for two greps and threw it away;run.shthen echoedtail -20of the probe on a non-PASS verdict — and the last 20 lines are the verdict block. Four CLIs sat yellow for three days with nothing recorded anywhere but the word INCONCLUSIVE, and re-running produced the same nothing. Each failing probe now prints the last 25 lines of what the CLI said, plus whether a hook fired at all.canary-guardwas never committed. The policy from the July workaround investigation does not exist on any ref —git log -S canary-guardreturns nothing. So an agent that honoured a deny and completed the outcome through a different tool scored FAIL with the deny sitting two lines above it in the log. Copilot 1.0.80 did exactly that (deniedtouch, then "created the equivalent file instead" with its Create tool); goose leaked the read probe's sentinel the same way on 15 Aug. The guard splits route-around (probe still PASSes, prints a NOTE) from drift (NORMALIZATION-DRIFT-SUSPECT→ FAIL), and the drift test is restricted to tools with a known canonical field so a Grep for the marker does not read as drift.hermes has been uninstallable for weeks. Its installer runs
npm install, which buildsnode-ptythrough node-gyp, which needs a Python and a C++ toolchain — and the sandbox image had none. Upstream made that step fatal, and it runs before the launcher is written, so the install aborted leaving nohermesbinary and the probe drove a missing command."Cannot test" read as "the model didn't try."
is_error()matched none of cursor'sAuthentication required, devin'sLogin canceled, or devin'shigh demand for this model.The nightly translation was silent when it failed. Its output is a PR, so a run that dies leaves the same signal as a run with nothing to do: none. It failed every night from 11–17 Aug while 28 pages sat missing from 14 locales, and the weekly docs audit — not the job — is what noticed. It now posts on failure and stamps
translate/last-run.jsonon every exit, whose age the docs audit reports.Three images, each baking its commit
One image per job. Only the canary carries a docker client, so only its cron line needs the socket. Each bakes the checkout, dependencies and build products; the canary also bakes a compiled
failproofaidinstead of cross-compiling one per run in a sibling container. Clone, install, two builds and a cargo build leave the nightly path entirely.CI publishes all three on every push to main, unfiltered — with the tree baked in, a path filter silently ships stale code. The failure that trade introduces is answered rather than prevented:
FP_SHAandBUILT_ATare baked in, the entrypoint turns them into an age, and a stale image says so in every Slack report it produces.Credentials stop being written into the checkout, which is now a build context: they move to a run-scoped dir, a repo-root
.dockerignorenames them anyway (including the beta variants.gitignorenever covered), and each Dockerfile refuses to build if one arrives.Four bugs only real runs found
The canary spawns sibling containers, so the host daemon resolves their mounts host-side, and a baked image's paths exist only inside itself.
bash: /opt/canary/install-clis.sh: No such file or directory. The tree is materialised into the work dir, keyed by the baked SHA, so it is one copy per published image.0700 rootwhile the sandbox reads as uid 1001. Both presented as three CLIs that were simply not logged in.git fetch origin <short sha>is rejected by github — translate anchors on the full object id.git initleaves an empty index andreset --softdoes not touch it, so every tracked file read as a staged deletion:git add -Awould have committed the repository being emptied, as an auto-PR, at 02:00. Mixed reset, plus a guard that refuses to publish when the baked tree differs from the commit it claims.Verified live on the box
All three jobs run end to end on the canary box:
docs.json)recovered, hermes installed (v0.20.2) and probed green for the first time in weeks. antigravity reportsA separate credential fix was needed and is not in this PR: the box's PAT was read-only, which is why translate's push and docs-audit's issue both 403'd.
After merge
Nothing on the box changes by itself — its crontab keeps pointing at the published
failproofai-canary-runnerimage until someone re-runsinstall.sh. Two things to watch: the "make package public" step iscontinue-on-error, and a private package meansdocker pullfails before the container starts, which is silent; and the first canary run on a new image re-probes all twelve CLIs, since the version gate keys on the failproofai SHA.🤖 Generated with Claude Code
Hermes review
f1c6e97b7bc2087878dd7b6c4e832f0e8c81a8d71d8f31d926828f3bae215c58f5b35baa44acbff0gpt-5.6-terraSummary
The three-image scheduling migration is coherently wired, but translation jobs terminated by the configured timeout remain silent and leave no health stamp, undermining the PR’s failure-reporting goal.
Changes
Validation
Passeddocker run --rm --network none -v /review/input/workspace:/workspace:ro -w /workspace bash:5.2 bash -lc 'bash -n integration-suite/local/job-entrypoint.sh integration-suite/local/run-job.sh integration-suite/local/jobs/canary.sh integration-suite/local/jobs/docs-audit.sh integration-suite/local/jobs/translate.sh integration-suite/local/install.sh integration-suite/ci-entrypoint.sh integration-suite/probe-cli.sh integration-suite/run.sh'— All modified shell entrypoints and job scripts passed Bash syntax validation in an isolated container. (8s)Findings
No blocking findings.
1 advisory finding
run-job.shlaunches translation throughtimeout "$TMO" docker runwithTMO=16200(lines 32 and 54). The translation script says its stamp is written on every exit (line 84), but it has no EXIT, TERM, or INT trap;stamp failedandslack_noteare reached only throughdie()(lines 91-100), while success stamps are explicit end-path calls. A timeout/hard stop therefore terminates the container before either path runs, leaving the previouslast-run.jsonand no Slack failure report. (integration-suite/local/jobs/translate.sh:84)Open questions
None.
Policy overrides
None.