fix(pr-fix): keep the item URL as the PR URL when CI evidence re-enqueues it - #1117
Conversation
…eues it
The item URL is identity: metadataPatch no longer carries url, so a
CI-failure re-enqueue can never flip it to a job URL. url is set at first
enqueue and backfilled only while empty. Ingestion now passes the PR URL
as the event url for check_run events; the job URL moves to checkRunUrl
and stays evidence in the feedback text ("Full log:" line).
Closes #1098
|
Status: ready for human review. All 13 CI checks are green (Tests, Coverage, Database integration, Typecheck, Lint, Build, Docker Build x2, npm audit, smoke, review, workflow lint). Independent review pass ran before push; its follow-ups (webhook job-URL fallback replaced with an empty-string backfill path, |
There was a problem hiding this comment.
AI Automated Review (incremental)
Incremental review: reviewed the changes since the last managed review; unresolved findings from that review are carried forward.
Analysis engine: glm-5.3-flash@https://litellm.jory.dev/v1 (openai) — routed smart (risk match: db_or_migration_changes)
Recommendation: approve. The delta correctly completes the PR 1098/PR 1118 fix: a targeted, well-scoped data-repair migration plus the enqueue-time heal guard and tests for both. No data-loss risk identified; CI (including the Database migrations check) is green.
Change-by-change findings
prisma/migrations/20261004000000_repair_pr_fix_item_urls/migration.sql(new) — Single targetedUPDATEthat rewritesurlto the PR API URL derived fromrepo+pronly where the stored value matches the Actions run/job URL shape (^https://github\.com/[^/]+/[^/]+/actions/runs/). The WHERE clause is anchored and narrow; no rows are deleted, and legitimate API-format, web/pull/, and NULL URLs are untouched (matching the regex semantics ofACTIONS_RUN_URLinpr-fix-queue.ts). This satisfies the must_check review migration for data loss risk: the only mutation is replacing a known-poisoned identity value with the correct one, which is the intent of PR 1118. One gap (minor): the regex — likeisActionsRunUrl— does not match the legacy check-runhtml_urlshapehttps://github.com/<org>/<repo>/runs/<id>, which is precisely the shape the old webhook path stored (this PR's own updated fixture uses.../runs/321). A row poisoned with that shape stays frozen by the write-once guard and is skipped by the migration. Issue PR 1118 scopes the repair to the Actions run/job shape and the known poisoned example matches it, so this is an edge case, not a blocker — but the guard and migration should ideally cover both shapes or the discrepancy should be documented.src/lib/pr-fix-queue.ts— The heal logic is correct:(!existing.url || isActionsRunUrl(existing.url)) && itemUrlFromInput(input)backfills only when the stored value is empty or a known-poisoned job URL, anditemUrlFromInputprevents a job URL from ever being written on create or backfill (legacy callers included). Theurlremoval frommetadataPatchis preserved. Tests added in the delta cover poisoned-row repair on next enqueue, no-touch of a legitimate stored URL, and never storing a job URL on create or re-enqueue — matching the tests PR 1118 asked for.src/lib/pr-fix-queue.test.ts(delta) — Three new tests as above; they pin the write-once invariant with observable enqueue/read assertions.src/app/api/pr-followup/{sync,webhook}/route.ts+ ingestion (from the earlier commits, unchanged in this delta) —urlnow always carries the PR URL and the job URL moves tocheckRunUrl→ "Full log:" feedback evidence, with a legacy-shape fallback. Webhook test covers the PR-without-url→""case.
Must-check items
- Review migration for data loss risk — Done (above). The migration is a narrow, idempotent-shaped repair; the only overwritten values are rows whose URL is already known-bad, and the replacement is derivable from
repo+prper the issue. - Test migration on a copy of production schema — The CI "Database migrations" check passed on the head SHA, confirming the migration applies cleanly. The PR body additionally claims a manual Postgres verification ("only the job-URL row changed; API URLs, web
/pull/URLs and NULLs are untouched"); this is self-reported and I could not independently reproduce it, but it is consistent with the regex semantics I verified against the diff.
Standards Compliance
- Lint/typecheck block CI and must pass (AGENTS.md): CI shows Typecheck and Lint success on the head commit — satisfied.
- Tokens/secrets never logged or persisted: no secrets or token material appear in the diff — satisfied.
- Prisma conventions: the migration follows the existing
prisma/migrations/<timestamp>_<name>/migration.sqlpattern used by 27 prior migrations; no schema change, so noschema.prismadrift. - Validation before DB operations: enqueue input validation is unchanged; the migration's
repo/prvalues come from the database and the host in the constructed URL is fixed, so this is data-integrity only, not an injection or SSRF surface.
Linked Issue Fit
- PR 1098 (keep the item URL as the PR URL when CI evidence re-enqueues): fully implemented — write-once guard, job URL relocated to evidence/feedback, and the issue's named acceptance test (review enqueue then CI-failure enqueue; URL stays the PR URL, job URL in evidence) exists in
pr-fix-queue.test.ts. - PR 1118 (repair already-poisoned rows): implemented as a one-time SQL migration plus the enqueue-time heal, with the requested tests for poisoned-row repair and no-touch of legit URLs. The issue suggested "a script or backfill on the update path"; a migration is a reasonable equivalent for a one-time normalization and is consistent with how this repo ships data repairs (cf.
20260516010000_reconcile_lane_classification,20260824000000_repair_current_lane_indexes).
Tool Harness Findings
8 tool calls ran. repo_contents for the new migration file and the direct gh_api contents fetch both failed (404/preflight), but the migration's full content was verified via the PR files API patch and the incremental delta, which agree byte-for-byte. pr-fix-queue.ts and .github/ai-review-rules.md were read successfully; PR PR 1117 metadata, the files list, and issues PR 1098/PR 1118 were fetched successfully. The per-rule standards file confirms the review tone policy (flag only real defects; prefer approve for reasonable PRs).
Unknowns or Needs Verification
- The manual Postgres verification of the migration's row selection is self-reported in the PR body; the CI migration check confirms applicability but not row-level selection on production data. If the deployment's poisoned rows include the legacy
/runs/<id>shape (see minor finding), they will remain unhealed — worth a quick production query (SELECT count(*) FROM "PrFixQueueItem" WHERE "url" ~ '^https://github\.com/[^/]+/[^/]+/runs/') before or after deploy. - Downstream consumers of
pullRequest.urlreceive either API-format or web-format PR URLs depending on row history (the migration writes API format; older legitimate rows may hold web format). Both resolve to the same PR and the codebase already handles both shapes in tests, but no corpus evidence pins every consumer.
Sources: PR PR 1117 diff and files API (head ad33042b), issues PR 1098 and PR 1118, .github/ai-review-rules.md, AGENTS.md, CI check results for ad33042b, repository migration history.
| -- URL (#1118). The item URL is identity and is now write-once, so these rows | ||
| -- would otherwise keep the job URL. repo + pr already identify the PR. | ||
| UPDATE "PrFixQueueItem" | ||
| SET "url" = 'https://api.github.com/repos/' || "repo" || '/pulls/' || "pr" |
There was a problem hiding this comment.
Minor: Both the migration's WHERE regex and isActionsRunUrl only match the /actions/runs/ shape, so a row poisoned with the legacy check-run html_url shape (https://github.com///runs/ — the exact shape the old webhook path stored, per this PR's own updated fixture) is neither repaired nor treated as empty by the heal guard.
Automated finding from AI PR review.
| return typeof url === "string" && ACTIONS_RUN_URL.test(url); | ||
| } | ||
|
|
||
| /** The item URL an enqueue may write: never a CI job URL (#1098, #1118). */ |
There was a problem hiding this comment.
Info: itemUrlFromInput blocklists only the Actions-run URL shape rather than positively validating the URL matches the item's repo+pr, so any other non-PR URL from an ingestion caller could still become the write-once identity; acceptable given callers are internal, but worth noting.
Automated finding from AI PR review.
| -- Repair PR-fix item URLs that the pre-#1098 ingestion overwrote with a CI job | ||
| -- URL (#1118). The item URL is identity and is now write-once, so these rows | ||
| -- would otherwise keep the job URL. repo + pr already identify the PR. | ||
| UPDATE "PrFixQueueItem" |
There was a problem hiding this comment.
Info: The job-URL pattern is duplicated between the migration SQL and ACTIONS_RUN_URL in pr-fix-queue.ts with no test asserting the two predicates stay in sync; consider a comment cross-reference on both sides.
Automated finding from AI PR review.
Closes #1098
Closes #1118
Problem
metadataPatchinsrc/lib/pr-fix-queue.tsoverwrote a queued item'surlon every re-enqueue, and CI-failure enqueues passed the failing job URL. The item's URL flipped between the PR URL and a CI job URL, andnext-taskhanded the flipped value out aspullRequest.urlviacreateFollowupPrTask.Fix
Two layers, per the issue and grooming guidance:
src/lib/pr-fix-queue.ts) —urlis removed frommetadataPatch(it still refreshesissue/branch/title/headSha/author). The item URL is now set at first enqueue and backfilled only while empty (null/""); no re-enqueue — fresh attempt or otherwise — can overwrite it.src/lib/pr-followup-ingestion.ts+ bothpr-followuproutes) —PrFollowupEvent.urlnow always means the PR URL.check_runevents carry the job URL in a new optionalcheckRunUrl, which feeds the existing "Full log:" line in the feedback text (evidence, where job links belong). The webhook builder uses the associated pull request's API URL, falling back to""(never a job URL, which the guard would freeze); the next sync enqueue backfills the real PR URL.ingestCheckRunEventacceptscheckRunUrland documents the contract.Tests
urlstays the PR URL whilefeedback/evidenceKeysstill append (the job URL shows up in the evidence).check_runwithcheckRunUrl→ item URL is the PR URL and the job URL is in the "Full log:" feedback; legacy shape (nocheckRunUrl) keeps the old behavior via fallback.url→ eventurlis"", job URL incheckRunUrl.Full suite green locally: 3491 passed / 16 skipped / 2 todo; lint and typecheck clean (one pre-existing unrelated lint warning). CI: all 13 checks green.
Notes
20261004000000_repair_pr_fix_item_urlsrewrites existing job-URL rows to the PR URL derived fromrepo+pr. Verified against Postgres: only the job-URL row changed; API URLs, web/pull/URLs and NULLs are untouched.