feat(add-to-project): reusable workflow + reconcile parity (#415)#466
Conversation
) Implements the multi-repo follow-on from the Initiatives Project pilot. Architecture decided in #415: reusable workflow + thin per-repo caller stubs pinned to the `add-to-project/stable` channel (not a webhook handler). §1 Multi-repo deployment - New `add-to-project-reusable.yml` holds all jobs; inputs for project_id, project_url, required_label, excluded_labels, ideas_category, agent_ref. - `add-to-project.yml` becomes the self-host thin caller (local ref, Ring 0 dogfood; passes agent_ref=${{ github.sha }}). - `standards/workflows/add-to-project.yml` is the adoptable template, pinned to add-to-project/stable for both the reusable ref and agent_ref. §3 Hard-coded labels/category → env-driven - evaluate_noise_gate reads REQUIRED_LABEL / EXCLUDED_LABELS; reconcile_ discussion reads IDEAS_CATEGORY. Defaults preserve current behavior. §2 Reconcile, not just add - Shared lib.sh with a single paginated find_project_item (title-prefix | content-id), plus add/draft/delete helpers — the "designed once" mechanism. Issues/PRs that stop qualifying (dev-lead removed or excluded label added) are now removed; workflow subscribes to `unlabeled`. §4 Fork-PR gate - add-issue-or-pr job runs for same-repo PRs (head.repo.fork == false) or trusted authors. External-fork PRs by untrusted authors remain skipped (documented; needs a non-`if:` solution) — see #415 §4. Tests: add-to-project bats suite 35 → 42 (remove path, env-driven config, env-driven category). shellcheck/yamllint/actionlint clean. CONTRIBUTING updated with adoption + reconcile docs.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Review limit reached
More reviews will be available in 49 minutes and 11 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughIntroduces ChangesReconcile-based add-to-project refactor
Sequence Diagram(s)sequenceDiagram
participant GH as GitHub Event
participant Workflow as add-to-project-reusable.yml
participant Script as add-issue-or-pr.sh
participant lib as lib.sh
participant API as GitHub GraphQL API
GH->>Workflow: issues/pull_request_target event
Workflow->>Workflow: fork/trusted-author gate check
Workflow->>Script: invoke with CONTENT_NODE_ID, LABELS_JSON, REQUIRED_LABEL, EXCLUDED_LABELS
Script->>Script: evaluate_noise_gate(labels_json)
alt qualifies (status 0)
Script->>lib: add_content_to_project(content_node_id)
lib->>API: addProjectV2ItemById mutation
API-->>lib: item added
else no longer qualifies (status 1)
Script->>lib: find_content_item_id(content_node_id)
lib->>API: projectV2 items query (paginated)
API-->>lib: item_id or not found
lib-->>Script: item_id
Script->>lib: delete_project_item(item_id)
lib->>API: deleteProjectV2Item mutation
API-->>lib: deleted or "not found" (treated as success)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Dev-Lead — waiting on PR blockers (intent: review-changes)PR: #466 |
|
Note @don-petry I reviewed this PR and no code changes were needed, but it still has blocking checks or reviews (failing or cancelled checks, or changes-requested reviews), so I cannot mark it done yet. I'll re-check automatically. |
SonarCloud githubactions:S7635 — replace `secrets: inherit` with explicit INITIATIVES_APP_ID / INITIATIVES_APP_PRIVATE_KEY in the self-host caller and the adoptable template (least privilege; the reusable declares exactly those two).
There was a problem hiding this comment.
Code Review
This pull request refactors the project board automation by extracting shared GraphQL operations and helper functions into a new lib.sh library, which is now utilized by both add-issue-or-pr.sh and reconcile-discussion.sh. It also introduces reconciliation logic to automatically remove items from the board when they no longer qualify, adds support for environment-driven configuration overrides, and includes comprehensive BATS tests. The review feedback suggests simplifying the case statement and optimizing the GraphQL response parsing in lib.sh to avoid multiple subshells, using the ${EXCLUDED_LABELS-...} parameter expansion to allow empty exclusions, and adding a macOS fallback for sha256sum in the test suite to improve local test portability.
- evaluate_noise_gate: use ${EXCLUDED_LABELS-default} (no colon) so an
explicitly empty value disables exclusions; jq -Rs handles empty input.
- find_project_item: pass the match predicate to jq as $kind (fully static
filter, no shell interpolation) and read the parsed fields with a single
while-read loop instead of four awk subshells.
- Add a test for empty EXCLUDED_LABELS disabling exclusions.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 @.github/workflows/add-to-project-reusable.yml:
- Around line 95-101: Add the security hardening parameter persist-credentials:
false to both checkout steps in the workflow file. In
.github/workflows/add-to-project-reusable.yml at lines 95-101, add
persist-credentials: false to the Checkout add-to-project scripts step. In
.github/workflows/add-to-project-reusable.yml at lines 131-137, add
persist-credentials: false to the second checkout step as well. This prevents
credential persistence across the workflow execution, reducing the risk of
accidental credential exposure if the workflow is extended with additional
operations later.
- Line 96: The inline version comment for the actions/checkout action is
incorrect. The pinned SHA df4cb1c069e1874edd31b4311f1884172cec0e10 corresponds
to version v6, not v5.0.0 as currently stated in the comment. Update the version
comment from v5.0.0 to v6 on the line containing the actions/checkout action to
accurately reflect the actual version of the pinned SHA.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: ccfe15c2-a825-4c6c-b0e5-0f03a7474c6b
📒 Files selected for processing (10)
.github/scripts/add-to-project/add-issue-or-pr.sh.github/scripts/add-to-project/lib.sh.github/scripts/add-to-project/reconcile-discussion.sh.github/workflows/add-to-project-reusable.yml.github/workflows/add-to-project-tests.yml.github/workflows/add-to-project.ymlCONTRIBUTING.mdstandards/workflows/add-to-project.ymltest/workflows/add-to-project/add-issue-or-pr.batstest/workflows/add-to-project/reconcile-discussion.bats
Dev-Lead — fix-bot-comment (applied)Changes committed and pushed. |
|
Dev-Lead — review-changes (no-changes)No changes were needed for this PR. |
Code reviewOverview. Refactors the Initiatives-project automation from a single-repo, add-only workflow into a reusable workflow + thin caller stubs, and upgrades the scripts from "add" to full "reconcile" (add when qualifying, remove when not). Implements #415 §1–4. Strengths
Suggestions (non-blocking)
Rollout prerequisite (already documented)The template pins Security — looks right
Tests43 bats covering add/remove/skip, env-driven gate + category, malformed payloads, pagination cursor, multi-match, null-node, draft↔issue. shellcheck ( Verdict: approve-quality. Correct, well-tested, faithful to org standards. The only thing worth acting on before heavy multi-repo fan-out is the Generated by Claude Code |
donpetry-bot
left a comment
There was a problem hiding this comment.
Automated review — APPROVED ✓
Risk: MEDIUM
Reviewed commit: df40c1caf63549e82c7813b56e617e618341fbe0
Review mode: triage-approved (single reviewer)
Summary
Refactors the auto-add-to-Initiatives-project automation into a reusable workflow + thin per-repo caller stubs (#415 §1), extracts a shared lib.sh with a paginated find_project_item helper, makes the noise-gate labels/category env-driven (§3), adds reconcile-on-unqualify (remove items that stop qualifying, §2), and tightens the fork-PR gate (§4). Confirmation review for triage-approved: the security-sensitive pull_request_target + secrets pattern is implemented correctly.
Linked issue analysis
No closingIssuesReferences (intentional — the PR notes execution of the multi-repo rollout is gated on the 2026-07-07 review #414; this is the shovel-ready implementation, not the rollout). It substantively addresses the four sub-items of the architecture decision #415: §1 reusable+thin-caller, §2 reconcile/remove, §3 env-driven config, §4 fork-PR gate. The deferred residual (maintainer-labeled PR from an untrusted external fork) is explicitly documented and fails closed.
Findings
Security (pull_request_target + secrets) — handled correctly:
- The reusable does not check out untrusted PR head code:
actions/checkoutusesref: ${{ inputs.agent_ref }}(self-host passesgithub.sha; template pinsadd-to-project/stable), withpersist-credentials: false. - Fork-PR gate fails closed: the issue/PR job runs only for
github.event_name == 'issues', same-repo PRs (head.repo.fork == false), or trusted authors (OWNER/MEMBER/COLLABORATOR). External-fork PRs by untrusted authors are skipped. - No shell/GraphQL injection: PR & discussion titles, labels, node IDs, URLs are passed via
env:and supplied togh api graphqlas-F/--argvariables — never interpolated intorun:scripts or query bodies. - Least privilege: top-level
permissions: {}, job-levelcontents: read; project mutations use a short-lived App installation token (notGITHUB_TOKEN). Secrets are passed explicitly to the reusable rather than viasecrets: inherit— an improvement over a blanket inherit. - Actions are SHA-pinned (checkout, create-github-app-token).
Correctness:
add-issue-or-pr.sh: qualifying → idempotentaddProjectV2ItemById(no find-first, relies on documented idempotency); non-qualifying → find by content-id and delete if present. Matches the new bats cases.lib.shfind_project_itempaginates and fails loudly (exit 75) ondata.node == nullrather than risking duplicate adds; multi-match emits a warning and proceeds with the first. The${line#prefix=}parsing preserves=in base64 cursors.EXCLUDED_LABELSuses${VAR-default}(no colon) so an explicitly empty value opts out of all exclusions while unset keeps defaults — covered by a dedicated test.- Trigger now includes
unlabeled(issues + PRs) so the remove path actually fires; concurrency group preserved.
Maintainability — minor (non-blocking):
- The new
add-to-project-reusable.ymlandstandards/workflows/add-to-project.ymlpinactions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10with comment# v6, but the pre-existing caller commented the same SHA as# v5.0.0. One comment is stale — reconcile so the pin comment is accurate (the SHA itself is what's enforced; actionlint is green).
CI status
All required checks green: Agent Security Scan, CodeQL/Analyze (actions), agent-shield/AgentShield, Secret scan (gitleaks), ShellCheck, Shellcheck and bats, SonarCloud, Lint, Detect ecosystems, pr-auto-review, dev-lead/dispatch — all SUCCESS. Language audits (cargo/npm/pip/pnpm/go) SKIPPED (not applicable). mergeStateStatus is BLOCKED only because reviewDecision is REVIEW_REQUIRED (this review).
Reviewed automatically by the PR-review agent (single-reviewer mode: fable 5). Reply if you need a human review.



Implements the multi-repo follow-on (#415) to the Initiatives Project pilot (#387), per the architecture decided in #415: reusable workflow + thin per-repo caller stubs pinned to the
add-to-project/stablechannel — not a webhook handler.What changed
§1 Multi-repo deployment — reusable + thin callers
add-to-project-reusable.ymlholds both jobs (issue/PR reconcile, discussion reconcile). Inputs:project_id,project_url,required_label,excluded_labels,ideas_category,agent_ref. Secrets:INITIATIVES_APP_ID/INITIATIVES_APP_PRIVATE_KEY.add-to-project.ymlis now the self-host thin caller (local./ref, Ring 0 dogfood; passesagent_ref: ${{ github.sha }}so scripts match the exact commit).standards/workflows/add-to-project.ymlis the adoptable template, pinning both the reusable ref andagent_reftoadd-to-project/stableper ci-standards.md → Reusable workflow versioning. Scripts are checked out viapath:frompetry-projects/.github@agent_ref, mirroring the provenfeature-ideation-reusable.ymlpattern.§3 Hard-coded labels/category → env-driven
evaluate_noise_gatereadsREQUIRED_LABEL+EXCLUDED_LABELS;reconcile_discussionreadsIDEAS_CATEGORY. Defaults preserve current behavior (dev-lead; the four excluded labels;Ideas).§2 Reconcile, not just add
lib.shwith a single paginatedfind_project_item(predicate:title-prefix|content-id) plus the add/draft/delete mutations and env guard — the "designed once" mechanism Initiatives Project: multi-repo rollout + reconciliation parity #415 called for. Both scripts source it; the discussion script keeps its public function names as thin wrappers.dev-leadremoved, or an excluded label added) are now removed from the board. The caller subscribes tounlabeledfor issues and PRs.§4 Fork-PR gate
head.repo.fork == false) or trusted authors (OWNER/MEMBER/COLLABORATOR). External-fork PRs by untrusted authors are still skipped — anif:can't safely evaluate the labeler; this residual case is documented and left for a non-if:follow-up (Initiatives Project: multi-repo rollout + reconciliation parity #415 §4).Tests / validation
test/workflows/add-to-projectbats suite: 35 → 42 (remove path, env-driven gate, env-driven category, multi-match/pagination preserved). All green locally.shellcheck -S warningandshellcheck -xclean;yamllint(max 200) clean;actionlintclean on all workflows;markdownlint-cli2clean onCONTRIBUTING.md.CONTRIBUTING.mdupdated: adoption-in-another-repo section, reconcile-on-unqualify behavior, revised fork-PR note.Prerequisites for the rollout (not code)
petry-projects-plannerapp installation must include each adopting repo.add-to-project/vX.Y.Zand move theadd-to-project/stablechannel before any external repo adopts the template (the template pins to that channel).Related
https://claude.ai/code/session_01SS98x3hHsfPRJfGLgJMFom
Generated by Claude Code
Summary by CodeRabbit
New Features
Documentation