bonsai-status-sync reusable (phase 1) + refreshed conversion/identity scopes - #21
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/reusable-conversion-scope.md`:
- Around line 190-201: Correct the Total estimate in the phase plan to 28–35h,
matching the sum of the listed phase maxima; leave the individual phase
estimates unchanged.
- Around line 20-22: Update the drift statement in the conversion discussion to
scope “structurally impossible” drift specifically to reusable implementation
logic. Explicitly acknowledge that per-repository caller configuration—such as
SHOPIFY_STORE_NAME, permissions, concurrency, secret mapping, and possibly
DRIVER_AGENTS_REF in shopify-tool-smoke.yml—can still drift.
- Around line 176-180: Update the sequencing plan so the OIDC spike gates only
the Claude conversion, while the bonsai-status-sync wave proceeds independently
even if the spike fails. In docs/reusable-conversion-scope.md lines 176-180,
clarify this independent ordering; in README.md lines 96-100, explicitly state
that the spike gates claude.yml only and bonsai-status-sync.yml may proceed on
failure.
- Around line 58-71: Update the spike steps to record the exact stale SHA-pinned
uses reference for the reusable speak-code.yml/claude.yml workflow and specify
the caller workflow event that triggers it. Tie the recorded SHA to the
deliberately advanced workflows/main state, ensuring the pilot invokes the
pinned reusable workflow rather than the default main pin and validates the
caller-to-reusable OIDC path.
- Around line 170-172: Revise the reusable-workflow guidance around
DRIVER_AGENTS_REF and ANTHROPIC_API_KEY so it does not require every inherited
or explicitly passed secret to be declared under on.workflow_call.secrets. State
that declarations are required only when enforced by this repository’s lint.yml
policy or the chosen calling style, while preserving the existing
secret-handling guidance.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b263fa9b-63a5-4c90-9dc4-8273ecd957e8
📒 Files selected for processing (2)
README.mddocs/reusable-conversion-scope.md
| **Conversion does not eliminate waves** — SHA-pinned stubs still need a pin bump per change. It changes what a | ||
| wave *is*: from re-copying 594 lines into 18 targets while hand-preserving per-repo edits, to changing one SHA | ||
| string. Drift becomes structurally impossible because there is no downstream logic to edit. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Limit the drift claim to reusable implementation logic.
The proposed design still leaves per-repository settings such as SHOPIFY_STORE_NAME, permissions, concurrency, secret mapping, and possibly DRIVER_AGENTS_REF in shopify-tool-smoke.yml. State that implementation drift becomes impossible. Caller configuration drift remains possible.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/reusable-conversion-scope.md` around lines 20 - 22, Update the drift
statement in the conversion discussion to scope “structurally impossible” drift
specifically to reusable implementation logic. Explicitly acknowledge that
per-repository caller configuration—such as SHOPIFY_STORE_NAME, permissions,
concurrency, secret mapping, and possibly DRIVER_AGENTS_REF in
shopify-tool-smoke.yml—can still drift.
| **Spike design (~3–4h):** | ||
| 1. In `DriverDigital/workflows`, add a throwaway `.github/workflows/spike-claude.yml` — `workflow_call`, | ||
| `id-token: write`, the real `claude-code-action` step, nothing else. | ||
| 2. In `vite-plugin-shopify-clean`, add `.github/workflows/spike-claude-caller.yml` with the **full** | ||
| permissions block. **Merge to the default branch** — mandatory, see "no pre-merge test path" below. | ||
| 3. **Push one trivial commit to `workflows/main` first**, so the pinned SHA is provably behind `main` HEAD. | ||
| This is the assertion nobody thinks to make: a pilot run at a pin that happens to equal `main` HEAD passes | ||
| and then breaks the fleet on the next commit to `main`. *(As of writing, `main` @ `9b70acf` is already one | ||
| untagged commit past `v1.6.0` @ `0a3934f` — the condition exists naturally; make it deliberate.)* | ||
| 4. Open one issue as `driver-digital-agents` containing `@claude`. | ||
| 5. **PASS** = log shows the OIDC exchange succeeding **and** a `pull_request` `opened` webhook whose | ||
| `user.login` is literally `claude[bot]`. **FAIL** = a green run with | ||
| `::warning::Skipping action due to workflow validation` and no PR. | ||
| 6. Delete both files. Record the run URL in the release notes either way. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)docs/reusable-conversion-scope\.md$|workflows' || true
echo
echo "== target section =="
sed -n '1,120p' docs/reusable-conversion-scope.md | cat -n
echo
echo "== workflow_sha references in repo =="
rg -n "spike-claude|uses:\s*[^@\s]+@\{?#?\s*[\da-fA-F]+\"?\s*$|@[\da-fA-F]{40}|id-token:\s*write|permissions:\s*\{|workflow_call|pull_request|opened" -S .Repository: DriverDigital/workflows
Length of output: 20002
Record the stale reusable SHA and caller trigger in the pilot steps.
The spike must use a SHA that is behind main, but step 3 only mentions pushing to workflows/main. Add the exact uses: reference for speak-code.yml/claude.yml and the caller workflow's event, so the pilot proves the caller-to-reusable OIDC path without accidentally running the default main pin.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~59-~59: The official name of this software platform is spelled with a capital “H”.
Context: ...iverDigital/workflows, add a throwaway .github/workflows/spike-claude.yml—workflow...
(GITHUB)
[uncategorized] ~61-~61: The official name of this software platform is spelled with a capital “H”.
Context: ... 2. In vite-plugin-shopify-clean, add .github/workflows/spike-claude-caller.yml with...
(GITHUB)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/reusable-conversion-scope.md` around lines 58 - 71, Update the spike
steps to record the exact stale SHA-pinned uses reference for the reusable
speak-code.yml/claude.yml workflow and specify the caller workflow event that
triggers it. Tie the recorded SHA to the deliberately advanced workflows/main
state, ensuring the pilot invokes the pinned reusable workflow rather than the
default main pin and validates the caller-to-reusable OIDC path.
| `DRIVER_AGENTS_REF` moves **into** the reusable (a win — it removes half the two-files-must-match hazard). | ||
| `ANTHROPIC_API_KEY` stays comment-only. Every secret the reusable references must be **declared** under | ||
| `on.workflow_call.secrets` or this repo's own `lint.yml` goes red. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate relevant files =="
git ls-files | rg '(^|/)(docs/reusable-conversion-scope\.md|lint\.ya?ml|.*\.ya?ml)$' || true
echo
echo "== docs excerpt =="
if [ -f docs/reusable-conversion-scope.md ]; then
sed -n '150,190p' docs/reusable-conversion-scope.md
fi
echo
echo "== lint files =="
for f in lint.yml .github/workflows/lint.yml lint.yaml .github/workflows/lint.yaml; do
if [ -f "$f" ]; then
echo "--- $f ---"
sed -n '1,220p' "$f"
fi
done
echo
echo "== reusable workflow secret declarations in workflows =="
rg -n 'workflow_call|DRIVER_AGENTS_REF|ANTHROPIC_API_KEY|secrets:|secrets: inherit|GITHUB_TOKEN|ANTHROPIC_API_KEY|DRIVER_AGENTS_REF' -S .github lint.yml lint.yaml docs/reusable-conversion-scope.md 2>/dev/null || trueRepository: DriverDigital/workflows
Length of output: 9109
🌐 Web query:
GitHub Actions reusable workflows inherited secrets referenced without declaring on.workflow_call.secrets documentation
💡 Result:
In GitHub Actions, you can reference secrets in a reusable workflow without explicitly declaring them under on.workflow_call.secrets if, and only if, you use the secrets: inherit keyword in the calling workflow [1][2]. When you use secrets: inherit in the caller's job configuration, all secrets accessible to the calling workflow—including organization, repository, and environment secrets—are implicitly passed to the reusable workflow [1][3][4]. This allows the reusable workflow to access these secrets directly via the ${{ secrets.SECRET_NAME }} context, even if the on.workflow_call section does not define them [1][5]. Key considerations regarding this approach include: 1. Automatic Availability: The inherit keyword bypasses the need for individual mapping [1][6]. Once inherited, the secrets are available within the reusable workflow's steps as if they were native to the environment [7]. 2. Scope: Secrets are only passed to directly called workflows. If you have a chain of workflows (e.g., A calls B, which calls C), secrets must be explicitly passed or inherited at each transition point [1][2]. 3. Security/Best Practices: While convenient, using secrets: inherit provides the reusable workflow access to all secrets available to the caller [6]. Explicitly declaring secrets is often preferred for reusable workflows intended for use by external repositories or to adhere to the principle of least privilege, as it limits the "blast radius" by ensuring only necessary secrets are provided [6]. 4. Parse-time vs. Runtime: Attempts to reference inherited secrets within a with: block (as inputs) will fail at parse time because with: blocks are evaluated in the context of the caller, where the inherited secrets are not yet available [7]. Inherited secrets should only be referenced directly within the steps of the reusable workflow [7].
Citations:
- 1: https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows
- 2: https://docs.github.com/en/enterprise-cloud@latest/actions/how-tos/reuse-automations/reuse-workflows
- 3: [BUG] Environment-scoped secrets unreachable from reusable workflow without secrets: inherit, despite called job declaring environment actions/runner#4453
- 4: https://docs.github.com/en/[email protected]/actions/how-tos/reuse-automations/reuse-workflows
- 5: https://github.com/github/docs/blob/main/content/actions/how-tos/reuse-automations/reuse-workflows.md
- 6: https://notes.kodekloud.com/docs/GitHub-Actions-Certification/Reusable-Workflows-and-Reporting/Step-2-Using-Secrets-in-Reusable-Workflow/page
- 7: https://dev.to/cseeman/why-your-github-actions-secrets-dont-work-in-reusable-workflow-inputs-462g
Don’t require all inherited secrets to be declared.
Lines [170-172] say every referenced reusable secret must be declared under on.workflow_call.secrets. GitHub reusable workflows can use secrets: inherit, and declared secrets can be passed explicitly without listing each reused secret in the callee. Keep this as a lint policy only if lint.yml enforces the repository’s preferred calling style.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/reusable-conversion-scope.md` around lines 170 - 172, Revise the
reusable-workflow guidance around DRIVER_AGENTS_REF and ANTHROPIC_API_KEY so it
does not require every inherited or explicitly passed secret to be declared
under on.workflow_call.secrets. State that declarations are required only when
enforced by this repository’s lint.yml policy or the chosen calling style, while
preserving the existing secret-handling guidance.
Source: MCP tools
| ## Sequencing — two independent waves, `bonsai-status-sync` first | ||
|
|
||
| `bonsai-status-sync.yml` goes first because **it can be proven pre-merge**. Its `pull_request` trigger runs | ||
| from the PR merge ref, so its own cutover PR tests it. It has no OIDC path, one secret, and its 18 deployed | ||
| copies are currently **byte-identical** to the template (sha256 `811148d0…`, verified) — a genuinely known |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Make the OIDC spike gate only the Claude conversion.
The plan describes independent waves, but the phase order and README wording imply that the spike blocks both workflows.
docs/reusable-conversion-scope.md#L176-L180: run thebonsai-status-syncwave independently, or explicitly make Phase 0 a global gate.README.md#L96-L100: state that the spike gates onlyclaude.yml;bonsai-status-sync.ymlcan proceed if the spike fails.
📍 Affects 2 files
docs/reusable-conversion-scope.md#L176-L180(this comment)README.md#L96-L100
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/reusable-conversion-scope.md` around lines 176 - 180, Update the
sequencing plan so the OIDC spike gates only the Claude conversion, while the
bonsai-status-sync wave proceeds independently even if the spike fails. In
docs/reusable-conversion-scope.md lines 176-180, clarify this independent
ordering; in README.md lines 96-100, explicitly state that the spike gates
claude.yml only and bonsai-status-sync.yml may proceed on failure.
| | Phase | Work | Est. | | ||
| |---|---|---| | ||
| | 0 | Spike: go/no-go on OIDC-in-reusable (see above) | 3–4h | | ||
| | 1 | Convert `bonsai-status-sync.yml` + stub + docs + lint | 3h | | ||
| | 2 | Pilot it (public leg + private leg; testable pre-merge) | 2h | | ||
| | 3 | Fleet wave for `bonsai-status-sync`, 18 targets | 3–4h | | ||
| | 4 | Convert `claude.yml` — move the 404 lines **faithfully** | 6–8h | | ||
| | 5 | Tag + repin kit stubs (README's mandatory 3-step release order) | 1h | | ||
| | 6 | Pilot `claude.yml` with the four assertions incl. pin-vs-HEAD | 4–6h | | ||
| | 7 | Fleet wave: 10 single-branch repos → Avara → Palmers as one 8-branch batch | 4–5h | | ||
| | 8 | Optional: convert `shopify-tool-smoke.yml` | 2h | | ||
| | | **Total** | **28–36h** | |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Correct the total effort estimate.
The phase maxima sum to 35 hours, not 36:
4 + 3 + 2 + 4 + 8 + 1 + 6 + 5 + 2 = 35
Change the total to 28–35h, or add the missing hour to a phase.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/reusable-conversion-scope.md` around lines 190 - 201, Correct the Total
estimate in the phase plan to 28–35h, matching the sum of the listed phase
maxima; leave the individual phase estimates unchanged.
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/identity-unification-scope.md`:
- Around line 97-99: Update the release-wave stub’s dual-login acceptance so
each login is matched with its own immutable ID: retain claude[bot] with
209825114 and driver-digital-agents with 261291955 during the overlap, then
remove the claude[bot]/209825114 pair after migration.
- Around line 224-234: Update the schedule table’s estimate to match the
authoritative PR estimate of 28–36h across all eight phases, or revise the PR
objectives to 25–28h if that lower scope is intentional; document any resulting
scope change and ensure the phase count and total are consistent.
- Around line 160-163: The workflow documentation incorrectly claims every event
uses created and that changing any types list causes an infinite loop. Rewrite
the section to enumerate the actual event types, including opened for issues,
submitted for pull-request reviews, and created for issue comments, and state
that the loop invariant is specifically that issue_comment must not subscribe to
edited.
- Around line 249-263: Add explicit pilot assertions for the private
driver-agents clone and Shopify provisioning flow, including a valid
SHOPIFY_STORE_NAME, successful store provisioning, and successful completion of
both operations before the fleet wave begins; ensure failures cannot degrade to
warnings with a successful exit.
- Around line 176-177: Update the self-authored guard in the actor-gate
documentation to use event-specific user ID fields instead of the invalid
github.event.*.user.id wildcard: use github.event.issue.user.id for issue
branches, github.event.comment.user.id for comment branches, and the
corresponding exact actor field for every other covered event branch.
- Around line 40-45: Update the measurement plan in the documented workflow
scope to track the secondary content-generation limit separately from GET
/rate_limit: add a spike step that counts every content-generating write and
records 403/429 responses, response bodies, and Retry-After values, alongside
the existing primary-limit measurements.
- Around line 25-28: Update the rate-limit explanation in the installation-token
section to include the measured organization user count and GitHub App
user-based scaling above the 20-user base. Show the complete calculation
alongside the existing repository-based calculation so the 6,850–7,850 range is
fully derivable, preserving the existing plan and token-scope details.
In `@docs/reusable-conversion-scope.md`:
- Around line 97-100: Update the Phase 0 reference in the sequencing paragraph
to explicitly say “the Phase 0 spike” instead of “Phase 0 below,” while leaving
the surrounding sequencing guidance unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 718b5357-2f22-48bf-9f6c-0efdfd3e928c
📒 Files selected for processing (2)
docs/identity-unification-scope.mddocs/reusable-conversion-scope.md
| DriverDigital is on the **Team** plan (not Enterprise Cloud; verified via `gh api /orgs/DriverDigital`). The | ||
| Claude App is installed org-wide across 57 repos, giving its installation token roughly **6,850–7,850 req/hr** | ||
| (5,000 base + 50/repo above 20, capped at 12,500). A user PAT is a **flat 5,000/hr** with no scaling — and | ||
| critically it is **per-account, not per-token**, so the proposed two-PAT split does *not* buy two buckets. |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^docs/identity-unification-scope\.md$|README|doc)' || true
echo "== target snippet =="
if [ -f docs/identity-unification-scope.md ]; then
nl -ba docs/identity-unification-scope.md | sed -n '1,80p'
fi
echo "== repository/user references =="
rg -n "DriverDigital|installed|installation token|7,?850|6,?850|Team plan|Enterprise Cloud|repo above 20|organization users|GitHub rate|rate limit|per-account" .Repository: DriverDigital/workflows
Length of output: 340
🌐 Web query:
GitHub REST API rate limits for verified organizations paid plans installation limits organization members
💡 Result:
GitHub REST API rate limits vary based on authentication method, the type of application, and whether the organization is using GitHub Enterprise Cloud [1][2]. Standard Rate Limits For most authenticated requests, the standard rate limit is 5,000 requests per hour [1][2]. Requests made on your behalf by an OAuth app or a GitHub App may be subject to this limit, though limits are higher for applications owned by or installed on GitHub Enterprise Cloud organizations [1][2]. GitHub Apps and Scalable Limits GitHub Apps using an installation access token provide significant advantages for organizations: 1. Scalability: Unlike OAuth apps, which have fixed limits, the rate limit for GitHub Apps using an installation access token scales based on the number of repositories and the number of organization users [1][3]. 2. Enterprise Cloud: When an installation is on a GitHub Enterprise Cloud organization or enterprise, the minimum rate limit for the installation increases to 15,000 requests per hour [1][2]. Organization Membership and Specific Limits - API Requests: Generally, an individual user's membership status in an organization does not increase their personal 5,000 requests-per-hour limit [1][2]. However, if you are a member of a GitHub Enterprise Cloud organization, requests made on your behalf by an OAuth app approved or owned by that organization may benefit from a 15,000 requests-per-hour limit [1][2]. - Secondary Rate Limits: Regardless of primary limits, all users are subject to secondary rate limits to prevent abuse and ensure system stability [4][5]. These are triggered by rapid, high-volume requests (e.g., creating too many resources quickly) [4][6]. - Specific Operational Limits: Certain actions have non-API rate limits. For example, organization owners can create 50 invitations per 24 hours, which increases to 500 invitations per 24 hours if the organization is older than one month or on a paid plan [6]. Best Practices To manage these limits, GitHub recommends: - Using GitHub Apps instead of personal access tokens for automation [1][2]. - Using webhooks instead of polling the API [7]. - Implementing conditional requests with ETag or Last-Modified headers, which do not count against your limit if the content has not changed [7]. - Handling rate limit responses (403/429) by respecting the Retry-After, X-RateLimit-Remaining, and X-RateLimit-Reset headers [4].
Citations:
- 1: https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api
- 2: https://docs.github.com/en/enterprise-cloud@latest/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28
- 3: https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/deciding-when-to-build-a-github-app
- 4: https://docs.github.com/en/enterprise-cloud@latest/rest/using-the-rest-api/troubleshooting-the-rest-api?apiVersion=2026-03-10
- 5: https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/rate-limits-for-github-apps
- 6: https://docs.github.com/en/rest/orgs/members
- 7: https://docs.github.com/en/enterprise-cloud@latest/rest/using-the-rest-api/best-practices-for-using-the-rest-api?apiVersion=2026-03-10
🌐 Web query:
GitHub Apps installation limits 5000 requests per hour 50 per repository organizations users rate limits
💡 Result:
GitHub Apps that use an installation access token are subject to rate limits that scale based on the installation's activity, specifically the number of repositories and users in an organization [1][2]. Key details regarding GitHub App rate limits include: Base Limits For GitHub App installations not on a GitHub Enterprise Cloud organization, the base rate limit is 5,000 requests per hour per installation [2][3]. Scalability The rate limit scales beyond the base amount: - Repositories: Installations with more than 20 repositories receive an additional 50 requests per hour for each repository [2][3]. - Users: Installations on an organization with more than 20 users receive an additional 50 requests per hour for each user [2][3]. - Maximum: These scalable limits have an upper bound, meaning they cannot increase beyond 12,500 requests per hour [2][3]. GitHub Enterprise Cloud If an installation is on a GitHub Enterprise Cloud organization or enterprise, it receives a higher rate limit of 15,000 requests per hour [4][2]. Additional Considerations - GraphQL: GitHub Apps also have limits on the point value of queries made to the GraphQL API, which follow similar scaling rules (e.g., 5,000 points per hour base, scaling up to 12,500 points per hour) [5][3]. - Secondary Rate Limits: In addition to the primary limits mentioned above, GitHub may apply secondary rate limits to prevent abuse and ensure system availability [5][6]. - Best Practices: To stay within these limits, developers are encouraged to use webhooks instead of polling, implement conditional requests, and use consolidated GraphQL queries where possible [7]. If a limit is hit, applications should respect the x-ratelimit-reset or Retry-After headers [7].
Citations:
- 1: https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/deciding-when-to-build-a-github-app
- 2: https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api
- 3: https://github.com/github/docs/blob/main/content/graphql/overview/rate-limits-and-query-limits-for-the-graphql-api.md
- 4: https://docs.github.com/en/enterprise-cloud@latest/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28
- 5: https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/rate-limits-for-github-apps
- 6: https://github.com/github/docs/blob/main/content/apps/oauth-apps/building-oauth-apps/rate-limits-for-oauth-apps.md
- 7: https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/best-practices-for-creating-a-github-app
🌐 Web query:
GitHub Apps rate limits 6850 7850 50 per repository users
💡 Result:
For GitHub Apps using installation access tokens, the rate limit is not a fixed number like 6850 or 7850; rather, it is a scalable system based on the size of the installation [1][2]. The standard rate limits for GitHub Apps using installation access tokens are as follows: 1. Minimum Limit: Every installation starts with a baseline rate limit of 5,000 requests per hour [1][2][3]. 2. Scaling: If an installation is on an organization with more than 20 repositories, it receives an additional 50 requests per hour for each repository [1][2][3]. Similarly, if the organization has more than 20 users, it receives an additional 50 requests per hour for each user [1][2][3]. 3. Maximum Limit: The rate limit for an installation cannot increase beyond 12,500 requests per hour [1][2][3]. 4. GitHub Enterprise Cloud: For installations on a GitHub Enterprise Cloud organization, the rate limit is 15,000 requests per hour [1][2][3]. The numbers "6850" and "7850" do not correspond to standard GitHub App rate limits. If you are encountering these specific values in API response headers, they likely reflect the calculated limit for a specific installation based on its number of repositories and users at that time. GitHub Apps using user access tokens are instead subject to the primary rate limits of the authenticated user (typically 5,000 requests per hour) [1][2][3].
Citations:
- 1: https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api
- 2: https://docs.github.com/rest/using-the-rest-api/rate-limits-for-the-rest-api
- 3: https://docs.github.com/en/enterprise-cloud@latest/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28
Document the complete installation-rate-limit calculation.
The 6,850–7,850 range cannot be derived from repo count alone. GitHub Apps also scale by organization users above the 20-user base, so add the measured user count and the full calculation here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/identity-unification-scope.md` around lines 25 - 28, Update the
rate-limit explanation in the installation-token section to include the measured
organization user count and GitHub App user-based scaling above the 20-user
base. Show the complete calculation alongside the existing repository-based
calculation so the 6,850–7,850 range is fully derivable, preserving the existing
plan and token-scope details.
| 1. **Move every read that does not need the machine-user identity onto `github.token`** — a separate | ||
| **1,000/hr *per repository*** bucket, so across 21 targets that is ~21,000 req/hr of free capacity going | ||
| unused today. Concretely: `ticketed-review.yml:63,126` and `pr-first-review.yml:78` are pure reads. | ||
| 2. **Measure before committing.** Add a step to one `claude.yml` and one `ticketed-review` run that hits | ||
| `GET /rate_limit` at job start and end and echoes `x-ratelimit-used` — that endpoint does not count against | ||
| the limit. Multiply by wave size. |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== doc excerpt =="
if [ -f docs/identity-unification-scope.md ]; then
nl -ba docs/identity-unification-scope.md | sed -n '1,80p'
else
echo "docs/identity-unification-scope.md not found"
fi
echo
echo "== search rate_limit usage in repo =="
rg -n "rate_limit|x-ratelimit|Retry-After|403|429|content-generation|secondary" -S . \
--glob '!**/.git/**' --glob '!node_modules/**' || true
echo
echo "== relevant workflow files if present =="
for f in ticketed-review.yml pr-first-review.yml claude.yml; do
echo "--- $f occurrences ---"
git ls-files | rg "(^|/)${f}$|${f}"
doneRepository: DriverDigital/workflows
Length of output: 219
🌐 Web query:
GitHub REST API /rate_limit requests to check secondary rate limit status Retry-After x-ratelimit-remaining documentation
💡 Result:
The GitHub REST API does not provide a dedicated endpoint or method to check the current status of your secondary rate limit [1][2]. Secondary rate limits are triggered dynamically based on activity and are separate from the primary, documented rate limits [3][2]. To manage secondary rate limits, you must handle the error responses provided by the API: 1. Handling 403 or 429 Errors: When you exceed a secondary rate limit, the API will return a 403 Forbidden or 429 Too Many Requests response [1][4]. This response will typically include an error message indicating that a secondary rate limit has been triggered [4][5]. 2. Using the Retry-After Header: If the response includes a Retry-After header, you must wait for the number of seconds specified in that header before retrying your request [1][4]. 3. Exponential Backoff: If a Retry-After header is not provided, or if your request continues to fail, you should implement an exponential backoff strategy, increasing the wait time between retries [1][2][5]. 4. Best Practices: To avoid triggering secondary rate limits, the recommended practice is to perform requests serially rather than concurrently, and, for write operations (POST, PATCH, PUT, DELETE), to wait at least one second between requests [5]. Regarding the GET /rate_limit endpoint: - While you can use GET /rate_limit to check your primary rate limit status without it counting against that primary limit, this call can still count against your secondary rate limit [1][3]. - You should prioritize using the primary rate limit response headers (such as x-ratelimit-remaining and x-ratelimit-reset) included with standard API responses to monitor your primary limits instead of frequently calling /rate_limit [1][2].
Citations:
- 1: https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api
- 2: https://docs.github.com/rest/using-the-rest-api/rate-limits-for-the-rest-api
- 3: https://docs.github.com/en/rest/rate-limit/rate-limit
- 4: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api
- 5: https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== doc excerpt =="
python3 - <<'PY'
from pathlib import Path
p = Path("docs/identity-unification-scope.md")
if not p.exists():
print("docs/identity-unification-scope.md not found")
raise SystemExit
for i in range(1, 81):
print(f"{i:5d}: {p.read_text(encoding='utf-8').splitlines()[i-1]}")
PY
echo
echo "== search rate_limit usage in repo =="
rg -n "rate_limit|x-ratelimit|Retry-After|403|429|content-generation|secondary" -S . \
--glob '!**/.git/**' --glob '!node_modules/**' || true
echo
echo "== relevant workflow files if present =="
for f in ticketed-review.yml pr-first-review.yml claude.yml; do
echo "--- $f occurrences ---"
git ls-files | rg "^${f}$|^\\..*${f}$|/${f}$" || true
doneRepository: DriverDigital/workflows
Length of output: 7570
Track the secondary content-limit separately.
GET /rate_limit and x-ratelimit-used only instrument the primary GitHub REST request limit. The 500/hr content-generation limit has no separate status endpoint, so add a spike step that counts each content-generating write and records 403/429 errors, response text, and Retry-After data.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/identity-unification-scope.md` around lines 40 - 45, Update the
measurement plan in the documented workflow scope to track the secondary
content-generation limit separately from GET /rate_limit: add a spike step that
counts every content-generating write and records 403/429 responses, response
bodies, and Retry-After values, alongside the existing primary-limit
measurements.
| **Fix:** dual-accept both logins in the stub for one release wave so in-flight `claude[bot]` PRs keep their | ||
| revise loop mid-cutover, then drop the old literal. Pair the login with the immutable id `261291955`. | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Pair each accepted login with its own immutable ID.
The release-wave plan accepts both claude[bot] and driver-digital-agents, but then pairs the login with only 261291955. Existing claude[bot] events would fail the gate if both values are required. Preserve the claude[bot] ID 209825114 during the overlap, then remove that pair after migration.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/identity-unification-scope.md` around lines 97 - 99, Update the
release-wave stub’s dual-login acceptance so each login is matched with its own
immutable ID: retain claude[bot] with 209825114 and driver-digital-agents with
261291955 during the overlap, then remove the claude[bot]/209825114 pair after
migration.
| **What still terminates — and this is load-bearing and documented nowhere:** the agent's final response is an | ||
| `updateComment` (`issue_comment: edited`), and every `on:` block subscribes to **`[created]` only**. That | ||
| single fact is what stops the human-reply path. **Adding `edited` to any `types:` list is an instant infinite | ||
| loop.** Write that invariant into the file. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
State the loop invariant for the correct event.
The supplied workflow does not use [created] for every event. It uses [opened] for issues and [submitted] for pull-request reviews. The relevant invariant is that issue_comment does not subscribe to edited. Rewrite this section to enumerate the event types and avoid implying that changing any unrelated types: list creates the same loop risk.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/identity-unification-scope.md` around lines 160 - 163, The workflow
documentation incorrectly claims every event uses created and that changing any
types list causes an infinite loop. Rewrite the section to enumerate the actual
event types, including opened for issues, submitted for pull-request reviews,
and created for issue comments, and state that the loop invariant is
specifically that issue_comment must not subscribe to edited.
| 1. **Self-authored guard** — every actor-gate branch gains `github.event.*.user.id != 261291955`. Cost: the | ||
| agent can no longer hand off to itself across events. Accept that; it is the point. |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate file =="
git ls-files | rg '(^|/)docs/identity-unification-scope\.md$' || true
echo "== relevant lines =="
if [ -f docs/identity-unification-scope.md ]; then
nl -ba docs/identity-unification-scope.md | sed -n '140,205p'
fi
echo "== search for guard fields =="
rg -n "issue\.user\.id|comment\.user\.id|review\.user\.id|github\.event\.\*\.user\.id|github\.event\.|user\.id|261291955|actor-gate|gate" docs/identity-unification-scope.md || trueRepository: DriverDigital/workflows
Length of output: 278
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file stats =="
wc -l docs/identity-unification-scope.md || true
echo "== relevant lines =="
awk '{printf "%6d\t%s\n", NR, $0}' docs/identity-unification-scope.md | sed -n '140,205p'
echo "== search for guard fields =="
grep -n "issue\.user\.id\|comment\.user\.id\|review\.user\.id\|github\.event\.\*\.user\.id\|github\.event\.\|user\.id\|261291955\|actor-gate\|gate" docs/identity-unification-scope.md || trueRepository: DriverDigital/workflows
Length of output: 6158
🌐 Web query:
GitHub event payload issue_comment user id field github.event.comment.user.id
💡 Result:
In the GitHub Actions environment, when a workflow is triggered by an issue_comment event, the payload structure provided to the workflow includes a comment object [1][2][3]. Within the GitHub Actions context, you can access the user ID of the commenter using the expression ${{ github.event.comment.user.id }} [4]. The underlying structure is provided by the GitHub webhook payload for the issue_comment event [2][5]. Inside the payload, the comment object contains a user object, which holds information about the user who authored the comment [1][6]. The user object includes the following relevant fields: - id: The unique integer identifier for the GitHub user [1][6]. - login: The username of the user [1][6]. When using GitHub Actions, the event payload is accessible via the github context [1]. The hierarchy is typically: github.event -> comment -> user -> id [1][4]. This data is consistent with the GitHub REST API representation of an issue comment, where the user field contains the metadata for the comment author [7][8][6].
Citations:
- 1: https://stackoverflow.com/questions/74252630/get-the-login-of-a-user-that-issued-a-comment-on-github-actions
- 2: https://docs.github.com/en/webhooks/webhook-events-and-payloads
- 3: https://docs.github.com/en/rest/using-the-rest-api/github-event-types
- 4: https://github.com/DanTheMan827/ntag215-puck.js/blob/297835ae1ed4d251840767305f64211a3bd965ee/.github/workflows/approve-user.yml
- 5: https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=fixed
- 6: https://octo.guide/generated/api/interfaces/commententity/
- 7: https://docs.github.com/en/rest/using-the-rest-api/issue-event-types
- 8: https://docs.github.com/en/rest/activity/events?apiVersion=2026-03-10
Use event-specific fields for the self-authored guard.
The wildcard github.event.*.user.id does not match any GitHub Actions event context. Put github.event.issue.user.id != 261291955 on the issue branch, github.event.comment.user.id != 261291955 on the comment branch, and the equivalent exact field on any other actor branch the guard covers.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~176-~176: The official name of this software platform is spelled with a capital “H”.
Context: ...guard** — every actor-gate branch gains github.event.*.user.id != 261291955. Cost: th...
(GITHUB)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/identity-unification-scope.md` around lines 176 - 177, Update the
self-authored guard in the actor-gate documentation to use event-specific user
ID fields instead of the invalid github.event.*.user.id wildcard: use
github.event.issue.user.id for issue branches, github.event.comment.user.id for
comment branches, and the corresponding exact actor field for every other
covered event branch.
| | Phase | Work | Est. | | ||
| |---|---|---| | ||
| | 0 | Dump live `AGENTS_GH_PAT` scope; mint + test the two PATs | 2h | | ||
| | 1 | Rate-limit measurement spike (`GET /rate_limit` instrumentation) | 2h | | ||
| | 2 | `claude.yml` identity swap + containment set | 4–6h | | ||
| | 3 | Rail gate rework (3 sites + counters + sentinel matching) | 3–4h | | ||
| | 4 | Move reads onto `github.token`; add write retry/backoff | 3h | | ||
| | 5 | Pilot (two legs, assertions below) | 4h | | ||
| | 6 | Fleet wave, 21 targets | 4h | | ||
| | 7 | Wave 2: trigger phrase, incl. the orchestrator | 3h | | ||
| | | **Total** | **25–28h** | |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Align the schedule total with the PR estimate.
This table totals 25–28h, while the PR objectives state 28–36h across eight phases. Choose one authoritative estimate and document the scope change if the lower total is intentional.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~230-~230: The official name of this software platform is spelled with a capital “H”.
Context: ...atching) | 3–4h | | 4 | Move reads onto github.token; add write retry/backoff | 3h | ...
(GITHUB)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/identity-unification-scope.md` around lines 224 - 234, Update the
schedule table’s estimate to match the authoritative PR estimate of 28–36h
across all eight phases, or revise the PR objectives to 25–28h if that lower
scope is intentional; document any resulting scope change and ensure the phase
count and total are consistent.
| **Assertions, each with its false-pass named:** | ||
|
|
||
| 1. **Authorship** — the PR's `user.login` is `driver-digital-agents`. *False pass:* a prefill PR link, or | ||
| `gh pr view` reporting `app/claude`. Read the webhook. | ||
| 2. **Commit attribution** — commits are authored by `driver-digital-agents`, not `github-actions[bot]`. | ||
| *False pass:* checking the PR author instead of the commits. | ||
| 3. **Cascade** — a `bonsai-status-sync` run exists triggered by `pull_request`/`opened`, and the Bonsai task | ||
| reads Internal Review. *False pass:* the task already being in that state. | ||
| 4. **Ticketed rail fires** — exactly one `ticketed-review` run for the PR, and it actually reviews. | ||
| *False pass:* a green skip; assert a check run exists. | ||
| 5. **Tag mode intact** — a human `@` gets a visible tracking comment *and* a final reply. *False pass:* | ||
| a green run with no comment — the #148 signature. | ||
| 6. **No self-trigger** — after the agent replies, assert zero further `claude.yml` runs on that thread. | ||
| 7. **`gh issue develop` works** on the implementer PAT's fine-grained scope. | ||
| 8. **Rate-limit headroom** — `x-ratelimit-used` recorded at job end, extrapolated to wave size. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Add explicit assertions for private cloning and store provisioning.
The document says that a failed private driver-agents clone and Shopify provisioning step can degrade to a warning and exit successfully. The pilot assertions do not require either operation to succeed. Add checks for the private clone, SHOPIFY_STORE_NAME provisioning, and successful completion before the fleet wave.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/identity-unification-scope.md` around lines 249 - 263, Add explicit
pilot assertions for the private driver-agents clone and Shopify provisioning
flow, including a valid SHOPIFY_STORE_NAME, successful store provisioning, and
successful completion of both operations before the fleet wave begins; ensure
failures cannot degrade to warnings with a successful exit.
| **If that project ships first, Phase 0 below ceases to exist** — no App token means no OIDC exchange, no | ||
| default-branch validation, and no `job_workflow_ref` question. The `claude.yml` stub also stops needing | ||
| `id-token: write`, which was what made it the most privileged stub in the kit. Sequencing identity-first is | ||
| therefore the cheaper order. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the Phase 0 cross-reference.
Line 97 says “Phase 0 below”, but the Phase 0 spike is defined above, at Lines 36-74. Replace this with “the Phase 0 spike” to avoid ambiguity in the sequencing instructions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/reusable-conversion-scope.md` around lines 97 - 100, Update the Phase 0
reference in the sequencing paragraph to explicitly say “the Phase 0 spike”
instead of “Phase 0 below,” while leaving the surrounding sequencing guidance
unchanged.
The README has carried 'converting those two full workflows into reusables remains future work' since the repo split. This is the scope to execute it. The case, in one number: those two files are 594 of the kit's 837 lines, and they are the two that get COPIED rather than called. Every drift incident so far traces to that — most recently Avara, where a repo hand-carried a subset of upstream changes and shipped the prompt-hijack bug that had already been fixed centrally. Conversion does not remove waves (SHA pins still need bumping); it changes a wave from re-copying 594 lines into 18 targets into changing one SHA string, and makes drift structurally impossible because there is no downstream logic left to edit. Gated on one spike. claude-code-action mints the claude[bot] App token through an endpoint that validates the workflow file against the default branch. It is undocumented whether that check reads the caller stub or also the SHA-pinned reusable. If the latter, every pin breaks the moment workflows/main moves and the immutable-SHA policy cannot survive. Evidence from claude-code-action#443 says caller-only, but nobody there reports a SHA-pinned cross-repo reusable, so Phase 0 settles it empirically — deliberately run at a pin behind main HEAD, because a pilot at a pin equal to HEAD passes and then breaks the fleet later. Also written down: the github_token workaround is an explicit NON-option (it changes the PR author and silently misroutes every ticketed PR to the wrong rail), the claude.yml stub cannot be thin in the privilege sense, the actor gate belongs inside the reusable, SHOPIFY_STORE_NAME must become a with: input (which fixes a latent data-loss bug — the next re-copy would silently reset Avara's handle), and claude.yml has no pre-merge test path, so its cutover PR must say that an @claude test on it is expected to fail. Sequenced as two independent waves with bonsai-status-sync first, because it is the only one provable before merge. 28-36h total across 8 phases. Researched by five parallel agents; all six hard-blocker claims were put to an adversarial challenge pass and all six were refuted. Sourced to official GitHub docs, the action source at its pinned SHA, issue #443, and this repo's files.
…ents Scopes the GitHub surface running entirely as the machine user, inside Actions. A Slack agent is a separate entity and out of scope. Ten hard-blocker claims were each put to an adversarial challenge pass and all ten were refuted, so nothing structurally blocks this. Five findings are silent-failure class and are written up as requirements rather than risks. The one that could change the decision: the aggregate API ceiling drops ~58-61%. The Claude App installation token scales with repo count (~6,850- 7,850/hr across 57 repos); a user PAT is a flat 5,000/hr and the limit is per-ACCOUNT, so the two-PAT split buys no extra headroom. Worse, the 500/hr content-creation secondary limit is also per-account, so two independent comment budgets collapse into one. A full ticketed cycle is 30-60 content ops; the 141-run wave earlier today would have blown through it. Mitigable by moving reads onto github.token (a separate 1,000/hr PER REPO bucket, ~21,000/hr of unused capacity across the fleet), but it is a real regression and is flagged for a decision, not buried. The nastiest silent failure: a PAT-authored implementer PR matches NEITHER review rail. The ticketed stub gates round 1 on the claude[bot] literal so the reusable is never invoked, and pr-first's guard passes but its has_ticket step skips on the uuid. Both rails go green having done nothing. This also corrects reusable-conversion-scope.md, which claimed such PRs route to PR-first; they route to nothing. Loop containment is the centrepiece, and today's safety turns out to be entirely accidental identity asymmetry: claude[bot] comments carry author_association NONE and type Bot, so they fail the actor gate twice over and the action's own checkHumanActor rejects them. A machine User passes both. What still terminates the human-reply path is that the final response is an updateComment and every trigger subscribes to [created] only - load bearing, and documented nowhere until now. Adding 'edited' to any types list would be an instant infinite loop. Also: bot_id/bot_name default to github-actions[bot] so commits are misattributed unless set; AGENTS_GH_PAT's live scope is undocumented and the three descriptions of it in this repo contradict each other; and identity is separable from the trigger phrase, so ship the token swap first with zero UX change and flip @claude -> @driver-digital-agents as its own wave. Sequencing identity-first also deletes Phase 0 of the reusable conversion.
Both docs were written against main @ 9b70acf (v1.6.0). Main is now a54c91e (v1.9.0) — v1.7.0 added Slack alerting, v1.8.0 an audit context + artifact leg, v1.9.0 renamed the store secrets. Every in-repo file:line citation re-read at a54c91e, every number recomputed, citations path-qualified (several filenames exist in both templates/github/ and .github/workflows/ with different content). Version drift picked up: - kit is 916 lines / 650 copied (still 71%); claude.yml 404 -> 460 - DRIVER_AGENTS_SCOPES_* -> DRIVER_ENGINEERING_APP_* in the proposed interface - SHOPIFY_ALERT_WEBHOOK missing from that interface; it's org-level, so an explicit secrets map that omits it kills Slack alerting fleet-wide, green - SHOPIFY_STORE_NAME gained a second consumer (the v1.8.0 artifact name) - the "pin is naturally behind HEAD" reassurance is now false — main == v1.9.0 Errors found on re-checking, independent of the version bump: - the self-authored guard as drafted (github.event.*.user.id) is valid syntax that returns an array, coerces to NaN, and is therefore unconditionally true. It fails open silently on every event. - the dual-accept fix paired both logins with one id; claude[bot] is 209825114, not 261291955, so it would have matched no real claude[bot] event - the measurement spike polled /rate_limit, which cannot see the secondary limit it was meant to measure and consumes that budget while polling - the "[created] everywhere" loop invariant was wrong on two of four events - the round-counter has a second hardcoded site (:252) that decides revise-vs-handoff; changing only :137 forces a silent handoff every run - pilot assertions never required the private clone or store provisioning to succeed, both of which degrade to a warning and exit 0 - reusable phase table max summed to 35 while stating 36 Estimates now 29-37h (26-33h if identity ships first) and 26-30h. Phases 1-3 of the reusable conversion — the bonsai-status-sync half, 8-11h — turn out to depend on neither project's decision and could start now.
1e48d5e to
1376a61
Compare
Converts the Bonsai status-sync job into a cross-repo reusable. The 190-line per-repo copy is the kit's second-largest drift surface; this moves its logic into one file. The job body is byte-identical to the copy it replaces except for one added comment. Triggers, permissions and concurrency semantics are unchanged. actionlint + shellcheck clean on both halves. Deliberately NOT in this commit: the caller stub. A new reusable's stub cannot be pinned until the tag containing that reusable exists, so it lands in step 2 of the release order. That is the house precedent — dependabot-keep-current's reusable landed in c362604 and its stub arrived later already carrying a real SHA. The kit keeps installing the working 190-line copy until then. The ready-to-land stub is parked verbatim in the scope doc. Also adds a lint guard that fails the build on any kit stub left carrying an all-zero placeholder pin: an unresolvable `uses:` ref fails a consumer's run as startup_failure, which this repo's README documents as producing no check run and no notification. That is the one failure shape a client repo must never get silently, and a release checklist alone would not catch it. Verified against the org before committing, closing two open decisions: - no branch anywhere pins `sync` or `claude` as a required status check, so the `sync` -> `sync / sync` context rename breaks nothing (all 42 protected branches across 15 repos checked, both contexts[] and the newer checks[], plus org and repo rulesets) - the wave is 18 repo@branch pairs across 11 repos, not 21 — 21 is the repin target list, which additionally counts three stub-only repos carrying neither full workflow. claude.yml sits on exactly the same 18 pairs. Decisions recorded in the scope docs: identity unification deferred, and the claude.yml half of this conversion tabled pending the OIDC spike.
|
@claude please run /code-review:code-review |
…-first The two-leg pilot section was written for claude.yml, whose public-first order follows from its App-token path and the persist-credentials private-fetch 403. Neither exists in bonsai-status-sync.yml. What is untested for that file is a private consumer resolving a public cross-repo reusable, and 10 of the 11 targets are private — so the private leg is the one that can actually fail.
Code reviewFirst off — this is a genuinely careful PR. I checked the load-bearing claims rather than taking them on faith, and they hold up: the reusable's Found 3 issues, all documentation accuracy rather than behaviour: 1. The scope doc's decision banner says the caller stub already shipped, but it didn't — and the same doc says so 340 lines later. The banner states, present tense, that "the 190-line per-repo copy is now a 51-line caller stub," and the Why section repeats it as " workflows/docs/reusable-conversion-scope.md Lines 14 to 18 in 98451f3 workflows/docs/reusable-conversion-scope.md Lines 31 to 34 in 98451f3 That's contradicted by the doc's own Phase 1 landed section — "Deliberately NOT shipped: the caller stub. … The kit therefore still installs the 190-line copy, which still works, until the repin" — and by ground truth: workflows/docs/reusable-conversion-scope.md Lines 356 to 361 in 98451f3 This one matters more than a normal doc typo because this document is the sequencing record a future session executes the wave from, and the banner is the first thing that session reads. Same root cause one file over: the new reusable's header says "Callers install the stub from templates/github/", which today sends a reader to the full 190-line copy rather than a stub. workflows/.github/workflows/bonsai-status-sync.yml Lines 7 to 9 in 98451f3 2. The identity doc's occurrence count for The doc states "all 11 occurrences of workflows/docs/identity-unification-scope.md Lines 212 to 216 in 98451f3 That count was correct before this PR, but the new 3. Stale line citation in the new reusable's header. The "REUSABLE CONTEXT SEMANTICS" block points at workflows/.github/workflows/bonsai-status-sync.yml Lines 49 to 52 in 98451f3 Small, but this repo leans hard on line citations as navigation, and the PR body itself makes re-verifying citations a selling point. Two things I checked and deliberately did not flag: the |
- reusable-conversion-scope.md: the decision banner and the Why section both claimed the 190-line kit copy "is now a 51-line caller stub". It isn't — a stub can't be pinned until a tag containing the reusable exists, which the doc's own "Phase 1 landed" section says 340 lines further down. The banner now agrees with that section and with the README. - identity-unification-scope.md: the 261291955 edit-site list read 11 across 4 files. The Phase 1 reusable carries the actor gate too, so it's 13 across 5. Recounted against this branch, path-qualified (the basename now exists in both halves), and the 209825114 "zero hits" claim scoped to non-docs, since it now appears in that doc itself. - bonsai-status-sync.yml: header cited :110-113 for the gh pr view / gh issue view reads, which are at :115-118 (110-113 is the set -e comment block); and said callers install a stub from templates/github/ that doesn't exist yet. Both workflow edits are line-count preserving, so the :115-118 citation and the identity doc's :139 / :141 moving-target cites stay valid. actionlint 1.7.12 clean across .github/workflows/ and templates/github/.
…y tripwire (v1.11.0) Step 2 of the release order for the bonsai-status-sync conversion, plus the two kit content changes that ship with it. - templates/github/bonsai-status-sync.yml: 190-line per-repo copy replaced by a 67-line caller stub. Completes the Phase 1 conversion started in #21. - All six stubs repinned. Note they jump v1.8.0 -> v1.10.0: v1.9.0 never got a kit repin commit while the 2026-08-01 wave repinned the fleet anyway, so the fleet had been a release ahead of the kit templates. Closed here. - DRIVER_AGENTS_REF -> 4d63371 in claude.yml and shopify-tool-smoke.yml (lockstep). The old pin 0bbb125 predated graphql_guard.py, so fleet runners had no fail-closed allowlist on destructive Shopify mutations. - The Shopify operator tripwire in claude.yml's static system prompt, pairing with that wrapper. - lint.yml: new guard asserting the system prompt survives shell-quote tokenization. - Both scope docs de-staled and every claude.yml citation re-anchored by content. Reviewed across two multi-agent passes; all findings above the bar applied. actionlint and the new tokenization guard green. Ships as v1.11.0 — tagged at this merge commit, then the six stubs repin to that SHA, then one wave to 18 repo@branch pairs. The fleet is NOT waved by this merge.
Two things, in dependency order: the scope docs get refreshed against current
main, then the first phase of the conversion they describe actually ships.What changed since this branch was opened
The branch was written against
main@9b70acf(v1.6.0).mainis nowa54c91e(v1.9.0) — three releases landed underneath it. The branch has been rebased onto main and both docs re-verified line-by-line.1.
feat: thebonsai-status-syncreusable.github/workflows/bonsai-status-sync.yml— the Bonsai status-sync job, converted from the 190-line per-repo copy that every fleet repo carries.BONSAI_BEARER_TOKENdeclaredrequired: true(org-level secret, no legitimate caller without it — fails at startup rather than as a 401 mid-run).The caller stub is deliberately not in this PR. A new reusable's stub cannot be pinned until the tag containing that reusable exists, so it lands in step 2 of the release order. Same as
dependabot-keep-current, whose reusable landed inc362604with its stub following later already carrying a real SHA. The kit keeps installing the working 190-line copy until then; the ready-to-land stub is parked verbatim in the scope doc.New lint guard: fails the build on any kit stub left carrying an all-zero placeholder pin. An unresolvable
uses:ref fails a consumer's run asstartup_failure, which this repo's README documents as producing no check run and no notification — the one failure shape a client repo must never get silently.2.
docs: both scope docs refreshed and decidedDecisions recorded: identity unification deferred; the
claude.ymlhalf of the conversion tabled pending the OIDC spike; thebonsai-status-synchalf approved and now in progress.Version drift picked up: kit is 916 lines / 650 copied,
claude.yml404 → 460, store secrets renamed,SHOPIFY_ALERT_WEBHOOKwas missing from the proposed interface (org-level, so an explicit map that omits it kills Slack alerting fleet-wide on a green run).Errors found that were wrong when written, independent of the version bump:
github.event.*.user.id) is valid syntax that returns an array, coerces toNaN, and is therefore unconditionally true — it fails open silently on every eventclaude[bot]is209825114, so it would have matched no realclaude[bot]event/rate_limit, which cannot see the secondary limit it was meant to measure, and consumes that budget while polling[created]-everywhere loop invariant was wrong on two of four events::warning::+exit 0Verified against the org, closing two open decisions
syncorclaudeas a required status check, so thesync→sync / syncrename breaks nothing. All 42 protected branches across 15 repos checked — bothcontexts[]and the newerchecks[], plus org and repo rulesets.claude.ymlsits on exactly the same 18 pairs.Two incidental findings worth knowing:
studio-sulzer@mainandTeam-Laird@developare live kit branches with no branch protection at all; andREADME.mdstill said v1.8.0 whilemainwas v1.9.0 (fixed here).Next
Phase 2 (pilot) → Phase 3 (wave, 18 targets) + tag/repin. ~6–7h. Only the
pull_requestleg is testable pre-merge — theissuesandpull_request_reviewlegs are default-branch-only and need validating after merge.