Skip to content

feat(attachments): report unserved attachments and audit them hourly - #79

Closed
gmaclennan wants to merge 3 commits into
fix/attachments-proxy-errorsfrom
feat/sentry-attachment-audit
Closed

gmaclennan wants to merge 3 commits into
fix/attachments-proxy-errorsfrom
feat/sentry-attachment-audit

Conversation

@gmaclennan

@gmaclennan gmaclennan commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Today the only sign that an attachment is partly synced to the cloud is a partner's connector failing downstream: the server logs to stdout and nothing else, so a 404, a 500 with UNKNOWN_ERROR or a 502 from the attachments route goes unnoticed until someone complains.

This adds optional Sentry reporting, on only when SENTRY_DSN is set (@sentry/node is imported on demand, so a deployment without a DSN does not pay for it). The attachments route in src/routes.js now sends one attachment.nonok event for every response it could not serve as a 200, tagged with the project, the first seven characters of the drive id, type, variant, the HTTP status, a coarse class (missing for an upstream 404, incomplete when core resets or truncates, error otherwise) and core's own error code where it sent one; the fetch cause (UND_ERR_SOCKET and the like) goes in the event detail and the pino line. The server also runs an integrity audit: src/attachments-audit.js lists a project's observations, fetches every variant of every photo and audio attachment from core's blob server, reads each to the end and counts ok / missing / incomplete / error. It is exposed as an authenticated GET /projects/:projectPublicId/attachments/audit and, when Sentry is on, runs once shortly after start and then every AUDIT_INTERVAL_MS (default one hour, 0 disables), capped at AUDIT_MAX_ATTACHMENTS newest attachments per project, sending one attachment.audit event per project with the counts in the event context. Each failure names the device that created the observation (createdBy), since that is the phone that has to sync again, and the result carries a devices map from those device ids to the names the project knows them by; both go to Sentry with the counts. The capture function is injected through the server options, so tests run without a DSN. Sentry's unhandled-rejection integration is set to strict so a DSN does not stop the process crashing the way it does today.

Scope and limits. Sentry is initialised after fastify and core are imported and its ESM loader hook is turned off, so there is no tracing or auto-instrumentation: only crashes, unhandled rejections and the two attachment events above are reported, and a 5xx that fastify turns into a response on another route is not. The dependency is @sentry/node 10.x rather than 11.x because 11 requires node 20.19 and the Dockerfile pins 20.17.0. And the audit reads every variant in full, so the default cap of 200 attachments is a guess at what a small VPS can comfortably read once an hour; once core exposes a blob status API the audit can stop downloading bytes.

Related changes. After digidem/comapeo-core#1350, a partial blob reaches this route as an upstream 404 with code BLOB_NOT_AVAILABLE_ERROR rather than a reset or truncation. The event already carries core's error code, and the audit's missing versus incomplete split should then key on that code rather than on how the body failed. The hourly audit stays useful either way, since it is the only server-side signal that media is missing.

@socket-security

socket-security Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​sentry/​node@​10.76.2991009596100

View full report

@gmaclennan
gmaclennan added this pull request to stack #87 November 5, 2026 13:36

@gmaclennan gmaclennan left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's separate this into 2 PRs: one for the audit endpoint, and the other for reporting missing attachments to Sentry. I'm not sure about the automated audit being sent to Sentry: servers can sit without sync for several days, so this seems like a lot of unnecessary noise and processing. Maybe open a 3rd draft PR for sentry audits, but perhaps rather than hourly, send after a sync, sent at most once per hour, and only if there is something missing.

@gmaclennan

Copy link
Copy Markdown
Member Author

Split as asked, stacked in order:

Three things I had to decide in #90 that you may want to change: it only runs when SENTRY_DSN is set, as the timer did; nothing runs at start, so a server restarted mid-sync waits for the next sync to end; and the five second debounce is a constructor option rather than an env var.

This PR can be closed once you are happy with the three.

@gmaclennan gmaclennan closed this Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant