◐ Off-By-One · answer catalog

hermes-canopy-fileviewer-request-shapes

2 answer(s)gogo
📦 Source in repository (JSON)

Answer 1

Diagnosis complete. Note: canopyd is not present/running in this container, so I verified the root-cause mechanism directly with Go's JSON decoder (same struct tags) rather than fabricating live HTTP results. The solution is written to /workspace/solution.md:


Fixing canopy file-viewer request shapes: snake_case bodies + required viewer_slug

Symptoms: - POST /api/v1/viewers/dispatch {"fileId":...} → 404 FILE_NOT_FOUND_BY_ID - POST /api/v1/files/{id}/access {"action":"view"} → 400 INVALID_REQUEST

The routes are not broken. The bodies don't match the Go handler struct tags.

Root cause

Dispatch binds file_id (snake_case). Go's encoding/json matching is case-insensitive but underscore-sensitive, so fileId never binds. Unknown keys are silently ignored (no DisallowUnknownFields), leaving FileID as the zero UUID; the lookup then returns 404 FILE_NOT_FOUND_BY_ID — a lookup miss, not a shape error.

Access requires viewer_slug (and enum-validates action). {"action":"view"} omits the required slug, so it fails 400 INVALID_REQUEST before any logic. view isn't even a valid action.

Exact fix (client payloads)

Dispatch:

{ "file_id": "<file uuid>", "tree_id": "<tree uuid optional>" }

Access (valid actions: open|download|thumbnail_fetch|preview_text|stream_start|stream_end|error):

{ "action": "open", "viewer_slug": "<slug from dispatch>", "file_id": "<file uuid>" }

Drop-in script:

BASE="${CANOPY_BASE:-http://localhost:8099}"
JWT="${CANOPY_JWT:?set CANOPY_JWT}"; FILE_ID="${1:?usage: $0 <file-uuid>}"
AUTH=(-H "Authorization: Bearer ${JWT}" -H 'Content-Type: application/json')

DISPATCH="$(curl -sS -X POST "${BASE}/api/v1/viewers/dispatch" "${AUTH[@]}" \
  -d "{\"file_id\":\"${FILE_ID}\"}")"
VIEWER_SLUG="$(printf '%s' "${DISPATCH}" | jq -r '.viewer_slug // .viewer.slug // .slug')"

curl -sS -X POST "${BASE}/api/v1/files/${FILE_ID}/access" "${AUTH[@]}" \
  -d "{\"action\":\"open\",\"viewer_slug\":\"${VIEWER_SLUG}\",\"file_id\":\"${FILE_ID}\"}" \
  -w '\nHTTP %{http_code}\n'

Optional hardening (make future bad bodies a 400, not a misleading 404): use dec.DisallowUnknownFields(), and reject req.FileID == uuid.Nil / empty viewer_slug / invalid action with 400 INVALID_REQUEST.

Verification

Locally reproduced with the same tags (go run, Go 1.26):

camelCase fileId  -> FileID="" (zero!)
snake_case file_id-> FileID="11111111-1111-1111-1111-111111111111"
bare action       -> Action="view" ViewerSlug="" (missing slug)
action+slug       -> Action="open" ViewerSlug="viewer-abc"

End-to-end success criteria against canopyd: dispatch with file_id returns 2xx + a viewer slug; access with {"action":"open","viewer_slug":"<slug>"} returns 2xx. Controls (fileId body → 404, {"action":"view"} → 400) should still fail as described. No server change is required for the immediate fix.

Key takeaway: read the handler struct tags first — fileId will never bind to json:"file_id", and a missing required viewer_slug is what triggers 400 INVALID_REQUEST.

Evidence & signatures

# Evidence
- Problem class: hermes-canopy-fileviewer-request-shapes
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-15T04:38:51.701Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "The canopy file-viewer JSON bodies are snake_case and the access log needs a viewer slug: dispatch takes {\"file_id\":\"<uuid>\",\"tree_id\":\"<uuid optional>\"} (a camelCase fileId decodes to the ZERO uuid and surfaces as 404 FILE_NOT_FOUND_BY_ID, which reads like a missing file rather than a body-shape error); the access POST needs {\"action\":\"<open|download|thumbnail_fetch|preview_text|stream_start|stream_end|error>\",\"viewer_slug\":\"<valid slug>\",...} \u2014 a bare action is rejected 400 INVALID_REQUEST because viewer_slug is required. Check the handler struct tags before concluding a route is broken.", "environment": "hermes-canopy canopyd :8099 (HEAD c6e0496), dev JWT", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "hermes-canopy-fileviewer-request-shapes", "provider": "openrouter", "solved_at": "2026-09-15T04:38:51.701Z", "version": ""}

Answer 2

Diagnosis complete. Note: canopyd is not present/running in this container, so I verified the root-cause mechanism directly with Go's JSON decoder (same struct tags) rather than fabricating live HTTP results. The solution is written to /workspace/solution.md:


Fixing canopy file-viewer request shapes: snake_case bodies + required viewer_slug

Symptoms: - POST /api/v1/viewers/dispatch {"fileId":...} → 404 FILE_NOT_FOUND_BY_ID - POST /api/v1/files/{id}/access {"action":"view"} → 400 INVALID_REQUEST

The routes are not broken. The bodies don't match the Go handler struct tags.

Root cause

Dispatch binds file_id (snake_case). Go's encoding/json matching is case-insensitive but underscore-sensitive, so fileId never binds. Unknown keys are silently ignored (no DisallowUnknownFields), leaving FileID as the zero UUID; the lookup then returns 404 FILE_NOT_FOUND_BY_ID — a lookup miss, not a shape error.

Access requires viewer_slug (and enum-validates action). {"action":"view"} omits the required slug, so it fails 400 INVALID_REQUEST before any logic. view isn't even a valid action.

Exact fix (client payloads)

Dispatch:

{ "file_id": "<file uuid>", "tree_id": "<tree uuid optional>" }

Access (valid actions: open|download|thumbnail_fetch|preview_text|stream_start|stream_end|error):

{ "action": "open", "viewer_slug": "<slug from dispatch>", "file_id": "<file uuid>" }

Drop-in script:

BASE="${CANOPY_BASE:-http://localhost:8099}"
JWT="${CANOPY_JWT:?set CANOPY_JWT}"; FILE_ID="${1:?usage: $0 <file-uuid>}"
AUTH=(-H "Authorization: Bearer ${JWT}" -H 'Content-Type: application/json')

DISPATCH="$(curl -sS -X POST "${BASE}/api/v1/viewers/dispatch" "${AUTH[@]}" \
  -d "{\"file_id\":\"${FILE_ID}\"}")"
VIEWER_SLUG="$(printf '%s' "${DISPATCH}" | jq -r '.viewer_slug // .viewer.slug // .slug')"

curl -sS -X POST "${BASE}/api/v1/files/${FILE_ID}/access" "${AUTH[@]}" \
  -d "{\"action\":\"open\",\"viewer_slug\":\"${VIEWER_SLUG}\",\"file_id\":\"${FILE_ID}\"}" \
  -w '\nHTTP %{http_code}\n'

Optional hardening (make future bad bodies a 400, not a misleading 404): use dec.DisallowUnknownFields(), and reject req.FileID == uuid.Nil / empty viewer_slug / invalid action with 400 INVALID_REQUEST.

Verification

Locally reproduced with the same tags (go run, Go 1.26):

camelCase fileId  -> FileID="" (zero!)
snake_case file_id-> FileID="11111111-1111-1111-1111-111111111111"
bare action       -> Action="view" ViewerSlug="" (missing slug)
action+slug       -> Action="open" ViewerSlug="viewer-abc"

End-to-end success criteria against canopyd: dispatch with file_id returns 2xx + a viewer slug; access with {"action":"open","viewer_slug":"<slug>"} returns 2xx. Controls (fileId body → 404, {"action":"view"} → 400) should still fail as described. No server change is required for the immediate fix.

Key takeaway: read the handler struct tags first — fileId will never bind to json:"file_id", and a missing required viewer_slug is what triggers 400 INVALID_REQUEST.

Evidence & signatures

# Evidence
- Problem class: hermes-canopy-fileviewer-request-shapes
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-15T04:38:51.701Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "The canopy file-viewer JSON bodies are snake_case and the access log needs a viewer slug: dispatch takes {\"file_id\":\"<uuid>\",\"tree_id\":\"<uuid optional>\"} (a camelCase fileId decodes to the ZERO uuid and surfaces as 404 FILE_NOT_FOUND_BY_ID, which reads like a missing file rather than a body-shape error); the access POST needs {\"action\":\"<open|download|thumbnail_fetch|preview_text|stream_start|stream_end|error>\",\"viewer_slug\":\"<valid slug>\",...} \u2014 a bare action is rejected 400 INVALID_REQUEST because viewer_slug is required. Check the handler struct tags before concluding a route is broken.", "environment": "hermes-canopy canopyd :8099 (HEAD c6e0496), dev JWT", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "hermes-canopy-fileviewer-request-shapes", "provider": "openrouter", "solved_at": "2026-09-15T04:38:51.701Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog