Skip to content

The tool watcher has never opened a PR and cannot — report instead (#376) - #377

Merged
VijitSingh97 merged 3 commits into
developfrom
fix/376-report-only
Aug 19, 2026
Merged

The tool watcher has never opened a PR and cannot — report instead (#376)#377
VijitSingh97 merged 3 commits into
developfrom
fix/376-report-only

Conversation

@VijitSingh97

Copy link
Copy Markdown
Contributor

Closes #376.

Dispatching #374 is how this was found, and it is the bigger defect

The very first stale pin the run reached:

Switched to a new branch 'bot/actionlint-1.7.12'
[bot/actionlint-1.7.12 e542809] chore(ci): bump actionlint to 1.7.12 (bot)
 1 file changed, 2 insertions(+), 2 deletions(-)
 ! [remote rejected] bot/actionlint-1.7.12 -> bot/actionlint-1.7.12
   (refusing to allow a GitHub App to create or update workflow `.github/workflows/ci.yml`
    without `workflows` permission)

GITHUB_TOKEN may not push changes to a workflow file, and no permissions: key grants it.
GitHub's workflow-syntax reference lists every available key — actions, checks, contents,
pull-requests, … — and there is no workflows. That scope exists only for a PAT or GitHub App
token. Seven of the nine pins live under .github/workflows/**.

It has never opened a single PR:

$ gh pr list --state all --limit 100 --jq '.[] | select(.headRefName|startswith("bot/"))'
(nothing)

Every Monday since July the run went green — on having found nothing to do. From the outside that
is indistinguishable from a watcher that works. This program's signature defect, in the watcher
itself.

What this does

Reports into one tracking issue instead, the same mechanism pithead adopted in
PR #1143. Works for every pin wherever
it lives, needs no new credential, net −22 lines.

xmrig-bump.yml is untouched and keeps its PR flow: XMRig lives in rigforge.sh, so that push is
not blocked, and a build-verified bump is worth having for the one pin that ships to a rig.

The trade, stated plainly

The PR flow was the better design — a shellcheck bump linted by the new shellcheck is real
verification that a table in an issue cannot give you. I am not pretending otherwise. But it has
never once actually happened, and buying it back means storing a long-lived token that can rewrite
this repo's CI, in a repo whose entire supply-chain posture is SHA-pinned actions and
least-privilege tokens. If that trade is ever worth making, it is a deliberate decision with a
credential-rotation story, not a side effect of fixing a watcher.

What was run

The report logic, driven against the real tree (the check() function copied verbatim from the
run block):

| shellcheck   | 0.11.0 | 0.11.0 | current |
| shfmt        | 3.13.1 | 3.13.1 | current |
| actionlint   | 1.7.7  | 1.7.12 | stale   |
| diff-cover   | 10.3.0 | 10.5.1 | stale   |
| gitleaks     | 8.30.1 | 8.30.1 | current |
| zizmor       | 1.25.2 | 1.29.0 | stale   |
| lychee       | 0.24.2 | 0.24.2 | current |
| jq           | 1.7.1  | 1.8.2  | stale   |
| kcov (image) | v42    | v42    | current |
failed=0

All nine pins, four stale, matching the audit in #373 exactly.

Both failure paths, driven over stubs — unreachable must not read as current:

gh() { return 1; }          -> | offline-tool | 1.0.0 | — | upstream lookup FAILED — not checked |   failed=1
gh() { printf 'nightly'; }  -> | badtag       | 1.0.0 | — | upstream tag unparseable — not checked | failed=1

ponytail-review on my own diff then found the table row format written in seven places —
four branches of check() and three in the kcov block — which is seven places for the columns to
drift apart. Collapsed to one row() helper. The probe above was then regenerated by extracting
the edited run block verbatim
and re-driven, rather than trusting that a refactor of the thing
under test left it alone: byte-identical table, both failure paths still counted.

make lint-actions clean — worth noting that actionlint shellchecks inline run: blocks, which
is why the logic stays inline here rather than moving to a script that nothing in this repo's lint
list would cover. yamllint --strict clean.

What is NOT proven until this merges

The workflow cannot run with these changes until they are on develop. I will dispatch it
immediately after merging and confirm the tracking issue appears
— that step is exactly what found
the defect this PR fixes, and skipping it is how the original went unnoticed for two months.

…nstead (#376)

Dispatching it after #374 is how this surfaced. The push is rejected:

  ! [remote rejected] bot/actionlint-1.7.12
    (refusing to allow a GitHub App to create or update workflow
     .github/workflows/ci.yml without `workflows` permission)

GITHUB_TOKEN may not push changes to a workflow file, and no `permissions:`
key grants it — GitHub's reference lists every key and there is no `workflows`
among them; that scope exists only for a PAT or GitHub App. Seven of the nine
pins live in .github/workflows/**.

`gh pr list --state all` confirms it: not one bot/* PR has ever existed. Every
Monday since July the run went green on having found nothing to do, which from
the outside is indistinguishable from a watcher that works.

So it reports now, into one tracking issue, like pithead's. The PR flow was a
better design — the new tool runs its own check on its own PR — but it has
never once actually happened, and buying it back means a long-lived token that
can rewrite this repo's CI. That is a poor trade for a repo whose posture is
SHA-pinned actions and least-privilege tokens.

xmrig-bump.yml keeps its PR flow: XMRig lives in rigforge.sh, so that push is
not blocked, and a build-verified bump is worth having for the one pin that
ships to a rig. Its push path is nonetheless untested for the same reason —
that pin has been current every week too.

Unreachable is not current: a failed lookup or an unparseable tag is counted,
named in the report as "not checked", and fails the run. The last fully
successful check date is carried forward on a failed run, so a watcher that has
stopped looks different from one with nothing to say.

Net -22 lines.
ponytail-review on my own diff: check()'s four branches and kcov's three each
wrote the '| a | b | c | d |' format themselves — seven places for the columns
to drift apart. One row() helper, one append per path. Re-extracted the edited
run block verbatim and re-drove it: byte-identical table, both failure paths
still counted.
…nsion

zizmor rejected my own workflow on this PR — template-injection, an expression
expanded straight into a run block becomes code rather than data. I had used
the env form in the sibling pithead workflow and did not carry it here.

Worth noting which zizmor caught it: 1.25.2, the stale pin this whole thread is
about. It is doing its job at four minors behind; the argument for bumping it is
that we do not know what the other four minors would catch.
@VijitSingh97

Copy link
Copy Markdown
Contributor Author

zizmor rejected my own workflow, and it was right

template-injection on .github/workflows/tool-bump.yml:138${{ steps.report.outputs.rc }} expanded straight into a run: block, where the value becomes code rather than data. I used the env: form in the sibling pithead workflow and did not carry it across. Fixed; zizmor is clean locally now (No findings to report).

Two things worth recording from that:

  1. The auditor caught it at 1.25.2 — the stale pin this whole thread is about. It is doing its job four minors behind. That is not an argument that the staleness is harmless; it is the reason the argument for bumping it has to be "we do not know what the other four minors would catch", which is exactly why an unwatched security tool is a problem.

  2. This is the verification the old design promised and never delivered. The bot-PR flow existed so a bumped tool would run its own check on its own branch. It never once did. What actually caught a real defect here was the ordinary PR gate on a human-authored change.

@VijitSingh97
VijitSingh97 merged commit 53f5cf7 into develop Aug 19, 2026
9 checks passed
@VijitSingh97
VijitSingh97 deleted the fix/376-report-only branch August 19, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tool-bump.yml has never opened a PR and cannot: GITHUB_TOKEN may not push .github/workflows/ changes

1 participant