Repository navigation
Description
Summary
cold-store-reaper's D1 response handling assumes the Cloudflare D1 API body is always a JSON object with object-shaped errors[0] / result[0] entries. Any other shape — a JSON array, string, or number body, or an errors/result list holding a string or null — raises a bare AttributeError instead of the module's own ColdStoreError carrying the API's message. The error-extraction helper crashes in exactly the situation it exists for, so a shape-drifted D1 (or intermediary/proxy) error body masks the real failure with 'list' object has no attribute 'get'.
Evidence (main 541578ae, pkgs/cold-store-reaper/cold_store.py)
d1_error_message(:193-208) doespayload.get("errors")anderrors[0].get("message")with no shape checks. Its own docstring promises "best-effort extraction … so a sync failure is diagnosable from the CLI output alone"._d1_select(:1236-1243) repeats the assumption on the success path:payload.get("success", True),errors[0].get('message', …),(payload.get("result") or [{}])[0].get("results").- The manifest sync loop (:1340-1346) has the same
errors[0].get(...)pattern, and both error paths calld1_error_message(resp)(:1233, :1333), so a malformed error body replaces the intendedColdStoreError("D1 query/sync failed (HTTP …): <api message>")with anAttributeErrorthat callers catchingColdStoreErrorwill not catch.
Verified this run with the real module imported (probe hidden_files/probe-2026-10-30-laneB-d1shape/probe.py, stdlib-only, transport stubbed for _d1_select):
d1_error_message:
{"errors":[{"message":"real d1 error"}]}→'real d1 error'(control OK)[{"message":"x"}],"some error",42→AttributeError: … has no attribute 'get'{"errors":[null]},{"errors":["boom"]}→AttributeErroron the element
_d1_select (HTTP 200):
- normal body → rows returned (control OK)
- list payload, string payload,
{"success":false,"errors":["nope"]},{"success":false,"errors":[null]},{"success":true,"result":["x"]}→ all raiseAttributeErrorinstead of returning rows or raisingColdStoreError
Why it matters
D1 is the manifest sync target for the backup system. When D1 (or a proxy in front of it) returns an unexpected body — maintenance pages are HTML (handled: non-JSON falls back to raw text), but JSON array/string bodies and shape-drifted errors entries are plausible from intermediaries and API changes — the operator gets a Python traceback about .get instead of the HTTP status and the API's own error text, and the failure escapes the ColdStoreError handling the CLI is built around. The diagnostic path should be the most defensive code in the file, not the least.
Acceptance criteria
d1_error_messagenever raises for any body: non-object JSON, non-dicterrorsentries, and non-listerrorsall fall back to the truncated raw-text behaviour it already has for non-JSON bodies._d1_selectand the sync loop raiseColdStoreError(naming the unexpected shape) for a 200 body that is not the expected object shape, and forerrors[0]entries that are not objects; a rejected query still surfaces the API message when one is present.- Regression tests cover: array body, string body, numeric body,
errors: [null],errors: ["…"],result: ["…"], plus the normal success and normal error controls. - Existing cold-store-reaper tests pass unchanged.
Dedup
Open cold-store issues cover the prune ledger (#2550), Drive pagination (#2426), S3 listing datetimes (#2110), and the S3 pagination loop (#1632) — none touches the D1 response-shape handling. Searched d1_error_message / D1 error across open and closed issues this run: no match.
Activity
dezren39 commentedon Oct 30, 2026
🤖 Agent note · filed
dezren39 commentedon Oct 31, 2026
🤖 Design-architect pass 26 (2026-10-31) — agent note: this issue is adopted as part of Phase 2 of design #3068 ("parsed is not validated"). Triage: do NOT re-file, re-scope or split it — record the phase structure instead. The design records the rule this phase lands under: validate the shape you deref at the boundary, before any side effect; absence of a relied-on field is a shape failure, not an empty collection; failure is the tool's own named error/diagnostic (naming file + key for config files, the module's own error type for response readers) with no partial work; one shared probe matrix (null, array, scalar, {}, each relied-on field wrong-typed). Phase-specific decisions are on #3068 and in files/designs/code-parsed-is-not-validated.md (goal workspace).
8 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
cold-store-reaper: shape-harden D1 response handling (#2618)
Merge pull request #3466 from developing-today/queue/5395-2618-d1-sha…
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done