fix(view): unusable-board reads surface as JSON 400, not a text/plain 500#60
Merged
Conversation
… 500
GET /features and /features/{fid} were the only store-touching routes outside
_guard — on an unusable board (no repo bound, no .beads, br missing) the
BoardError escaped as FastAPI's text/plain 500, which the board view could only
render as WebKit's 'SyntaxError: The string did not match the expected pattern'
(r.json() on a non-JSON body). Hit on a manager-tier agent with project_board
enabled but no repo bound.
- api: wrap both GET routes in _guard like every other route — the view now
receives the actionable BoardError message as JSON 400.
- view: api() parses defensively — a JSON error body surfaces its detail, a
non-JSON body its HTTP status; load()'s catch renders that instead of a raw
parse error (previously a 400 would have silently rendered an EMPTY board:
r.features || [] swallowed the detail).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
👀 Quinn is reviewing — verdict (PASS / WARN / FAIL) + findings to follow. |
There was a problem hiding this comment.
QA Audit — PR #60 | fix(view): unusable-board reads surface as JSON 400, not a text/plain 500
VERDICT: PASS
CI Status
- test: success ✅
Diff Review
api.py: both GET/featuresand/features/{fid}now route through_guard, consistent with every other store-touching route. The_featurehandler correctly scopes_guardto just the store call so the 404 path remains distinct.board_view.py:api()now parses defensively — a JSON error body throws itsdetail, a non-JSON body throwsHTTP <status>. This catches both the new JSON 400 and any future non-JSON error.tests/test_api.py:test_unusable_board_reads_surface_as_json_400_not_500covers both GET routes with aBrokenStoreraisingBoardError, asserting 400 + message.
Observations
- No issues found. The fix is minimal (3 files, ~36 lines), follows the established
_guardpattern consistently, and the test covers the exact regression path described in the PR. _feature's scoping (_guardonget_featureonly, 404 outside) is correct — aBoardErrorbecomes 400, a missing feature stays 404.
— Quinn, QA Engineer
|
Submitted APPROVE review on |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
On an agent where the board is unusable — no
project_board.repobound, cwd has no.beads/,br initfails (e.g. a read-only cwd on a desktop-spawned manager) — the board view died with:GET /featuresandGET /features/{fid}were the only store-touching routes outside_guard, so theBoardErrorescaped as FastAPI'stext/plain500 and WebKit'sr.json()threw the cryptic parse error instead of showing the (already actionable) message.The fix
_guardlike every other route → JSON 400 carrying the BoardError message ("repo '.' has no beads workspace … set project_board.repo / db_path").api()parses defensively — a JSON error body throws itsdetail, a non-JSON body throwsHTTP <status>;load()'s catch renders that. Without this, the JSON 400 would have silently rendered an empty board (r.features || []).Tests
test_unusable_board_reads_surface_as_json_400_not_500— a store raisingBoardErroron reads → both GETs answer 400 JSON with the message. 202 tests pass; ruff check + format clean.🤖 Generated with Claude Code