Skip to content

ci(guardrails): batch conditional source reads - #6780

Merged
cv merged 3 commits into
NVIDIA:mainfrom
HOYALIM:codex/codebase-growth-guardrails-retry
Jul 13, 2026
Merged

ci(guardrails): batch conditional source reads#6780
cv merged 3 commits into
NVIDIA:mainfrom
HOYALIM:codex/codebase-growth-guardrails-retry

Conversation

@HOYALIM

@HOYALIM HOYALIM commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

The changed-test conditional guard previously fetched base and head contents one file at a time and failed the whole check on a single transient GitHub API error. This change batches trusted blob reads through GraphQL and retries transient failures while preserving the existing counting policy and REST fallback.

For a PR with 11 changed test files, the normal request shape drops from 23 GitHub API calls (one file-list request plus 22 content requests) to 3 (one file-list request plus two blob batches), an approximately 87% reduction.

Changes

  • Batch up to 25 base or head blobs per GraphQL request, with fork head reads kept repository-scoped and passed as GraphQL variables.
  • Retry network failures, HTTP 408/425/429/5xx responses, and transient GraphQL errors up to four times with exponential backoff and jitter.
  • Fall back to the retried REST content endpoint only for truncated GraphQL blobs.
  • Add workflow-level regression coverage for batching and a transient HTTP 502 recovery while retaining all existing conditional-counting cases.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification: This changes an internal CI guardrail implementation without changing its policy or user-facing interface.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: Author review confirmed the pull_request_target job still executes only trusted base-branch code, validates repository names, sends PR-controlled paths as GraphQL variables, and performs read-only GitHub API operations.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Verification

  • PR description includes the DCO sign-off declaration and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run check:diff passed when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — npx vitest run --project integration test/codebase-growth-guardrails-conditionals.test.ts (11 passed)
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result:
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Ho Lim subhoya@gmail.com

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability and accuracy of codebase growth guardrails by preloading changed-file contents in batches before counting.
    • Added retry handling for transient API/GraphQL failures to reduce incorrect failures.
    • Added a safe fallback to alternate content retrieval when GraphQL content is missing or truncated.
  • Tests
    • Added coverage for batched content fetching behavior.
    • Added coverage verifying transient GraphQL failures are retried and that fallback is used when GraphQL indicates truncated content.

Copilot AI review requested due to automatic review settings July 13, 2026 18:43
@copy-pr-bot

copy-pr-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

E2E Advisor Recommendation

Required E2E: None
Optional E2E: None

Workflow run

Full advisor summary

E2E Recommendation Advisor

Failed: Could not parse JSON from advisor output; see /home/runner/work/NemoClaw/NemoClaw/artifacts/e2e-advisor/e2e-advisor-raw-output.txt

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The conditional guardrail workflow now retrieves changed-file content through retryable, batched GraphQL requests with REST fallback, counts prefetched base and head text, and adds tests for batching, retries, and truncated-blob fallback.

Changes

Conditional scanning guardrail

Layer / File(s) Summary
Retryable content retrieval
.github/workflows/codebase-growth-guardrails.yaml
API helpers retry transient failures, batch GraphQL blob queries, reject binary content, and fall back to REST retrieval for truncated or missing text.
Prefetched conditional counting
.github/workflows/codebase-growth-guardrails.yaml
The scanner deduplicates base and head paths, fetches both content batches in parallel, and counts conditionals from prefetched text.
Guardrail behavior tests
test/codebase-growth-guardrails-conditionals.test.ts
The harness mocks GraphQL batching, transient failures, and truncation, with assertions for request batching, retries, and REST fallback.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GuardrailTests
  participant ConditionalScanningScript
  participant GitHubGraphQLAPI
  participant GitHubRESTContentsAPI
  GuardrailTests->>ConditionalScanningScript: Execute conditional scan
  ConditionalScanningScript->>GitHubGraphQLAPI: Fetch base and head blobs with retries
  GitHubGraphQLAPI-->>ConditionalScanningScript: Return text or fallback metadata
  ConditionalScanningScript->>GitHubRESTContentsAPI: Retrieve truncated or missing content
  GitHubRESTContentsAPI-->>ConditionalScanningScript: Return decoded text or null
  ConditionalScanningScript-->>GuardrailTests: Report conditional totals and status
Loading

Suggested reviewers: cv

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: batching guardrail source reads to reduce API requests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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

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

Signed-off-by: Ho Lim <subhoya@gmail.com>
@HOYALIM
HOYALIM force-pushed the codex/codebase-growth-guardrails-retry branch from 203b942 to 26e5f1f Compare July 13, 2026 18:50

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

🧹 Nitpick comments (2)
test/codebase-growth-guardrails-conditionals.test.ts (1)

216-234: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Retry test asserts a fetch-ordering detail rather than only the retry outcome.

expect(result.stderr).toContain("retry: graphql NVIDIA/NemoClaw@base-sha attempt 1 failed") only holds because the source's Promise.all([fetchBlobs(REPO, BASE_SHA, ...), fetchBlobs(HEAD_REPO, HEAD_SHA, ...)]) evaluates the base call first and the mock fetch has no internal await, so the single simulated failure deterministically lands on the base request. That's correct today, but it couples the test to an internal call-ordering detail rather than purely the retry contract (transient failure → automatic recovery, exit 0). A regex that doesn't pin the repo/sha (e.g. /retry: graphql .* attempt 1 failed/) would still prove the retry-and-recover behavior without breaking if the base/head fetch order or labeling changes.
Per path instructions, tests should "Prefer observable outcomes... over ... mock-call assertions" — the sha-specific match leans toward locking in an implementation detail.

🔧 Suggested loosening of the assertion
-    expect(result.stderr).toContain("retry: graphql NVIDIA/NemoClaw@base-sha attempt 1 failed");
+    expect(result.stderr).toMatch(/retry: graphql \S+ attempt 1 failed/);
🤖 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 `@test/codebase-growth-guardrails-conditionals.test.ts` around lines 216 - 234,
Loosen the stderr assertion in the “retries a transient GraphQL failure” test to
match the retry message and attempt number without requiring the specific
repository or SHA. Keep the existing exit-status and request-count assertions so
the test continues to verify transient-failure recovery without depending on
fetch ordering.

Source: Path instructions

.github/workflows/codebase-growth-guardrails.yaml (1)

397-459: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Second copy of the entire retry/GraphQL batching stack — extract to a shared script instead of duplicating.

withRetry, requestJson, getJson, graphql, splitRepoName, buildBlobQuery, fetchBlobs, and getContentViaRest here are near-verbatim copies of the same helpers already implemented in the "stay within size budget" step above (lines 130-292). This doubles the maintenance surface for ~150 lines of retry/batching logic and has already produced drift between the two copies:

  • Step 1's graphql() label uses variables?.owner ?? ""}/${variables?.name ?? ""}@${variables?.oid ?? ""} (line 204); this copy uses variables.owner}/${variables.name}@${variables.oid} (line 442) without the optional-chaining/fallback.
  • This copy's getContentViaRest adds an if (!file) return null; guard (line 470) that step 1's equivalent (line 272) doesn't have.

Neither drift is currently a live bug (both are always called with a defined variables/file), but it shows the two copies are already silently diverging, which is exactly the risk of hand-duplicated infra code. Consider factoring these helpers into a single checked-in script (e.g. scripts/ci/github-api-helpers.mjs) that both steps invoke, so retry/backoff and batching semantics stay in one place.

Also applies to: 469-529

🤖 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 @.github/workflows/codebase-growth-guardrails.yaml around lines 397 - 459,
The workflow contains a duplicated retry, GraphQL, batching, and content-fetch
implementation that should be centralized. Extract withRetry, requestJson,
getJson, graphql, splitRepoName, buildBlobQuery, fetchBlobs, and
getContentViaRest into one checked-in shared script, then update both workflow
steps to invoke or import that implementation and remove the duplicate inline
helpers. Preserve the existing retry/backoff, batching, optional-value handling,
and missing-file behavior consistently across both callers.
🤖 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.

Nitpick comments:
In @.github/workflows/codebase-growth-guardrails.yaml:
- Around line 397-459: The workflow contains a duplicated retry, GraphQL,
batching, and content-fetch implementation that should be centralized. Extract
withRetry, requestJson, getJson, graphql, splitRepoName, buildBlobQuery,
fetchBlobs, and getContentViaRest into one checked-in shared script, then update
both workflow steps to invoke or import that implementation and remove the
duplicate inline helpers. Preserve the existing retry/backoff, batching,
optional-value handling, and missing-file behavior consistently across both
callers.

In `@test/codebase-growth-guardrails-conditionals.test.ts`:
- Around line 216-234: Loosen the stderr assertion in the “retries a transient
GraphQL failure” test to match the retry message and attempt number without
requiring the specific repository or SHA. Keep the existing exit-status and
request-count assertions so the test continues to verify transient-failure
recovery without depending on fetch ordering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a806f33b-8555-40b4-af35-f9179fbd86d7

📥 Commits

Reviewing files that changed from the base of the PR and between 206223b and 203b942.

📒 Files selected for processing (2)
  • .github/workflows/codebase-growth-guardrails.yaml
  • test/codebase-growth-guardrails-conditionals.test.ts

Signed-off-by: Ho Lim <subhoya@gmail.com>
@wscurran wscurran added area: ci CI workflows, checks, release automation, or GitHub Actions chore Build, CI, dependency, or tooling maintenance labels Jul 13, 2026
@wscurran

Copy link
Copy Markdown
Contributor

✨ Thanks for the optimization, @HOYALIM. Batching GraphQL blob reads and adding retries should make the guardrails more reliable and efficient. Ready for maintainer review.

@wscurran wscurran added v0.0.82 area: ci CI workflows, checks, release automation, or GitHub Actions and removed area: ci CI workflows, checks, release automation, or GitHub Actions chore Build, CI, dependency, or tooling maintenance labels Jul 13, 2026
@cjagwani cjagwani self-assigned this Jul 13, 2026
Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>

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

🧹 Nitpick comments (2)
test/codebase-growth-guardrails-conditionals.test.ts (2)

92-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No REST-call counter to prove fallback is conditional on truncation.

REST content is pre-mocked for every input.contents entry (94-96), but nothing tracks how many times REST (contentsUrl) is actually invoked (only MOCK_GRAPHQL_REQUESTS is tracked, Lines 116-117, 120). This means a bug where the guard always falls back to REST regardless of isTruncated — instead of only when GraphQL reports truncation — would still pass the truncated-blob test (261-280) and the batching test, since both scenarios yield identical observable results.

Add a MOCK_REST_REQUESTS counter (mirroring graphqlRequests) and assert it's 0 in the non-truncated batching test and 1 here, to actually exercise the truncation-conditioned branch rather than just its side effect.

As per path instructions, "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

Also applies to: 261-280

🤖 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 `@test/codebase-growth-guardrails-conditionals.test.ts` around lines 92 - 141,
Add REST request counting to the generated fetch mock alongside graphqlRequests,
incrementing it only when the requested URL matches contentsUrl. Emit the count
on process exit as MOCK_REST_REQUESTS, then update the non-truncated batching
test to assert zero REST requests and the truncated-blob test to assert exactly
one, ensuring the truncation-dependent fallback is exercised.

Source: Path instructions


92-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

High-complexity inline mock script.

This block builds an entire mock GraphQL/REST server as a string template embedded in another string template, with nested regex/parsing logic (Lines 118-141). It's dense and hard to follow/modify. As per coding guidelines, **/*.{ts,tsx,js,jsx} should "Keep function complexity low." Consider extracting the alias-parsing/response-building logic into small named helper functions (even if they still get serialized into the wrapper string) to improve readability.

🤖 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 `@test/codebase-growth-guardrails-conditionals.test.ts` around lines 92 - 141,
The generated wrapper in the test contains overly dense inline GraphQL mock
logic. Refactor the wrapper construction around the
`graphqlFailuresRemaining`/`global.fetch` script by extracting alias parsing and
GraphQL response-building into small named helper functions, ensuring those
helpers are included in the generated script and preserving the existing
request, failure, and response behavior.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@test/codebase-growth-guardrails-conditionals.test.ts`:
- Around line 92-141: Add REST request counting to the generated fetch mock
alongside graphqlRequests, incrementing it only when the requested URL matches
contentsUrl. Emit the count on process exit as MOCK_REST_REQUESTS, then update
the non-truncated batching test to assert zero REST requests and the
truncated-blob test to assert exactly one, ensuring the truncation-dependent
fallback is exercised.
- Around line 92-141: The generated wrapper in the test contains overly dense
inline GraphQL mock logic. Refactor the wrapper construction around the
`graphqlFailuresRemaining`/`global.fetch` script by extracting alias parsing and
GraphQL response-building into small named helper functions, ensuring those
helpers are included in the generated script and preserving the existing
request, failure, and response behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 88a780fe-314d-4463-ae01-bbaf7fa13a99

📥 Commits

Reviewing files that changed from the base of the PR and between 7a7bb7f and 5674f2c.

📒 Files selected for processing (1)
  • test/codebase-growth-guardrails-conditionals.test.ts

@cv
cv merged commit 70c6d7e into NVIDIA:main Jul 13, 2026
41 of 43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ci CI workflows, checks, release automation, or GitHub Actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants