Skip to content

fix(add-to-project): make un-gated reconcile+classify pipeline complete reliably - #635

Merged
don-petry merged 5 commits into
mainfrom
fix/reconcile-prefetch-membership
Jul 10, 2026
Merged

fix(add-to-project): make un-gated reconcile+classify pipeline complete reliably#635
don-petry merged 5 commits into
mainfrom
fix/reconcile-prefetch-membership

Conversation

@don-petry

@don-petry don-petry commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Why

Un-gating issues (#608) put ~450 open issues in scope for the daily add-to-project reconcile + classify pipeline. That exposed three defects which together left the Initiatives board almost entirely unclassified — only 1 of ~840 board items had an Initiative set, even though the classifier matches ~99% of them.

Root causes & fixes

1. Reconcile overran the 20-min job timeout → classify never ran.
reconcile-backlog.sh made one idempotent GraphQL add per qualifying item and one find-to-remove per disqualified item, every cycle — ~450+ sequential round-trips. The reconcile job (which runs classify as step 2) hit its 20-min timeout mid-reconcile and was cancelled daily, so classify was skipped every run.
→ Prefetch board membership once into declare -gA _ATP_ON_BOARD; both hot paths (add_content_to_project, the disqualify find) consult it and skip the API call unless a genuine change is needed. Gated on _atp_membership_ready, which the event path never sets — event-path behavior unchanged. Job timeout raised 20→30 for cold catch-up cycles.

2. All-numeric single-select option ids were rejected.
set_item_single_select_value passed optionId with gh api -F, which type-infers: an all-digit id (e.g. GH-AW's 91479014) was coerced to a JSON number and the String! param rejected it (provided invalid value), aborting the sweep on the first such item. Dry-run never calls the API, so this was invisible in testing.
→ Send optionId with -f (raw string). ID! args stay -F (node ids always carry a non-numeric prefix).

3. No per-item resilience in classify.
A single transient API error (network blip / secondary rate limit) aborted the entire ~800-item sweep under set -e, losing all progress.
→ Per-item error isolation: log a warning, count it (transient set fails in the summary), continue. The fill-blanks-only next sweep is idempotent and refills anything left blank — mirrors reconcile-backlog.sh's model.

Validation

Run end-to-end against the live board (org project #1) with these scripts:

  • optimized reconcile: prefetched 699 items, 233 genuine adds, 265 disqualified-skips with zero wasted find lookups, 0 erroneous removes — full 10-repo fleet in one pass.
  • classify: fills the board with 0 invalid value errors and 0 transient failures.

Tests

  • +4 reconcile fast-path tests (prefetch skip-add / skip-find / still-add-when-absent).
  • +1 regression: an all-numeric option id is sent as a string (-f, not -F).
  • 71 bats green, shellcheck clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Reconciliation now uses a preloaded project membership check to speed up board updates and reduce unnecessary actions.
    • Longer reconcile runs are allowed, helping large backlogs complete more reliably.
  • Bug Fixes

    • Improved handling of project field updates so numeric option values are passed correctly.
    • Reconcile and classify jobs now continue past individual item failures instead of stopping the whole run.
    • Items already known to be off the board are skipped cleanly without extra lookup steps.

…te reliably

Un-gating issues (#608) put ~450 open issues in scope for the daily
reconcile+classify pipeline, which surfaced three defects that together left
the Initiatives board almost entirely unclassified (1 of ~840 items had an
Initiative set):

1. Reconcile timeout. reconcile-backlog.sh issued one idempotent GraphQL add
   per qualifying item AND one find-to-remove per disqualified item, every
   cycle — ~450+ sequential round-trips that overran the 20-min job timeout,
   so the classify step (step 2) was cancelled every run. Fix: prefetch board
   membership once (declare -gA _ATP_ON_BOARD) and let both hot paths answer
   "already on the board?" from memory — only genuine adds/removes touch the
   API. Gated on _atp_membership_ready so the event path is unchanged. Job
   timeout raised 20->30 for cold catch-up cycles.

2. All-numeric option id rejected. set_item_single_select_value passed the
   single-select option id with `gh api -F`, which type-infers: an all-digit
   id (e.g. GH-AW's "91479014") became a JSON number and the String! param
   rejected it ("provided invalid value"), aborting the whole sweep on the
   first such item. Dry-run never calls the API, so it was invisible. Fix:
   send optionId with `-f` (raw string).

3. No per-item resilience. A single transient API error (network blip,
   secondary rate limit) aborted the entire ~800-item classify sweep under
   set -e, losing all progress. Fix: per-item error isolation — log a
   warning, count it, continue; the fill-blanks-only next sweep is idempotent
   and refills anything left blank. Mirrors reconcile-backlog's model.

Tests: +4 reconcile fast-path (prefetch skip add / skip find), +1 regression
that an all-numeric option id is sent as a string (-f, not -F). 71 bats green,
shellcheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@don-petry
don-petry requested a review from a team as a code owner July 9, 2026 19:47
Copilot AI review requested due to automatic review settings July 9, 2026 19:47
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@don-petry, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 49b83a74-6ade-439d-b76e-01a4bb526920

📥 Commits

Reviewing files that changed from the base of the PR and between 2c4377e and 5a6cc66.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • .github/scripts/add-to-project/classify-initiative.sh
  • .github/scripts/add-to-project/reconcile-backlog.sh
📝 Walkthrough

Walkthrough

This PR adds an in-memory board-membership cache to the add-to-project scripts to reduce redundant GraphQL calls during reconciliation, fixes a numeric optionId typing bug in set_item_single_select_value, makes classify-initiative's sweep tolerant of per-item write failures via a new failed counter, and increases the reconcile workflow timeout, alongside corresponding test coverage.

Changes

Add-to-Project Reconcile Improvements

Layer / File(s) Summary
Membership cache primitives
.github/scripts/add-to-project/lib.sh
Adds _atp_membership_ready and _atp_on_board cache-check helpers; add_content_to_project no-ops when cache indicates the item is already on board.
Prefetch board membership
.github/scripts/add-to-project/reconcile-backlog.sh, test/workflows/add-to-project/reconcile-backlog.bats
Adds prefetch_board_membership, paginating GraphQL project items into a global cache before reconciliation, with error handling and new tests for the fast-path behavior.
Skip removal via cache
.github/scripts/add-to-project/add-issue-or-pr.sh
Skips find_content_item_id/removal when the cache confirms the item is not on the board.
Fix optionId typing
.github/scripts/add-to-project/lib.sh, test/workflows/add-to-project/classify-initiative.bats
Switches optionId GraphQL binding from -F to -f to avoid numeric coercion errors; adds a regression test.
Resilient classify-initiative sweep
.github/scripts/add-to-project/classify-initiative.sh
Adds a failed counter reported in _ci_report; guards Initiative/Theme field writes so failures warn and continue instead of aborting.
Workflow timeout increase
.github/workflows/add-to-project-reconcile.yml
Raises the reconcile job timeout from 20 to 30 minutes.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ReconcileScript as reconcile-backlog.sh
  participant PrefetchFn as prefetch_board_membership
  participant GitHubAPI as GitHub GraphQL API
  participant Cache as Membership Cache
  participant AddOrPr as add-issue-or-pr.sh

  ReconcileScript->>PrefetchFn: call prefetch_board_membership
  PrefetchFn->>GitHubAPI: query ProjectV2 items (paginated)
  GitHubAPI-->>PrefetchFn: items with content.id
  PrefetchFn->>Cache: populate content IDs, set ready flag
  ReconcileScript->>AddOrPr: process content item
  AddOrPr->>Cache: _atp_membership_ready / _atp_on_board
  Cache-->>AddOrPr: on-board / not-on-board
  AddOrPr->>AddOrPr: skip add or skip removal accordingly
Loading

Possibly related PRs

  • petry-projects/.github#466: Both PRs modify the shared .github/scripts/add-to-project/lib.sh GraphQL helpers, evolving add_content_to_project and project-item handling logic used by reconciliation scripts.

Suggested labels: needs-human-review

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: improving the add-to-project reconcile and classify pipeline reliability.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/reconcile-prefetch-membership

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — review-changes (no-changes)

No changes were needed for this PR.

@don-petry
don-petry enabled auto-merge (squash) July 9, 2026 19:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves reliability and throughput of the daily “add-to-project reconcile + classify” pipeline by eliminating redundant per-item GraphQL calls during backlog reconciliation, fixing a GraphQL variable typing pitfall for numeric-looking option IDs, and making classification resilient to per-item transient failures.

Changes:

  • Add a one-time board membership prefetch to skip idempotent add/remove lookup calls during fleet-wide backlog reconcile, and increase workflow timeout for cold catch-up runs.
  • Fix single-select optionId submission to always be treated as a string (gh api -f), preventing GraphQL String! validation failures for all-numeric option IDs.
  • Add per-item error isolation to the classify sweep, with summary reporting and new regression/perf-path tests.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/workflows/add-to-project/reconcile-backlog.bats Adds tests and improved GH stub behavior to validate the membership-prefetch fast path.
test/workflows/add-to-project/classify-initiative.bats Adds regression coverage ensuring numeric-looking option IDs are sent as raw strings.
.github/workflows/add-to-project-reconcile.yml Raises reconcile job timeout to avoid starving the classify step during catch-up runs.
.github/scripts/add-to-project/reconcile-backlog.sh Implements membership prefetch caching for the backlog reconcile hot paths.
.github/scripts/add-to-project/lib.sh Adds membership-cache helpers and fixes optionId submission to prevent type inference issues.
.github/scripts/add-to-project/classify-initiative.sh Adds per-item resilience, failure counting, and extended summary output for classify sweeps.
.github/scripts/add-to-project/add-issue-or-pr.sh Skips remove “find” calls when cache indicates content is not on the board (batch reconcile only).

Comment thread .github/scripts/add-to-project/classify-initiative.sh Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request optimizes the project reconciliation process by introducing a prefetched board-membership cache to reduce API round-trips, improving error resilience during initiative sweeps, and fixing a type-coercion bug with numeric option IDs. The review feedback focuses on enhancing the robustness of the new Bash and jq code in reconcile-backlog.sh by using defensive jq filters (using the ? operator) to prevent null-indexing errors, handling potential trailing newlines and carriage returns when reading stream inputs, and explicitly returning 0 from the prefetch function.

Comment thread .github/scripts/add-to-project/reconcile-backlog.sh Outdated
Comment thread .github/scripts/add-to-project/reconcile-backlog.sh Outdated
Comment thread .github/scripts/add-to-project/reconcile-backlog.sh Outdated
Comment thread .github/scripts/add-to-project/reconcile-backlog.sh
@don-petry
don-petry disabled auto-merge July 9, 2026 19:51
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 9, 2026
@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — fix-reviews (applied)

Changes committed and pushed.

@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — waiting on PR blockers (intent: review-changes)

PR: #635
No changes were committed, but the PR still has blocking checks or reviews (failing or cancelled checks, or changes-requested reviews). The retry cron will re-attempt automatically. Next attempt after: 2026-07-09T20:25:44Z

@don-petry

Copy link
Copy Markdown
Contributor Author

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.
Next attempt after: 2026-07-09T20:25:44Z

@don-petry
don-petry enabled auto-merge (squash) July 9, 2026 19:55

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 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/scripts/add-to-project/classify-initiative.sh:
- Around line 293-317: The Theme warning in classify-initiative.sh is misleading
because the item will be skipped on later sweeps once Initiative is already set,
so the Theme write is not retried automatically. Update the warning text in the
Theme best-effort block around set_item_single_select_value and
CI_THEME_FIELD_ID to reflect that a failed Theme assignment will not be retried
on the next normal sweep unless RECLASSIFY=all is used, while keeping the
Initiative success path unchanged.

In @.github/scripts/add-to-project/reconcile-backlog.sh:
- Around line 83-86: Broaden the membership prefetch validation in
reconcile-backlog.sh so it rejects any response where the fetched node is not a
ProjectV2, not just when .data.node is null. Update the jq-based guard around
the membership prefetch to also verify that .data.node.items exists before
allowing the cache to be marked ready, so _ATP_MEMBERSHIP_READY is only set when
the ProjectV2 items payload is actually present.
- Around line 94-95: The pagination loop in reconcile-backlog.sh can spin
forever when .data.node.items.pageInfo.hasNextPage is true but endCursor is
empty. After assigning cursor from the jq result, add a guard that breaks (or
otherwise stops pagination) if cursor is empty, so the loop does not re-fetch
the first page; use the existing hasNextPage/cursor logic in the same block to
locate the fix.
🪄 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: 4b8b4743-ed64-46a0-8c27-43f117a16c9c

📥 Commits

Reviewing files that changed from the base of the PR and between 8563923 and 2c4377e.

📒 Files selected for processing (7)
  • .github/scripts/add-to-project/add-issue-or-pr.sh
  • .github/scripts/add-to-project/classify-initiative.sh
  • .github/scripts/add-to-project/lib.sh
  • .github/scripts/add-to-project/reconcile-backlog.sh
  • .github/workflows/add-to-project-reconcile.yml
  • test/workflows/add-to-project/classify-initiative.bats
  • test/workflows/add-to-project/reconcile-backlog.bats

Comment thread .github/scripts/add-to-project/classify-initiative.sh
Comment thread .github/scripts/add-to-project/reconcile-backlog.sh Outdated
Comment thread .github/scripts/add-to-project/reconcile-backlog.sh
@don-petry
don-petry disabled auto-merge July 9, 2026 20:01
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 9, 2026
@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — fix-reviews (applied)

Changes committed and pushed.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 9, 2026
@don-petry
don-petry disabled auto-merge July 9, 2026 20:05
@don-petry
don-petry enabled auto-merge (squash) July 9, 2026 20:09
@don-petry
don-petry disabled auto-merge July 9, 2026 20:10
@don-petry
don-petry enabled auto-merge (squash) July 9, 2026 20:12
@don-petry
don-petry disabled auto-merge July 9, 2026 21:44
@don-petry
don-petry enabled auto-merge (squash) July 9, 2026 21:48
@don-petry
don-petry disabled auto-merge July 9, 2026 23:37
@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — waiting on PR blockers (intent: fix-reviews)

PR: #635
No changes were committed, but the PR still has blocking checks or reviews (failing or cancelled checks, or changes-requested reviews). The retry cron will re-attempt automatically. Next attempt after: 2026-07-10T00:08:46Z

@don-petry
don-petry enabled auto-merge (squash) July 9, 2026 23:38
@don-petry
don-petry disabled auto-merge July 10, 2026 00:13
@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — review-changes (applied)

Changes committed and pushed.

@sonarqubecloud

Copy link
Copy Markdown

@don-petry
don-petry disabled auto-merge July 10, 2026 00:18
@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — review-changes (no-changes)

No changes were needed for this PR.

@don-petry
don-petry enabled auto-merge (squash) July 10, 2026 00:20

@donpetry-bot donpetry-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated review — APPROVED ✓

Risk: MEDIUM
Reviewed commit: 5a6cc6650a910308528c004a53b61466a57fa03e
Review mode: triage-approved (single reviewer)

Summary

Fixes three defects that left the Initiatives board unclassified after issue un-gating (#608): (1) reconcile-backlog.sh now prefetches board membership once into an associative array so both hot paths skip per-item GraphQL round-trips, keeping the job under its (raised 20→30 min) timeout so classify actually runs; (2) set_item_single_select_value sends optionId with -f instead of -F, so all-numeric option ids are no longer type-coerced to JSON numbers and rejected by the String! parameter; (3) classify-initiative.sh gains per-item error isolation so one transient API failure no longer aborts an ~800-item sweep. Each fix has a matching bats regression test; the event path is explicitly gated off the new cache and unchanged.

Linked issue analysis

No formally linked closing issue. The PR body documents the operational failure (only 1 of ~840 board items classified) and its three root causes in detail, referencing #608 (issue un-gating) as the triggering context. The diff substantively addresses each documented root cause.

Findings

  • No blocking findings. Shell changes are sound: the cache lookup is gated by _atp_membership_ready (event path unchanged), prefetch_board_membership fails loudly (exit 75) on empty/inconsistent API responses rather than silently treating the board as empty, and per-item failure handling uses if ! guards that are safe under set -e.
  • The -f optionId fix is correct: gh api -F type-infers all-digit values to JSON numbers, which the String! GraphQL parameter rejects; ID! args safely stay -F since node ids carry non-numeric prefixes. Covered by a new bats test.
  • Prior CodeRabbit CHANGES_REQUESTED (commit 2c4377e6) raised 3 actionable comments; all were fixed in follow-up commits and every review thread on the PR is resolved. CodeRabbit's reviews on later commits are comment-only.
  • Workflow change is limited to timeout-minutes: 20 → 30 with an explanatory comment — no security-relevant surface (no trigger, permission, or injection changes).
  • Minor, non-blocking: package-lock.json "name" churned from pr-609 to pr-635 (empty packages; appears to be a generated artifact).
  • Secret scan: the run_secret_scanning MCP tool is not available in this environment; the gitleaks CI check passed, and the diff contains no credential-like content.

CI status

All required checks green at 5a6cc66: Shellcheck and bats, ShellCheck, Lint, CodeQL (actions), SonarCloud quality gate, npm audit, gitleaks secret scan, AgentShield, and Agent Security Scan all SUCCESS. Skipped checks (pnpm/cargo/pip/govulncheck audits, dependabot-automerge) are ecosystem-not-present skips; CodeRabbit check is NEUTRAL (comment-only).


Reviewed automatically by the PR-review agent (single-reviewer mode: fable 5). Reply if you need a human review.

@don-petry
don-petry merged commit ab3dc79 into main Jul 10, 2026
23 checks passed
@don-petry
don-petry deleted the fix/reconcile-prefetch-membership branch July 10, 2026 00:24
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.

3 participants