Skip to content

feat(add-to-project): add an org-wide backfill scope - #124

Merged
ExtraToast merged 1 commit into
mainfrom
feat/org-wide-project-backfill
Aug 29, 2026
Merged

feat(add-to-project): add an org-wide backfill scope#124
ExtraToast merged 1 commit into
mainfrom
feat/org-wide-project-backfill

Conversation

@ExtraToast

Copy link
Copy Markdown
Contributor

What

Adds a scope: org input to the reusable add-to-project.yml. When set, it boards every open issue and pull request across the whole organization instead of just the calling repo.

Why

Boarding an issue costs a billable minute in whichever repo fires the event. The private repos fire ~130 of those a week — ~516 minutes/month — for a job that averages 7 seconds and touches no repository content at all. It is pure Projects-V2 GraphQL.

The key fact: Actions minutes are billed to the caller, never to the repo holding the reusable workflow. The existing uses: shims into this repo already centralise the code, and that saves exactly nothing — a private repo firing the event pays for the run.

The only way this work stops costing minutes is for the run itself to be triggered in a public repo, where standard runners are free. That is what scope: org enables, driven by the scheduled caller in JorisJonkers-dev/.github (companion PR).

Behaviour

scope: repo is the default and existing behaviour is untouched — event-driven adds, manual single-URL adds, and the per-repo backfill all work exactly as before.

Two details worth reviewing

collect_repo writes to a file rather than returning URLs on stdout. Calling it inside a command substitution would run it in a subshell — losing the unreachable counter, and capturing the ::warning:: lines into the URL list instead of emitting them. I wrote it the naive way first and caught this before pushing.

Unreadable repos warn but don't fail an org-wide sweep. A repo the App was never granted is a fact about installation scope, not about the board. For a single named repo it stays a hard error, because the caller asked for exactly that one.

The token now also requests metadata: read, needed to enumerate the org's repositories.

Verification

I ran the collection logic against the live org:

::notice::org-wide backfill across 30 repo(s) in JorisJonkers-dev
RESULT: 159 open issue/PR urls collected, 0 unreadable repo(s)

actionlint and shellcheck -S warning both clean.

The add itself is unchanged and already idempotent — addProjectV2ItemById returns the existing item on re-add — which is what makes a 15-minute sweep safe.

Closes #122

Boarding an issue costs a billable minute in whichever repo fires the event.
The private repos fire ~130 of those a week -- ~516 minutes a month -- for a
job that averages 7 seconds and touches no repository content at all.

Actions minutes are billed to the caller, never to the repo holding the
reusable workflow, so the existing 'uses:' shims into this repo save nothing.
The only way this work stops costing minutes is for the run itself to be
triggered in a public repo, where standard runners are free. A new 'scope:
org' input makes that possible: one sweep, called from the public .github
repo, boards every open issue and pull request across the organization.

scope: repo remains the default and the existing behaviour is untouched.

The token now also requests metadata: read, needed to enumerate the
organization's repositories.

collect_repo writes to a file rather than returning urls on stdout. Calling
it inside a command substitution would run it in a subshell, losing the
unreachable counter and capturing the ::warning:: lines into the url list
instead of emitting them.

A repo the App cannot see warns and is skipped rather than failing an
org-wide sweep -- that is a fact about installation scope, not about the
board. For a single named repo it stays a hard error, because the caller
asked for exactly that one.

Verified by running the collection logic against the live org: 30 repos, 159
open issues and pull requests, 0 unreadable.

Refs #122

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AJYmuNrmiYv9wGRABoQYUi
@ExtraToast ExtraToast added type: feature New user-facing or operator-facing capability. area: tooling Reusable workflows, Gradle, templates, Renovate, and API tooling. component: ci Continuous integration workflow or check behavior. priority: P1 High; important and should be handled in the current iteration. labels Aug 27, 2026
@ExtraToast
ExtraToast merged commit 592d6df into main Aug 29, 2026
6 checks passed
@ExtraToast
ExtraToast deleted the feat/org-wide-project-backfill branch August 29, 2026 18:47
ExtraToast added a commit to JorisJonkers-dev/.github that referenced this pull request Aug 29, 2026
#36)

Actions minutes are billed to the caller, never to the repo holding the
reusable workflow. The per-repo 'uses:' shims already point at
github-workflows and that saves nothing -- a private repo firing the event
pays for the run.

Standard runners are free in public repositories, and this repo is public, so
every run of this sweep costs nothing. That is also why the cron can be this
frequent: there is no minute budget to ration, only GitHub's scheduling
latency.

Boarding across the private repos currently costs ~516 billable minutes a
month for a job that averages 7 seconds and reads no repository content --
it is pure Projects-V2 GraphQL and never needed to run where the content is.

Second reason this exists: when GitHub stops creating runs from webhook
events -- which has happened in this estate, stranding 24 issues at once --
the event-driven path silently stops boarding anything. A sweep keeps
working, because it is not waiting on a webhook.

Depends on the scope: org input added in
JorisJonkers-dev/github-workflows#124.

Refs #33


Claude-Session: https://claude.ai/code/session_01AJYmuNrmiYv9wGRABoQYUi

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: tooling Reusable workflows, Gradle, templates, Renovate, and API tooling. component: ci Continuous integration workflow or check behavior. priority: P1 High; important and should be handled in the current iteration. type: feature New user-facing or operator-facing capability.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an org-wide project backfill that runs on a public runner

1 participant