Skip to content

fix(db): visual_capture_unobtainable_sha is written but never mapped, so #9881's degrade has never fired #10270

Description

@JSONbored

The bug

visual_capture_unobtainable_sha is written but never read back. The row→record mapper for a cached pull request does not map the column, so pr.visualCaptureUnobtainableSha is undefined at every reader, and captureUnobtainable is permanently false.

stage file state
column src/db/schema.ts:507 present
write markPullRequestVisualCaptureUnobtainable, src/db/repositories.ts:4739 works
clear src/db/repositories.ts:4699 works
row → record mapper src/db/repositories.ts:7505-7507 field is absent
read src/queue/processors.ts:3542 and :12612 always undefined === headShafalse

Its two siblings written by the very same code path, visualCaptureSatisfiedSha and visualCaptureRetryPendingSha, are both mapped on the adjacent lines. This one was missed.

Why TSC did not catch it

PullRequestRecord.visualCaptureUnobtainableSha is declared optional (src/types.ts:778):

visualCaptureUnobtainableSha?: string | null | undefined;

A required field would have made the mapper a compile error the moment the column was added. Optional makes "never populated" indistinguishable from "populated with undefined", so nothing anywhere fails.

Correction to an earlier draft of this issue: all three visualCapture* fields are optional on this type, not just this one — visualCaptureSatisfiedSha (:774) and visualCaptureRetryPendingSha (:785) are ?: as well. So optionality is not what distinguishes the broken field from its working siblings; the other two were simply mapped and this one was not. That makes the class wider than "one field was declared wrong": every one of these three could have been missed with no compiler signal, and two happened not to be.

Consequence

#9881 exists to stop the screenshot-table gate closing a contributor PR over evidence the bot is structurally unable to produce — a repo with no preview deployment at all. That degrade is gated on captureUnobtainable, so it has never fired. A contributor on such a repo gets the undegraded close, which they cannot reopen.

Two follow-ups were then built on top of the same signal and are inert for the same reason:

Evidence (Orb, 2026-07-31)

rows_with_unobtainable | rows_with_satisfied | total
                     1 |                   8 | 12863

github_app.screenshot_table_close_degraded_capture_unobtainable, last 30 days: 0

One PR row carries the mark, so the write path demonstrably works. Zero degrade events in 30 days, so no reader has ever acted on it.

Fix

  1. Map the column in the pull-request record mapper, beside its two siblings.
  2. Make the field required on PullRequestRecord, matching visualCaptureSatisfiedSha and visualCaptureRetryPendingSha, so the next column added to this write path cannot repeat this silently.
  3. Regression test: write the mark, read the record back, assert the gate degrades. test/unit/queue-3.test.ts has the screenshot-table gate (#9881) cases to sit beside.

Worth checking in the same pass

Whether any OTHER optional field on PullRequestRecord is written to the DB but missing from the mapper. The audit is mechanical: every pullRequests column in schema.ts that some .set({...}) writes should appear in the mapper. A scripts/check-*.ts guard over that pairing would close the class rather than this instance — the same shape as check-focus-manifest-fields.ts.

How this was found

While covering the visual-capture branches for #10227: a test asserted visualCaptureUnobtainableSha on the record after driving a real previewUnobtainable capture through the pipeline, and read undefined while the DB column held the value.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions