Problem
Each hygiene gate step in lint and lint-2 runs with continue-on-error: true, and scripts/aggregate-hygiene-results.sh fails the job at the end. For a failed gate it emits only:
::error title=<check> failed::Hygiene check finished with outcome: failure
The gate step's own annotation is the generic "Process completed with exit code 1." with no step name. The reason (which file, which rule, what command fixes it) is printed only in the gate step's log, so a reader has to open the full job log and search for it.
Evidence
Run 37094085450, job lint-2 (111120385079), on #5992:
gh api repos/melodic-software/claude-code-plugins/check-runs/111120385079/annotations returns six annotations: three "Process completed with exit code 1." and three "Hygiene check finished with outcome: failure" titled spoke-plugin-root failed, plugin-options-docs failed, shell-portability-lint failed. The reasons are only in the 3,976-line log:
- line 802:
plugins/harness-config/skills/audit/reference/audit-checklist.md: has 2 occurrence(s) of ${CLAUDE_PLUGIN_ROOT}, scripts/spoke-plugin-root-baseline.txt allows ...
- line 1234:
STALE options docs in 1 plugin(s): harness-ops / Run: python scripts/sync-plugin-options-docs.py
- line 3714:
PORTABILITY: plugins/harness-ops/hooks/session-event-log.test.sh:345: \\b -> ...
Each of those lines would have been enough to fix the failure without opening the log.
Impact
Every hygiene failure costs a log download and search (gh run view --log plus grep, or scrolling the web log) before the fix can start. Agents pay this on every red run, and the PR checks page shows nothing actionable.
Proposed fix options
- Capture each gate's output and put its tail in the aggregate annotation. Each gate step tees stdout and stderr to
$RUNNER_TEMP/hygiene/<id>.log; on failure the aggregator adds the last N lines (say 10) to the ::error message and writes all failing tails to $GITHUB_STEP_SUMMARY. One change in the aggregator plus a tee in each step, or a small wrapper the steps call.
- Have each gate script emit its own
::error file=...,line=...:: annotations. Best signal (inline on the diff), but it means editing about 35 scripts and the composite actions in ci-workflows.
- Fold this into the single gate runner proposed in the companion draft ("ci: add one local entry point that runs the lint and lint-2 hygiene gates against the diff"): the runner already holds each gate's output and can emit the annotation itself.
Recommendation: option 1 now, which is small and self-contained, and option 3 if the gate runner lands. Basis: the annotation and log lines above.
Acceptance criteria
- For a failed hygiene gate, the check run's annotations include at least the gate's last non-empty output lines, so the three failures in run 37094085450 would each show their reason line above in the annotation.
- The job summary lists every failing gate with its output tail.
scripts/aggregate-hygiene-results.sh --self-test covers the new behaviour.
Problem
Each hygiene gate step in
lintandlint-2runs withcontinue-on-error: true, andscripts/aggregate-hygiene-results.shfails the job at the end. For a failed gate it emits only:The gate step's own annotation is the generic "Process completed with exit code 1." with no step name. The reason (which file, which rule, what command fixes it) is printed only in the gate step's log, so a reader has to open the full job log and search for it.
Evidence
Run 37094085450, job
lint-2(111120385079), on #5992:gh api repos/melodic-software/claude-code-plugins/check-runs/111120385079/annotationsreturns six annotations: three "Process completed with exit code 1." and three "Hygiene check finished with outcome: failure" titledspoke-plugin-root failed,plugin-options-docs failed,shell-portability-lint failed. The reasons are only in the 3,976-line log:plugins/harness-config/skills/audit/reference/audit-checklist.md: has 2 occurrence(s) of ${CLAUDE_PLUGIN_ROOT}, scripts/spoke-plugin-root-baseline.txt allows ...STALE options docs in 1 plugin(s): harness-ops/Run: python scripts/sync-plugin-options-docs.pyPORTABILITY: plugins/harness-ops/hooks/session-event-log.test.sh:345: \\b -> ...Each of those lines would have been enough to fix the failure without opening the log.
Impact
Every hygiene failure costs a log download and search (
gh run view --logplus grep, or scrolling the web log) before the fix can start. Agents pay this on every red run, and the PR checks page shows nothing actionable.Proposed fix options
$RUNNER_TEMP/hygiene/<id>.log; on failure the aggregator adds the last N lines (say 10) to the::errormessage and writes all failing tails to$GITHUB_STEP_SUMMARY. One change in the aggregator plus ateein each step, or a small wrapper the steps call.::error file=...,line=...::annotations. Best signal (inline on the diff), but it means editing about 35 scripts and the composite actions in ci-workflows.Recommendation: option 1 now, which is small and self-contained, and option 3 if the gate runner lands. Basis: the annotation and log lines above.
Acceptance criteria
scripts/aggregate-hygiene-results.sh --self-testcovers the new behaviour.