Skip to content

propagate-hooks.yml propagates to zero repos: tokenless gh api misclassifies every repo as hookless #807

Description

@hyperpolymath

Summary

.github/workflows/propagate-hooks.yml cannot propagate anything. Its
repository-discovery step calls gh api with no token in scope, so every
call fails, every repo is classified as having no .githooks, and the job then
crashes on the empty result. The hook-propagation path across the estate is
therefore dormant — and it fails in a way that looks like "nothing to do"
rather than like a break.

Exposed by the merge of #804, which is the first commit in a long time to touch
.githooks/** and so the first to trigger this workflow's path filter. The
defect is not caused by #804.

Evidence

Run 34952192815,
job Identify Repositories to Update, on 4c9225a1:

🔍 Filtering repos with .githooks directory...
  ✗ hyperpolymath/standards skipped (no .githooks)
##[error]Unable to process file command 'output' successfully.
##[error]Invalid format '  ""'

hyperpolymath/standards is the repo that owns .githooks/ — it has 14 of
them. A classifier that answers "no" for the canonical repo is answering a
different question than it appears to.

Root cause — two defects, one visible

  1. No credential. The step runs
    gh api /repos/$ORG/$REPO_NAME/contents/.githooks | jq -e '. | type == "array"'
    with no env: GH_TOKEN and no env: GITHUB_TOKEN. The only token in the
    file is on line 155, an input to a different step. Tokenless gh api
    fails; 2>/dev/null hides the error; jq -e on the error object returns
    false; the repo is silently classified as hookless. Every repo takes
    this branch, so REPOS_WITH_HOOKS is always empty.

  2. Empty bash array through printf. Line 106:

    REPOS_JSON=$(printf '%s\n' "${REPOS_WITH_HOOKS[@]}" | jq -R . | jq -s .)

    With an empty array this still prints one empty line, so REPOS_JSON
    becomes [""], not []. Writing that multi-line value to $GITHUB_OUTPUT
    without a heredoc delimiter produces Invalid format ' ""'.

Defect 2 is the only reason this was ever visible. Had the array been handled
correctly, the job would have gone green while propagating to zero repos
a fake green, and this would still be undiagnosed.

Why it matters now

#804 added .githooks/validate-actions-lock.sh, the SHA-exact
uses ⊆ actions.lock validator. Estate repos will not receive it until this
workflow works. More generally: no .githooks change has propagated for as
long as this has been broken, so estate hook state has been drifting silently.

Acceptance criteria

  • The discovery step has an explicit env: GH_TOKEN: ${{ secrets.… }} with
    a credential that can read contents on target repos, and the token's
    scope is stated in a comment.
  • A tokenless or failing gh api call causes the step to fail, not
    to classify the repo as hookless. Distinguish "API said no" from "API did
    not answer" — drop the blanket 2>/dev/null and check the exit status.
  • hyperpolymath/standards itself is classified as having .githooks.
    Use it as a permanent positive control in the job: if the canonical repo
    is not in the list, abort.
  • An empty result yields [] and the job reports "0 repos" explicitly
    rather than crashing or silently succeeding. Guard with
    ${arr[@]+"${arr[@]}"} or build the JSON with jq -n.
  • Multi-line $GITHUB_OUTPUT writes use the heredoc delimiter form.
  • A run on a real .githooks change propagates
    validate-actions-lock.sh, and the summary names a non-zero repo count.

Related

The same shape as the recurring "a guard asks a different question than its
consumer" trap: here the guard asks "did an unauthenticated API call return a
JSON array?"
while the consumer needs "does this repo have .githooks?".

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions