Skip to content

Return clear errors instead of crashes on three production failure paths - #277

Merged
jodeleeuw merged 2 commits into
testfrom
fix/endpoint-error-handling
Sep 25, 2026
Merged

jodeleeuw merged 2 commits into
testfrom
fix/endpoint-error-handling

Conversation

@jodeleeuw

@jodeleeuw jodeleeuw commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Three problems found in the first week of production logs (osf-relay, since 2026-09-21).

Reserved experiment IDs returned a 500. On 9/24 a participant site sent the unfilled placeholder __DATAPIPE_STUDY1_ID__ to /api/data 3 times. Firestore throws on reserved IDs instead of treating them as a miss. Every endpoint that looks up an experiment from a client-supplied ID now goes through getExperiment() (functions/src/experiment-id.ts). It returns null for a missing experiment and for an ID that breaks Firestore's document-ID rules, so each endpoint's existing not-found response covers both:

  • data, base64, condition and session start return 400 EXPERIMENT_NOT_FOUND
  • finalize, queue-status, clear-errors and ensure-derived-paths return 403 Access denied

Queue-status also checks its download queue-entry ID the same way, and returns 404 instead of a 500 for __x__ or a/b. writeLog skips IDs Firestore would reject, so it no longer logs an error for each such request.

createexperiment 502s left no trace. On 9/23 a user got 4 of these in a row, and Cloud Logging had nothing. The provider, uid and full error (stack and network code, such as ECONNRESET) are now logged. Tokens are not.

OSF listFiles ignored the HTTP status. When OSF returned an error, the code crashed with Cannot read properties of undefined (reading 'filter'), which hid OSF's real status. Through collision-cache rehydration, 2 uploads on a legacy OSF experiment failed permanently with only that TypeError to go on. listFiles now throws OSF listing failed: <status> <reason>, where the reason is OSF's own errors[0].detail or, failing that, the status text. It also guards against a 200 with no file list.

Not covered: a made-up but validly formatted experiment ID still creates an ownerless logs/{id} document through the not-found branch. That predates this PR.

Tests

  • experiment-id-validation.test.js (unit) covers isValidDocumentId.
  • experiment-id-validation-emulator.test.js sends __DATAPIPE_STUDY1_ID__ to data, base64, condition and session start (expects 400), and to finalize, clear-errors, ensure-derived-paths and queue-status (expects 403). It also sends download=__x__ and download=a%2Fb to queue-status for an owned experiment (expects 404).
  • providers-osf.test.js covers 403/404/410/429 responses, OSF's detail in the message, the fallback to status text for a non-JSON body, and a 200 with no data.
  • Emulator run on datapipe-test: 8 suites, 84 tests, all passing. It covered experiment-id-validation, data, base64, routing, finalize, queue-status, clear-errors and ensure-derived-paths.

🤖 Generated with Claude Code

jodeleeuw and others added 2 commits September 25, 2026 12:06
- Reject experiment IDs Firestore can't store (e.g. an unfilled
  "__DATAPIPE_STUDY1_ID__" placeholder) with the normal not-found
  response. Firestore throws on these instead of missing, so /api/data
  answered with a 500.
- Log the provider error when createexperiment returns a 502, so the
  failure is visible in Cloud Logging and not only in the browser.
- Check the HTTP status in OSF listFiles. An OSF error response crashed
  with "Cannot read properties of undefined (reading 'filter')", which
  hid OSF's real status and failed uploads permanently through the
  collision cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
getExperiment() returns null for a missing experiment and for an id
Firestore would reject outright, so every endpoint that looks an
experiment up by a client-supplied id keeps a single not-found branch
and none can reach the doc() call that throws. This replaces the guard
copied into six handlers and extends the fix to clear-errors and
ensure-derived-paths, which still returned 500s.

- api-queue-status checks its `download` queue-entry id the same way
  (404 instead of a 500 on "__x__" or "a/b").
- writeLog skips ids Firestore would reject, rather than every caller
  avoiding it on a comment that said it would throw (it never did).
- create-experiment logs the Error object and the uid on a 502.
- OSF listFiles errors carry OSF's own errors[0].detail, falling back
  to statusText.
- Emulator coverage now includes session start, finalize, clear-errors,
  ensure-derived-paths and queue-status (experimentID and download).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jodeleeuw
jodeleeuw merged commit dd52ceb into test Sep 25, 2026
1 check passed
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