Runbook: stale upload reconciliation
Object storage and the SQL catalog cannot share one transaction. A client that streams bytes but never calls back to commit leaves a file_versions row stuck in upload_state = 'pending' and a possibly-orphaned object in storage. Neither state is ever readable — tv file get and search only ever see committed versions — but stale pending rows and orphaned objects should still be found and cleaned up periodically.
Implementation status: implemented and exposed to operators.
tv admin reconcilewires the administrator-gatedpublic.reconcile_stale_file_uploadsRPC to a command and cross-checks each result against a live storage listing, so the report distinguishes a genuine orphaned object from a version whose upload never reached storage. The RPC is also exercised directly intests/integration/cli-files-lifecycle.test.mjsandtests/security/cli-files-security.test.mjs.
Detect stale pending uploads
Run tv admin reconcile as an admin/global_admin account (the command checks the caller's role itself and refuses a user-role token with FORBIDDEN):
tv admin reconcile [--stale-after <seconds>] [--json]
--stale-after is the age threshold in seconds (default 3600, one hour); only pending rows older than that are reported. The result is a read-only report, never a mutation:
{
"schemaVersion": 1,
"command": "admin reconcile",
"stalePending": [
{
"fileId": "...",
"versionId": "...",
"storageKey": "...",
"createdAt": "2026-08-20T12:00:00Z",
"objectExistsInStorage": true
}
],
"correlationId": "..."
}
stalePending is ordered oldest-first and scoped to the caller's own organization. An empty array means nothing needs attention. objectExistsInStorage is the per-entry storage cross-check the command performs automatically: true means an object actually landed in the bucket; false means the upload never reached storage.
Clean up
- For each
storageKeywithobjectExistsInStorage: true, the upload's commit call never arrived (client crash, network failure, or a completed-but-uncommitted upload). The object is orphaned — safe to delete directly from storage once you've confirmed no in-flight client is still working on it (comparecreatedAtagainst your--stale-afterthreshold; the command already only reports rows past that age). - For each
storageKeywithobjectExistsInStorage: false, the upload never got as far as storage. The pending row itself is inert — it was never readable and never will be — but if you want it removed for hygiene rather than left as a permanently-pending row, that requires a direct, audited SQL statement againstfile_versions, since no RPC exists to fail/cancel a pending version out-of-band today (fail_file_uploadrequires the original uploader's own live session, not an administrator override). - Record what you did (which keys, which rows, why) outside
tv— there is no CLI audit event for this manual cleanup path yet, since it bypasses the RPC surface entirely. See Incident investigation for how correlation IDs tie this back to the original failed upload attempt if the client reported one.
Cadence
Run this as a scheduled check (daily is reasonable for MVP scale) rather than only reactively — --stale-after should comfortably exceed your largest expected upload's realistic duration plus retry window, so a still-in-flight legitimate upload is never mistaken for orphaned.
Continue with Restore from archive or Incident investigation.