OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-bridge commits / 051f2ed

0.9.44: listBlobs must report WHY, never a bare InternalError
0.9.43 returned 500 InternalError with nothing in the log. The peer side was verified working over its UDS (9 CIDs, cursor paging), so the fault is the bridge->peer hop — and the route gave no way to tell which of three operator problems it was: a front with no live RPC channel, a peer too old to know the method, or a genuine fault. That is the same silence that hid the hpp OTA bug for four versions and the Mastodon Update bug behind a 2xx. A route that swallows its own reason is a route nobody can debug from prod. Catches the peer call, LOGS it with the error code, and answers 502 PeerUnavailable with the message; 501 stays for genuinely-absent support, and a store-level read failure is distinguished from both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
051f2edc5b73b25fe5cff443fa7e3a0572008075
by plankatron <fleeky@gmail.com> · 2026-08-07T02:31:41.000Z

Changed files (3) +29 -7

package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "atproto-bridge",
3 "version": "0.9.43",
3 "version": "0.9.44",
44 "description": "Stateless keyless atproto MEMBRANE \u2014 fronts self-custody hyper-proto-peers to bluesky over HTTP/WS, holds no keys and never signs. Bare-runnable. Deploy root / flat closure over the identity+repo+store+pds packages.",
55 "type": "module",
66 "main": "index.js",
pds/lib/server-bare.js +14 -3
……@@ -179,9 +179,20 @@ export function createPdsServer ({ peers = null, serverDid = null, availableUser
179179 case 'com.atproto.sync.listBlobs': {
180180 const front = await frontIfPeer(q.get('did'))
181181 if (!front) return notHere(res)
182 if (typeof front.listBlobs !== 'function') return sendErr(res, 501, 'MethodNotImplemented', 'peer does not serve list-blobs')
183 const r = await front.listBlobs({ limit: q.get('limit'), cursor: q.get('cursor') })
184 if (!r || r.skipped) return sendErr(res, 501, 'MethodNotImplemented', 'peer has no blob inventory')
182 if (typeof front.listBlobs !== 'function') return sendErr(res, 501, 'MethodNotImplemented', 'peer front too old to serve list-blobs')
183 // ⛔ Report why, never a bare InternalError. A peer with no live RPC channel, one too old to
184 // know the method, and a genuine fault are three different operator problems, and collapsing
185 // them into 500 is the same silence that hid the OTA bug for four versions.
186 let r
187 try {
188 r = await front.listBlobs({ limit: q.get('limit'), cursor: q.get('cursor') })
189 } catch (e) {
190 const msg = (e && e.message) || String(e)
191 console.error(JSON.stringify({ msg: 'listBlobs: peer call failed', did: q.get('did'), error: msg, code: e && e.code }))
192 return sendErr(res, 502, 'PeerUnavailable', 'peer could not answer list-blobs: ' + msg)
193 }
194 if (!r || r.skipped) return sendErr(res, 501, 'MethodNotImplemented', 'peer has no blob inventory' + (r && r.skipped ? ' (' + r.skipped + ')' : ''))
195 if (r.error) return sendErr(res, 502, 'PeerUnavailable', 'peer inventory read failed: ' + r.error)
185196 const body = { cids: r.cids || [] }
186197 if (r.cursor) body.cursor = r.cursor
187198 if (q.get('since')) body.sinceIgnored = true
pds/lib/server.js +14 -3
……@@ -127,9 +127,20 @@ export function createPdsServer ({ peers = null, serverDid = null, availableUser
127127 case 'com.atproto.sync.listBlobs': {
128128 const front = await frontIfPeer(q.get('did'))
129129 if (!front) return notHere(res)
130 if (typeof front.listBlobs !== 'function') return sendErr(res, 501, 'MethodNotImplemented', 'peer does not serve list-blobs')
131 const r = await front.listBlobs({ limit: q.get('limit'), cursor: q.get('cursor') })
132 if (!r || r.skipped) return sendErr(res, 501, 'MethodNotImplemented', 'peer has no blob inventory')
130 if (typeof front.listBlobs !== 'function') return sendErr(res, 501, 'MethodNotImplemented', 'peer front too old to serve list-blobs')
131 // ⛔ Report why, never a bare InternalError. A peer with no live RPC channel, one too old to
132 // know the method, and a genuine fault are three different operator problems, and collapsing
133 // them into 500 is the same silence that hid the OTA bug for four versions.
134 let r
135 try {
136 r = await front.listBlobs({ limit: q.get('limit'), cursor: q.get('cursor') })
137 } catch (e) {
138 const msg = (e && e.message) || String(e)
139 console.error(JSON.stringify({ msg: 'listBlobs: peer call failed', did: q.get('did'), error: msg, code: e && e.code }))
140 return sendErr(res, 502, 'PeerUnavailable', 'peer could not answer list-blobs: ' + msg)
141 }
142 if (!r || r.skipped) return sendErr(res, 501, 'MethodNotImplemented', 'peer has no blob inventory' + (r && r.skipped ? ' (' + r.skipped + ')' : ''))
143 if (r.error) return sendErr(res, 502, 'PeerUnavailable', 'peer inventory read failed: ' + r.error)
133144 const body = { cids: r.cids || [] }
134145 if (r.cursor) body.cursor = r.cursor
135146 if (q.get('since')) body.sinceIgnored = true