OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-store commits / 5a7dc49

overlay-repo-store: log byte-resolution failures instead of swallowing them
ensureLocal's fetch path was `catch { return null }` with no logging, so a record whose bytes never replicated was indistinguishable from any ordinary miss. That matters because this path GATES SIGNING: the forward-only sign gate skips while a converged pointer's bytes are unresolved, so a permanent miss silently stops a co-owner from signing at all — with no diagnosable cause anywhere. Found on the live fleet: wj62 has one record (app.bsky.feed.post/3mr6v6r2ofs22, authored by the other co-owner 75+ hours ago) whose pointer is linearized but whose bytes have never resolved on tabertron. The gate correctly refuses to sign — signing would emit a delete for a record that is live and served — but nothing recorded WHY the fetch failed, so the cause was unobtainable. The autobase replicates fine (indexedLength tracks, netRev advances) and cross-node fetch-by-oid demonstrably works (test/overlay-blob-store), so the failure is specific to that oid rather than a broken transport. Logs the reason once per oid (deduped — the resolve loop calls ensureLocal per record on every list()), distinguishing "no connected peer served this oid" from a thrown error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5a7dc4955e0918f29c59875a6f25ad3640131c09
by plankatron <fleeky@gmail.com> · 2026-07-25T02:27:26.000Z

Changed files (1) +21 -2

lib/overlay-repo-store.js +21 -2
……@@ -77,11 +77,30 @@ export class OverlayRepoStore {
7777 try {
7878 fs.mkdirSync(join(this._recordsDir, collection), { recursive: true })
7979 const r = await this._overlay.fetchFile(oid, { destPath: disk })
80 if (!r) return null
81 } catch { return null }
80 if (!r) { this._logFetchMiss(collection, rkey, oid, 'fetchFile returned null — no connected peer served this oid'); return null }
81 } catch (err) { this._logFetchMiss(collection, rkey, oid, (err && err.message) || String(err)); return null }
8282 return this.get(collection, rkey)
8383 }
8484
85 // Byte-resolution failures used to be swallowed entirely (`catch { return null }`), which made a
86 // record whose bytes never replicated indistinguishable from any ordinary miss. That matters because
87 // this path GATES SIGNING: the forward-only sign gate skips while a converged pointer's bytes are
88 // unresolved, so a permanent miss silently stops a co-owner signing at all, with no diagnosable
89 // cause. Observed on wj62 — one record unresolved for 75+ hours with nothing logged anywhere.
90 // Deduped per oid, because the resolve loop calls this once per record on every list().
91 _logFetchMiss (collection, rkey, oid, reason) {
92 if (!this._fetchMisses) this._fetchMisses = new Set()
93 const k = String(oid)
94 if (this._fetchMisses.has(k)) return
95 this._fetchMisses.add(k)
96 try {
97 console.error(JSON.stringify({
98 msg: 'overlay byte-resolution FAILED — pointer converged but bytes unavailable (blocks signing)',
99 did: this._did, key: `${collection}/${rkey}`, oid: k.slice(0, 16), reason
100 }))
101 } catch {}
102 }
103
85104 async get (collection, rkey) {
86105 const raw = await this.getRaw(collection, rkey)
87106 return raw ? raw.record : null