Skip to content

fix(standards-sync-stuck-automerge-alert): author the tracking issue as the App, not the ambient token - #223

Merged
kyle-sexton merged 1 commit into
mainfrom
fix/210-app-token-issue-ops
Jul 22, 2026
Merged

fix(standards-sync-stuck-automerge-alert): author the tracking issue as the App, not the ambient token#223
kyle-sexton merged 1 commit into
mainfrom
fix/210-app-token-issue-ops

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

No related issue: design amendment to merged #213 (runner-policy contract conflict)

Related

#210, #213, melodic-software/standards components/runner-policy

Summary

  • The reusable already mints a read-only App token to scan every standards-sync target repo for stuck armed pull requests, but it still opens/updates/closes its own tracking issue with the ambient GITHUB_TOKEN, authored as github-actions[bot].
  • That only works if the CALLING job explicitly grants issues: write (cross-org reusable workflows cannot elevate their own ambient-token scope beyond what the caller's job permissions declare — GitHub's own reusable-workflow calling example shows permissions: set on the job with uses:, and this repo's own runner-policy precedent for link-check.yml/pulumi-version-drift-check.yml already encodes the same requirement for their issue-writing behavior).
  • Granting issues: write triggers runner-policy's invariant that a contract with allowedCallerPermissions must map every allowedSecrets entry as an exact identity passthrough ${{ secrets.<name> }}, where <name> must be a valid, non-hyphenated identifier. This reusable's own workflow_call.secrets inputs are app-client-id / app-private-key — kebab-case, so no mapping can ever satisfy that invariant. A caller adopting this reusable for its issue-writing behavior is permanently blocked by runner-policy, independent of which secret names it picks.
  • Rejected alternative: relax runner-policy's write-caller-permissions invariant to allow non-identity secret mappings. Declined — that invariant is a deliberate defense-in-depth boundary (don't let a write-permission grant hide behind an opaque secret remap), and weakening it is a security-relevant call for the standards owner, not something to do as a side effect of unblocking one caller.

Change

  • Mint a second, narrowly-scoped App token (permission-issues: write, owner/repositories omitted so it defaults to the calling repository only — verified against create-github-app-token's own input docs) and use it for all three issue-management steps (find/adopt, open-or-update, close).
  • Update ISSUE_AUTHOR_LOGIN from github-actions[bot] to melodic-standards-sync[bot] — empirically verified via gh api users/melodic-standards-sync%5Bbot%5D (login: "melodic-standards-sync[bot]", type: "Bot").
  • Drop the job's own issues: write permission — no ambient-token issue write remains, so it isn't needed.
  • This is stronger decoy resistance than before, not just a policy workaround: github-actions[bot] is the shared identity of every ambient-token workflow in a repo, so any other workflow with issues: write could theoretically author a decoy tracking issue. The App's identity is exclusive to this workflow's own token mint.
  • Result: the reusable's workflow_call inputs/secrets contract is unchanged, but a caller no longer needs to grant issues: write at all — its runner-policy contract entry can be secrets-only, with no allowedCallerPermissions waiver.

Test plan

  • actionlint .github/workflows/standards-sync-stuck-automerge-alert.yml — clean
  • zizmor .github/workflows/standards-sync-stuck-automerge-alert.yml — no findings
  • CI (this PR's own checks)
  • Manual verification once melodic-standards-sync App gets its Issues: read+write grant (tracked separately as an operator action) — first live scheduled run in a caller repo confirms the tracking issue is authored by melodic-standards-sync[bot]

🤖 Generated with Claude Code

https://claude.ai/code/session_01KYvF6bWGqemS9aYFfWJRiW

…as the App, not the ambient token

The reusable minted a read-only App token for its cross-repo PR scan but
still opened/updated/closed its own tracking issue as the ambient
GITHUB_TOKEN (github-actions[bot]), which only works if the calling job
grants issues:write. That forced every caller into runner-policy's
write-caller-permissions contract shape, which requires an identity-passthrough
secret mapping (`${{ secrets.<name> }}`, no hyphens) that this reusable's
kebab-case app-client-id/app-private-key inputs can never satisfy.

Mint a second App token scoped to only the calling repository's
issues:write, and author the tracking issue with it instead. This is
strictly tighter decoy resistance (the App's identity isn't shared by every
other ambient-token workflow in the repo) and removes the caller's need to
grant issues:write at all, so its runner-policy contract can be secrets-only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KYvF6bWGqemS9aYFfWJRiW
@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude encountered an error after 0s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude encountered an error after 0s —— View job


I'll analyze this and get back to you.

@github-actions

Copy link
Copy Markdown

Warning

Automated review did not complete — this is an infrastructure failure, not a review verdict.

Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."

Re-running the job, or pushing a new commit, will retry the review.

@github-actions

Copy link
Copy Markdown

Warning

Automated security review did not complete — this is an infrastructure failure, not a review verdict.

Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."

Re-running the job, or pushing a new commit, will retry the review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c18c0ef64d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/standards-sync-stuck-automerge-alert.yml
@kyle-sexton
kyle-sexton enabled auto-merge (squash) July 22, 2026 21:09
@kyle-sexton
kyle-sexton merged commit 43bc8d0 into main Jul 22, 2026
38 of 39 checks passed
@kyle-sexton
kyle-sexton deleted the fix/210-app-token-issue-ops branch July 22, 2026 21:14
kyle-sexton added a commit to melodic-software/standards that referenced this pull request Jul 22, 2026
… SHA

melodic-software/ci-workflows#223 merged (43bc8d0), moving the reusable's
tracking-issue writes onto a self-minted App token. Flip the caller pin and
its policy.json contract key to that SHA now that the blocker is resolved;
no other change needed (the caller was already written for this shape: no
issues:write, no allowedCallerPermissions waiver).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KYvF6bWGqemS9aYFfWJRiW
kyle-sexton added a commit to melodic-software/standards that referenced this pull request Jul 22, 2026
…tomerge-alert (#251)

No related issue: caller rollout for merged
melodic-software/ci-workflows#213 (issue #210 closed)

## Related
ci-workflows#210, ci-workflows#213, ci-workflows#223

## Summary
- standards owns the sync trigger point and already holds the App
secrets `sync.yml` uses, so it hosts the scheduled caller for the
stuck-automerge watchdog reusable added in
melodic-software/ci-workflows#213, rather than medley.
- **This PR is a draft, blocked on melodic-software/ci-workflows#223.**
The pinned SHA (`dd45dacd7b74dd05f3334b78769e57225f7356d8`) is the
reusable as merged in #213, which authors its tracking issue with the
caller's ambient `GITHUB_TOKEN` and therefore needs a caller-granted
`issues: write`. That combination cannot satisfy `runner-policy`'s
write-caller-permissions invariant (identity-passthrough-only secret
mapping, no hyphens) against this reusable's kebab-case
`app-client-id`/`app-private-key` secret inputs — see #223 for the full
diagnosis and the fix (the reusable mints its own App token for issue
writes instead).
- The workflow file and `policy.json` entry here are already written for
the **post-#223 shape**: no caller `issues: write`, no
`allowedCallerPermissions` waiver, secrets-only contract. Only the
pinned SHA (in both the workflow file and the policy.json key) needs to
flip to #223's merge SHA once it lands — no other change.

## Test plan
- [x] `npm run test:runner-policy` — 228/228 pass
- [x] `npm run lint:runner-policy` — Runner policy passed
- [x] `actionlint
.github/workflows/standards-sync-stuck-automerge-alert.yml` — clean
- [x] `zizmor
.github/workflows/standards-sync-stuck-automerge-alert.yml` — no
findings
- [x] `npx biome check components/runner-policy/policy.json` — no fixes
needed
- [ ] Flip pinned SHA to ci-workflows#223's merge SHA once it merges,
mark ready for review

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01KYvF6bWGqemS9aYFfWJRiW

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 24, 2026
…to a cheap page fetch and a per-candidate merge-state probe with retry (#234)

Closes #226

## Summary

- Splits the stuck-automerge scan into two phases so the hourly cron
stops dying on GitHub's opaque server-side GraphQL error: phase 1 pages
open PRs selecting only `number`/`url`/`author`/`autoMergeRequest {
enabledAt }` (no `mergeStateStatus`), phase 2 probes `mergeStateStatus`
one PR at a time via `repository.pullRequest(number:)`, and only for
armed, past-threshold bot PRs.
- Wraps every GraphQL call in `graphqlWithRetry` — exponential backoff
(`GRAPHQL_RETRY_ATTEMPTS=4`, `GRAPHQL_RETRY_BASE_MS=1000`, both
step-env-tunable), rethrowing the last error on exhaustion so a
persistent failure still fails the run loudly. On a throw the
`stuck-count` output is never set, so the close-issue branch cannot fire
a false all-clear.
- Retains the existing manual-cursor pagination and `MAX_PAGES`
soundness guard unchanged; "stuck" semantics are preserved (the
`BLOCKED` check moved from the bulk page filter to the per-candidate
probe).
- No changes to the trigger surface, App-authored issue identity,
`uses:` SHA pins, or permissions blocks.

## Test plan

- `node --test .github/scripts/*.test.cjs` (CI's exact command): 257/257
pass, including 24 in `standards-sync-stuck-automerge-alert.test.cjs` —
new coverage asserts the page query never selects `mergeStateStatus`,
exactly one phase-2 probe per candidate (no bulk fan-out),
transient-error retry on both page fetch and probe, and persistent-error
loud failure with no false all-clear.
- `actionlint` on the changed workflow: clean.
- `biome check` (CI config/scope): clean.
- Post-merge follow-through: the phase-2 query shape is mock-validated
only — confirm ≥2 consecutive green scheduled runs against the real
fleet (the issue's remaining acceptance criterion).

## Related

- Refs melodic-software/standards#251 — deployment PR for the failing
hourly caller
- Refs #224 — comment-events opt-in context named by the issue
- Refs #223 — prior fix on the same workflow (App-authored issue
identity), deliberately untouched here

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant