ci: auto-update OpenAPI snapshot on PRs#2521
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
Auto-updates the OpenAPI snapshot on PR branches so developers don't have to remember the pre-commit hook — CI regenerates it and pushes back if stale.
|
|
There was a problem hiding this comment.
Medium urgency. The workflow achieves its goal but has a few concerns: overly broad contents: write at workflow level, snapshot commits happening before CI validation, and a CI double-run on every snapshot update.
|
|
||
| permissions: | ||
| contents: read | ||
| contents: write |
There was a problem hiding this comment.
Security / least-privilege: contents: write is set at the workflow level, so it applies to every job — including create-agents-e2e which doesn't need write access. On push and merge_group events, no commit-back is needed either. Consider scoping this to a job-level permissions block on just the ci job, so other jobs and non-PR events retain contents: read.
| with: | ||
| ref: ${{ github.head_ref || github.ref }} |
There was a problem hiding this comment.
Checkout ref change affects non-PR events. github.head_ref is empty on push events, so this falls back to github.ref (e.g. refs/heads/main). The default checkout for push events uses the exact commit SHA, not the branch ref — so this subtly changes behavior and introduces a theoretical race where a newer commit lands on main between trigger and checkout. Safer to only override the ref for PRs:
| with: | |
| ref: ${{ github.head_ref || github.ref }} | |
| with: | |
| ref: ${{ github.event_name == 'pull_request' && github.head_ref || '' }} |
An empty string preserves the default checkout behavior for non-PR events.
| - name: Commit OpenAPI snapshot if changed | ||
| if: github.event_name == 'pull_request' | ||
| run: | | ||
| if git diff --quiet agents-api/__snapshots__/openapi.json 2>/dev/null; then | ||
| echo "OpenAPI snapshot is up to date" | ||
| else | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
| git add agents-api/__snapshots__/openapi.json | ||
| git commit -m "chore: update OpenAPI snapshot" | ||
| git push | ||
| echo "::notice::OpenAPI snapshot was auto-updated and committed. This CI run will continue with the updated snapshot." | ||
| fi |
There was a problem hiding this comment.
Snapshot committed even when CI fails. These steps run before Run CI checks, so a snapshot from broken code gets committed to the PR branch regardless of whether CI passes. If the intent is to only commit known-good snapshots, move these two steps after the CI checks step (or add if: ... && steps.ci-check.outcome == 'success').
| - name: Commit OpenAPI snapshot if changed | ||
| if: github.event_name == 'pull_request' | ||
| run: | | ||
| if git diff --quiet agents-api/__snapshots__/openapi.json 2>/dev/null; then | ||
| echo "OpenAPI snapshot is up to date" | ||
| else | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
| git add agents-api/__snapshots__/openapi.json | ||
| git commit -m "chore: update OpenAPI snapshot" | ||
| git push | ||
| echo "::notice::OpenAPI snapshot was auto-updated and committed. This CI run will continue with the updated snapshot." | ||
| fi |
There was a problem hiding this comment.
CI double-trigger. When git push adds a commit to the PR branch, the pull_request/synchronize event fires again, re-running the entire workflow. The second run finds the snapshot unchanged and terminates cleanly, but you still pay for a full redundant CI run (install, build, test). Adding [skip ci] to the auto-commit message is the simplest fix:
git commit -m "chore: update OpenAPI snapshot [skip ci]"
There was a problem hiding this comment.
PR Review Summary
(5) Total Issues | Risk: Medium
🟠⚠️ Major (4) 🟠⚠️
🟠 1) .github/workflows/ci.yml:84-103 Missing fork protection for auto-commit steps
Issue: The auto-commit steps will fail silently or produce confusing CI output when PRs come from forks. Fork PRs cannot push back to the head branch because GITHUB_TOKEN is read-only for fork PRs by default.
Why: This is inconsistent with the established pattern in auto-format.yml which explicitly guards against fork PRs. Without this guard, the workflow will execute the snapshot update step on fork PRs, fail at git push, and produce confusing error messages. If repository settings ever evolve to allow write tokens for forks, this becomes a potential code injection vector.
Fix: Add a conditional check to skip the auto-commit steps for fork PRs, matching the pattern already established in this repo:
- name: Update OpenAPI snapshot
if: |
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository
run: pnpm --filter @inkeep/agents-api openapi:update-snapshot
env:
ENVIRONMENT: test
CI: true
- name: Commit OpenAPI snapshot if changed
if: |
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository
run: |
# ... existing logicRefs:
🟠 2) .github/workflows/ci.yml:12-14 Workflow-level write permissions broader than necessary
Issue: The contents: write permission is set at the workflow level (line 13), which means it applies to all triggers (push, pull_request, merge_group) and all jobs (including create-agents-e2e), not just the PR snapshot steps that need it.
Why: This violates the principle of least privilege. The elevated permission grants unnecessary write access to: (1) CI runs on main after merge that don't need write access, (2) the create-agents-e2e job which only runs tests. If vulnerabilities are introduced in other steps or jobs are added later, they inherit write access unnecessarily.
Fix: Move the elevated permission to job-level for only the ci job, keeping the workflow default at contents: read:
permissions:
contents: read
actions: read
jobs:
ci:
permissions:
contents: write
runs-on: ubuntu-32gb
# ... rest of jobRefs:
🟠 3) .github/workflows/ci.yml:101 Git push without retry logic risks silent failures
Issue: The git push command has no retry mechanism. If the push fails due to a transient network issue or a concurrent push from auto-format.yml, the command will fail and CI will fail without clear indication of why.
Why: Both ci.yml and auto-format.yml workflows run on pull_request events and can push commits back to the PR branch. While each workflow has its own concurrency group, they are separate workflows that can run concurrently. The existing auto-format.yml workflow (lines 125-135) demonstrates a robust pattern with 3 retries, exponential backoff, and pull --rebase between attempts. This new code lacks that resilience.
Fix: Add retry logic similar to auto-format.yml:
for i in 1 2 3; do
if git push; then
break
fi
echo "Push failed, attempting pull --rebase and retry ($i/3)"
git pull --rebase origin "${{ github.head_ref }}" || exit 1
sleep $((i * 2))
doneRefs:
🟠 4) scope Race condition between ci.yml and auto-format.yml
Issue: Both workflows can run concurrently on the same PR and attempt to push commits, creating a race condition where one may fail with a non-fast-forward error.
Why: ci.yml uses concurrency group CI-${{ github.ref }} and auto-format.yml uses Auto Format-${{ github.ref }}. These are separate groups, so both workflows can run simultaneously. If auto-format.yml pushes a formatting commit while ci.yml is also trying to push the snapshot, one will fail. The retry logic in fix #3 would mitigate this, but it's worth being aware that these workflows can interact.
Fix: The retry logic suggested in issue #3 addresses this. Alternatively, consider unifying the concurrency groups across auto-commit workflows, or moving the snapshot update into the auto-format.yml workflow itself since they serve similar purposes (auto-fixing generated artifacts).
Refs:
🟡 Minor (1) 🟡
Inline Comments:
- 🟡 Minor:
ci.yml:97Missing branch existence check before push
💭 Consider (2) 💭
💭 1) .github/workflows/ci.yml:34 Checkout ref change affects all triggers
Issue: The checkout step now uses ref: ${{ github.head_ref || github.ref }} which changes behavior for all event types, not just PRs. On push events to main, github.head_ref is empty so it falls back to github.ref.
Why: This is likely correct behavior (explicit ref matching implicit), but the change has broader scope than the PR description suggests. Worth confirming main branch CI continues to work as expected.
Fix: Consider adding a note to the PR description acknowledging this affects all triggers, not just PRs.
💭 2) .github/workflows/ci.yml:99 Consider [skip ci] to prevent re-trigger loops
Issue: Pushing a commit back to the PR branch will re-trigger CI. While the git diff --quiet check should prevent an infinite loop (assuming snapshot generation is deterministic), adding [skip ci] to the commit message would make the intent explicit.
Why: The existing auto-format.yml doesn't use [skip ci] and works fine in practice, so this is likely not needed. However, if snapshot generation ever becomes non-deterministic (e.g., includes timestamps), this could cause repeated CI runs.
Fix: Optionally add [skip ci] to the commit message: git commit -m "chore: update OpenAPI snapshot [skip ci]"
🚫 REQUEST CHANGES
Summary: This PR introduces a useful automation for keeping OpenAPI snapshots in sync, but it needs a few defensive improvements before merging. The most critical issues are: (1) missing fork protection that's already established in auto-format.yml, (2) workflow-level permissions that are broader than necessary, and (3) missing retry logic that could cause flaky CI failures when racing with the auto-format workflow. These are all straightforward fixes that align with existing patterns in the repo.
Discarded (3)
| Location | Issue | Reason Discarded |
|---|---|---|
ci.yml:32 |
Actions pinned to mutable tags rather than SHAs | Pre-existing pattern across all workflows, not introduced by this PR |
ci.yml:17 |
Concurrency group may cancel in-progress runs | Pre-existing concurrency behavior, not changed by this PR |
| N/A | Security concerns about fork write tokens | The pull_request event already provides protection (GITHUB_TOKEN is read-only for forks by default); the fork protection suggestion is defense-in-depth, not critical security |
Reviewers (3)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-sre |
5 | 2 | 1 | 0 | 1 | 0 | 1 |
pr-review-devops |
5 | 2 | 1 | 0 | 0 | 0 | 2 |
pr-review-security-iam |
2 | 0 | 0 | 0 | 0 | 0 | 2 |
| Total | 12 | 4 | 2 | 0 | 1 | 0 | 5 |
Note: Security and DevOps findings on fork protection and permissions were merged into single findings (counted under DevOps/SRE in final output).
| if git diff --quiet agents-api/__snapshots__/openapi.json 2>/dev/null; then | ||
| echo "OpenAPI snapshot is up to date" | ||
| else | ||
| git config user.name "github-actions[bot]" |
There was a problem hiding this comment.
🟡 Minor: Missing branch existence check before push
Issue: If a PR is merged or closed while CI is running, the head branch may be deleted and git push will fail with a confusing error.
Why: The existing auto-format.yml workflow defensively checks branch existence before pushing (lines 120-123). This new code lacks that guard, leading to inconsistent error handling patterns across workflows.
Fix:
# Add before git push:
if ! git ls-remote --exit-code --heads origin "${{ github.head_ref }}" > /dev/null 2>&1; then
echo "::notice::Remote branch '${{ github.head_ref }}' no longer exists. Skipping push."
exit 0
fiRefs:
There was a problem hiding this comment.
PR Review Summary
(4) Total Issues | Risk: Medium
Re-review scoped to delta. This review covers only the changes since the previous review. The delta adds a branch existence check before pushing (commit
9d7d3935f).
✅ Delta Assessment
The new commit addresses Minor issue #5 from the previous review ("Missing branch existence check before push"). The implementation is correct:
- Uses the same
git ls-remote --exit-code --headspattern asauto-format.yml:120-123 - Correctly exits with
0to avoid failing CI when the branch is deleted - Uses
::notice::annotation for visibility - Variable quoting is correct
The TOCTOU (time-of-check-to-time-of-use) race between the check and push is acceptable — the window is milliseconds, and the worst case is a failed push which is the same outcome as not having the check at all.
No new issues found in the delta.
🕐 Pending Recommendations (4)
The following issues from the previous review remain unaddressed:
- 🟠
ci.yml:84-103Missing fork protection for auto-commit steps — fork PRs will fail atgit pushwith confusing errors - 🟠
ci.yml:12-14Workflow-levelcontents: writepermission broader than necessary — should be scoped to job-level - 🟠
ci.yml:108Git push without retry logic risks silent failures — race withauto-format.ymlcan cause non-fast-forward errors - 🟠
scopeRace condition betweenci.ymlandauto-format.yml— both can push simultaneously due to separate concurrency groups
🚫 REQUEST CHANGES
Summary: The delta is well-implemented and addresses the branch existence check concern. However, the 4 Major issues from the previous review remain unaddressed. The most critical are the missing fork protection (will cause confusing CI failures for external contributors) and the lack of retry logic (can cause intermittent failures when racing with auto-format.yml). These should be addressed before merging.
Reviewers (2)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-devops |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
pr-review-sre |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Total | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Note: Delta was minimal and both reviewers confirmed the implementation follows established patterns with no new issues.
There was a problem hiding this comment.
PR Review Summary
(0) Total Issues | Risk: Low
Re-review scoped to delta. This review covers only the changes since the previous review (commit
9d7d3935f→2dc86021f).
✅ Delta Assessment
The delta commit (2dc86021f ci: add fork protection, job-level perms, push retry logic) correctly addresses all 4 Major issues from the previous review:
| Previous Issue | Resolution | Verified |
|---|---|---|
| 🟠 Fork protection missing | Added github.event.pull_request.head.repo.full_name == github.repository to both step conditions |
✅ Matches auto-format.yml:17-19 |
| 🟠 Workflow-level write perms too broad | Changed workflow-level contents: read, added job-level contents: write on ci job only |
✅ Least-privilege principle applied |
| 🟠 No retry logic for git push | Added 3-retry loop with exponential backoff (sleep $((i * 2))) |
✅ Matches auto-format.yml:125-135 |
| 🟠 Race condition with auto-format.yml | Added branch existence check within retry loop | ✅ Same defensive pattern as auto-format.yml |
Implementation quality:
- Comment updated to explain fork protection:
# Auto-update OpenAPI snapshot on PRs (skip fork PRs since GITHUB_TOKEN is read-only) - Branch existence check runs before initial push AND within each retry iteration
- Notice annotations provide good visibility:
::notice::Remote branch '$PUSH_REF' was deleted during retry. Skipping. - Variable quoting is correct throughout (
"$PUSH_REF")
No new issues introduced by this delta.
💭 Consider (1) 💭
💭 1) ci.yml:114-126 Retry loop exits silently after exhausted retries
Issue: If all 3 push attempts fail, the script falls through with exit code 0 rather than failing the step.
Why: This matches the identical pattern in auto-format.yml:125-135, so it's consistent behavior. The rationale is likely that a failed snapshot push shouldn't block CI entirely—the snapshot will be updated on the next push. However, this does mean snapshot update failures can go unnoticed.
Refs: auto-format.yml:125-135 — same pattern
✅ APPROVE
Summary: All 4 Major issues from the previous review have been correctly addressed. The implementation follows established patterns from auto-format.yml and applies the principle of least privilege for permissions. This is ready to merge. 🎉
Reviewers (1)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-devops |
1 | 0 | 1 | 0 | 0 | 0 | 0 |
| Total | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
There was a problem hiding this comment.
PR Review Summary
(0) Total Issues | Risk: Low
Re-review scoped to delta. This review covers only the changes since the previous review (commit
2dc86021f→e6cfc7e6f).
✅ Delta Assessment
The delta commit (e6cfc7e6f ci: build deps before snapshot update, fail on exhausted retries) adds two improvements:
| Change | Assessment |
|---|---|
| Build dependencies step | ✅ Correct — uses --filter=@inkeep/agents-api... (Turborepo ellipsis syntax) to build the package and all its transitive dependencies before snapshot generation. Same conditional guards as other snapshot steps. |
| Explicit failure on exhausted retries | ✅ Addresses prior "Consider" item — tracks push_succeeded=false and explicitly exits with exit 1 + ::error:: annotation after 3 failed attempts. This is actually an improvement over auto-format.yml which falls through silently. |
Implementation quality:
- Build step uses consistent fork protection pattern
- Environment variables match existing steps (
ENVIRONMENT: test,CI: true) - Error message is actionable:
"Failed to push OpenAPI snapshot update after 3 attempts." - Uses
::error::annotation for visibility in GitHub Actions UI
No new issues introduced by this delta.
🧹 While You're Here (optional)
Consider backporting the explicit failure handling pattern to auto-format.yml for consistency — currently that workflow falls through silently after exhausted retries (lines 125-135). This is out of scope for this PR but worth a follow-up.
✅ APPROVE
Summary: The delta is clean and addresses the "Consider" item from the previous review. The build step ensures dependencies are available, and the explicit failure handling is an improvement over existing patterns. Ready to merge! 🚀
Reviewers (2)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-devops |
2 | 0 | 0 | 1 | 0 | 0 | 0 |
pr-review-sre |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Total | 2 | 0 | 0 | 1 | 0 | 0 | 0 |
Note: Both reviewers confirmed the delta follows established patterns and introduces no new issues. DevOps noted the error handling is an improvement over auto-format.yml.
There was a problem hiding this comment.
PR Review Summary
(0) Total Issues | Risk: Low
Re-review scoped to delta. This review covers only the changes since the previous review (commit
e6cfc7e6f→cd6663ed6).
✅ Delta Assessment
The delta commit (cd6663ed6 ci: add database URL env vars to build and snapshot steps) adds 4 lines — two database URL environment variables to each of the "Build dependencies for snapshot update" and "Update OpenAPI snapshot" steps:
INKEEP_AGENTS_MANAGE_DATABASE_URL: postgresql://appuser:password@localhost:5432/inkeep_agents
INKEEP_AGENTS_RUN_DATABASE_URL: postgresql://appuser:password@localhost:5433/inkeep_agents| Criterion | Assessment |
|---|---|
| Values consistent? | ✅ Identical to all other usages in the workflow (lines 169-170 in "Run CI checks", lines 341-342 and 347-348 in create-agents-e2e) |
| Security concern? | ✅ None — these are CI-specific localhost credentials for GitHub Actions service containers, not real credentials. Hardcoding localhost CI credentials is an established pattern in this repo. |
| Necessary? | ✅ Required for correctness — agents-api/src/env.ts requires these variables (they are not optional in the Zod schema). Without them, the build step would fail during env schema parsing. |
No new issues introduced by this delta.
✅ APPROVE
Summary: Clean delta that adds required database URL environment variables to the snapshot update steps. The values match the existing pattern used throughout the workflow, and are necessary for the env schema parsing to succeed. This PR is ready to ship! 🚀
Reviewers (1)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-devops |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Total | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Note: Delta was minimal (4 lines adding env vars). DevOps reviewer confirmed the values are consistent with existing patterns and necessary for correctness.
When the OpenAPI snapshot is out of date on a PR, CI now automatically regenerates it and commits the update back to the PR branch. The snapshot diff remains visible in the PR for review. Only runs on pull_request events, not on main. Closes PRD-6226
- Move contents:write to job-level (ci only), keep workflow default read - Skip snapshot steps on fork PRs (GITHUB_TOKEN is read-only for forks) - Add 3-retry push with pull --rebase, matching auto-format.yml pattern - Re-check branch existence between retries
- Add turbo build step for agents-api deps before running snapshot update (vitest needs built packages to resolve imports) - Fail the step explicitly if all 3 push retries are exhausted
Allow Claude code review to trigger on commits from github-actions[bot], such as the auto-updated OpenAPI snapshot.
cd6663e to
11a0746
Compare
|
A couple of notes: Snapshot gating: The snapshot update runs on every PR without path filtering. We could gate it to only run when files that affect the OpenAPI spec change (route files, openapi.ts, createApp.ts), but we'd need to update that list whenever new files start affecting the spec. Open to either approach - keeping it ungated is simpler and only adds ~5-15s. Claude review on bot commits: We discussed having all bot commits on PRs trigger Claude review (not just the current allow-list of inkeep-internal-ci and github-actions). Holding off on that broader change for now since it could increase review costs. Currently only the two bots we care about are allow-listed. |
- Move git identity (name/email) to job-level env vars - Extract branch check, retry, and push logic into scripts/ci-commit-and-push.sh (reusable by other workflows) - Replaces ~35 lines of inline bash with a single script call
|
Claude finished @vnv-varun's task in 1m 8s —— View job Code Review
Overall Assessment: Looks good to mergeThe PR is well-structured and iteratively addressed all prior review feedback. The extracted What's done well
Minor observations1.
2. Checkout ref: ${{ github.head_ref || github.ref }}On ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref }}This was flagged in earlier reviews too. Low risk but worth considering. 3. CI double-trigger consideration When the script pushes a snapshot commit back to the PR branch, it triggers a new git commit -m "$COMMIT_MSG [skip ci]"The tradeoff is that the snapshot commit itself wouldn't be CI-validated, but since it was generated during a CI run that validates everything, this seems acceptable. This was discussed in earlier reviews — up to you whether the ~5min saved per snapshot update is worth it. 4. Script isn't executable in the repo The script SummaryThe PR is solid. All major review feedback from previous rounds has been addressed. The reusable script is a clean pattern. The remaining items above are all minor/optional improvements that could be done in follow-ups. | |
Summary
Changes
ci.yml (OpenAPI snapshot auto-update):
contents: writepermission on thecijob only (workflow default staysread)github.head_refon PRs so we can push back to the branchopenapi:update-snapshoton PRs (skips fork PRs)claude-code-review.yml:
github-actions[bot]to the bot allow-list so review triggers on automated commitsWhy
Developers sometimes skip or miss the pre-commit hook that auto-updates the OpenAPI snapshot, causing CI to fail. This catches that case automatically. The snapshot diff is still visible in the PR for reviewers to inspect.
Running Claude review on bot commits means automated changes (like the snapshot update) still get reviewed.
Closes PRD-6226
Tested
Verified end-to-end on this PR: pushed an intentionally stale snapshot (added a dummy field), CI detected the diff, regenerated the snapshot, and committed the fix back as github-actions[bot] (commit bdefee5). The dummy field was correctly removed.
Test plan