A rotted action pin skipped the publish job rather than failing it, so edge stopped publishing and the phone sat on an old build for a working day with nothing saying why. The runner journal logs task pickup and never outcome, which is what was being read. ci-status.sh asks Gitea instead, drilling run -> job -> log tail on a failure. claude-hook-push-ci.sh runs it after every git push so the answer arrives without anyone choosing to look. TASK-42 has the three-layer plan; this is layer 1.
69 lines
2.9 KiB
Markdown
69 lines
2.9 KiB
Markdown
# tools
|
|
|
|
Cross-repo operational tooling. Lives here rather than in a component repo
|
|
because it reaches across all of them.
|
|
|
|
## `ci-status.sh` — what did CI actually do
|
|
|
|
./ci-status.sh # infer the repo from the cwd's git remote
|
|
./ci-status.sh souveraine # name it
|
|
./ci-status.sh souveraine 10 # last 10 runs instead of 3
|
|
./ci-status.sh --all # newest run for every repo we own
|
|
|
|
Exits 0 when the newest run is green or still going, 1 when it failed, so it
|
|
works as a gate as well as a report. On a failure it drills down by itself: the
|
|
run, then which job, then the tail of that job's log with the docker noise
|
|
stripped.
|
|
|
|
Token comes from `$GITEA_TOKEN`, else `~/.git-credentials`, else the Pixel3Arch
|
|
remote URL. No new copy of a secret in the tree.
|
|
|
|
### Read this before trusting anything else for build status
|
|
|
|
**`journalctl -u gitea-runner` is not a status source.** It logs task *pickup*
|
|
and never *outcome*, so a run that died thirty seconds in still shows as
|
|
activity. That habit produced three confident wrong diagnoses in a row on
|
|
2026-07-28 while the correct answer sat in the API untouched.
|
|
|
|
The failure that prompted all this: a pinned action SHA rotted, `rust-test` died
|
|
before compiling, and so `aarch64-artifact` was **skipped** — not failed. The run
|
|
looked unremarkable, `edge` silently stopped publishing, and the phone kept
|
|
installing a build from days earlier for a working day. Full write-up and the
|
|
three-layer fix in `../docs/tasks/42-build-outcomes-are-invisible.md`.
|
|
|
|
## `claude-hook-push-ci.sh` — the same thing, without having to remember
|
|
|
|
A Claude Code `PostToolUse` hook on `Bash`. It ignores everything that is not a
|
|
`git push`, and after one it prints that repo's CI status.
|
|
|
|
Wired in `~/.claude/settings.json`:
|
|
|
|
```json
|
|
"hooks": {
|
|
"PostToolUse": [
|
|
{ "matcher": "Bash",
|
|
"hooks": [ { "type": "command",
|
|
"command": "/home/casey/Projects/SouveraineOS/tools/claude-hook-push-ci.sh" } ] }
|
|
]
|
|
}
|
|
```
|
|
|
|
It always exits 0 — a status reporter that can block a push is a worse problem
|
|
than the one it solves.
|
|
|
|
**What it cannot do.** A cross build takes ~20 minutes, so straight after a push
|
|
the honest answer is usually "running". It reliably catches the fail-fast class
|
|
— pin rot, lint, attribution, a broken workflow edit — which is what actually
|
|
bites. Catching "publishing quietly stopped" needs the staleness comparison in
|
|
TASK-42 layer 2, which belongs to `souveraine-updater`, not to a longer sleep
|
|
in a hook.
|
|
|
|
## Pinning actions, since this is where it cost us
|
|
|
|
- A **tag's** SHA is immutable. `actions/checkout@34e1148` (v4) survived
|
|
untouched.
|
|
- A **branch tip's** SHA is not. `dtolnay/rust-toolchain` force-moves `stable`
|
|
every Rust release, which orphaned the pin — the commit still exists as a
|
|
GitHub object but is reachable from no ref, so act cannot resolve it.
|
|
- If an action must track a branch, pin to a commit on an **append-only**
|
|
branch and say in a comment why that branch was chosen.
|