chore: Extract the failure-tail reporter into a shared action #38
Labels
No labels
priority/P0
priority/P1
priority/P2
priority/P3
size/L
size/M
size/S
size/XL
size/XS
state/done
state/in-progress
state/in-review
state/needs-refinement
state/ready
type/bug
type/chore
type/feature
type/refactor
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
amtronics/platform-actions#38
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Four jobs across three repositories now carry the same hand-copied twenty lines: capture a job's output with
tee, and on failure post the tail as a pull request comment. It exists because Forgejo serves no job logs over its API, so a red check is otherwise a bare word.The copies have already drifted, and one of them is wrong.
expiro'simagejob usesexec > >(tee -a log) 2>&1; the newer ones useset -o pipefailwithcmd 2>&1 | tee -a log. Both preserve the command's exit status, but the process-substitution form'steeoutlives the shell, so the log can be unflushed — or absent — when a later step reads it. Measured on 2026-09-03: the file did not exist immediately after the step returned.expiro#27tracks that copy specifically.A second drift: the reporter's fallback for a missing log was unreachable in its first two copies, because
tail -c 40000 log > log.tail || truecreates the file beforetailfails, so the read returns an empty string rather than throwing. Fixed inexpiro#26; whether every copy has it is exactly the question a shared action removes.This is the shape the repository exists for. An action referenced by every pipeline is the difference between fixing a bug once and finding four copies of it.
Scope
report-job-failurecomposite action taking the log path and the pull request number, posting the tail on failure; the README entry; migrating the consumers.teeits own output, since an action cannot wrap steps it does not own. This action covers the reporting only.security-scan's internal capture (platform-actions#29), which already exposeslog-pathand would become a consumer.Acceptance criteria
pipefailwith a pipeline and not theexec > >(tee)form, and why.Notes
The third criterion is the one to be careful about. The reporter runs
if: failure(), and acurlthat fails there must not turn a clear red into a confusing one — the build failure is the news.One sentence in the context is wrong: "It exists because Forgejo serves no job logs over its API." It does serve them —
— and the reason I concluded otherwise is that
/actions/tasksreturns task ids while that endpoint wants a job id, which comes from/actions/runs/{run_id}/jobs. I had been passing the wrong id and reading the 404 as "no such API".This does not weaken the case for extracting the action. That case is the drift, and the drift is unaffected: four hand-copied copies, one of them using the
exec > >(tee …)form whose log can be absent when a later step reads it, and a fallback that was unreachable in the first two copies. Consolidating one correct implementation is worth doing whether or not the log is reachable by other means — arguably more so, since the copies are now the only thing that can be quietly wrong.What does change is the framing of the benefit. The reporter is a convenience — the failure lands on the pull request, next to the reviewer — rather than the only way to see a red job. Worth saying plainly in the extracted action's README so nobody re-derives my mistake from the code.
I have corrected the same sentence on
ahmad/imamah#147, which is the other issue that leans on it.Taking this. Three files in this repository: a new
report-job-failure/action.yml, this repo's ownpr.ymlmigrated onto it, and the README entry.Migrating this repository's own reporter is how the fourth criterion gets met without a four-repo pull request. "Migrating the consumers" spans
expiro,portfolioandimamahas well, and a single change touching four repositories could not be reviewed or reverted as a unit — those follow as their own issues. This repo has a consumer of its own, so equivalence can be demonstrated here.That consumer has the bug the shared action must not inherit. Its reporter reads:
The redirection creates the file before
tailcan fail, soexistsSyncis always true, the read returns"", and the fallback is unreachable — an empty code fence, exactly the defect fixed inahmad/expiro#27. So this is not only a consolidation: it fixes a live reporter here.The third criterion is the one to be careful about. A reporter that cannot post must not turn a clear red into a confusing one. The action will not use
set -e, will not fail the step on a non-2xx, and will say on stderr that the comment could not be posted — the job's own failure stays the news.Verification. The action driven against a local HTTP server with only the hostname substituted, over the four log states (absent, empty, populated, stale tail) plus a post that fails; a mutation per guard; and
bash -nover everyrun:body through this repo's own checker, which the new action must also satisfy.One thing I will decide rather than ask:
curlversus node'sfetch.imamahposts withfetchbecause its container is node-based; the others usecurl. A shared action cannot assume either, so it will usecurl— it is what a composite action can rely on across the images here — and I will note in the README that a node-only image needs the caller to install it, rather than leaving the divergence undocumented.