Skip to content

fix(containers): answer 404 when a path is outside every workspace - #50

Merged
miguelrisero merged 1 commit into
mainfrom
mr/attempt-context-404
Jul 27, 2026
Merged

fix(containers): answer 404 when a path is outside every workspace#50
miguelrisero merged 1 commit into
mainfrom
mr/attempt-context-404

Conversation

@miguelrisero

Copy link
Copy Markdown
Owner

Problem

GET /api/containers/attempt-context?ref=<dir> returns 500 for any directory that isn't inside a workspace.

On a live instance this was 571 errors — the single largest source of server errors. After the SQLite contention fix (#49) it became the only remaining source:

44  matched_path="/api/containers/attempt-context"   ← all remaining 5xx

The client sees "An internal error occurred. Please try again."

It was never a database failure

Workspace::resolve_container_ref_by_prefix does this:

let workspaces = sqlx::query_as!().fetch_all(pool).await?;   // succeeds

Self::best_matching_container_ref(path,)
    .map(|workspace_id| ContainerInfo { workspace_id })
    .ok_or(sqlx::Error::RowNotFound)                          // ← fabricated

The query succeeds. The prefix match then runs in memory, finds no workspace containing the path, and manufactures sqlx::Error::RowNotFound to mean "no match" — using a database-error type as an Option::None.

Both callers then do .map_err(ApiError::Database), and error.rs maps ApiError::Database(_) to ErrorInfo::internal("DatabaseError") → 500.

So "is this directory a workspace?" answered about a directory that isn't one is reported as a server fault.

Who hits it

Editors and MCP clients call these endpoints with whatever directory they were started in, so a non-workspace path is the ordinary case. Observed ?ref= values, none of which are workspace container refs (those live under /var/tmp/vibe-kanban/worktrees/):

?ref= hits
/home/miguel 25
/var/tmp/admin2-lane/wt 12
/var/tmp/br-lanes/receiver-redesign 11
/home/miguel/projects/patricia-monorepo/.wt/starter-credits 10
/var/tmp/chief-worktrees/bc-mobile-files 9

Upstream: BloopAI/vibe-kanban#3272, open since March.

Change

  1. resolve_container_ref_by_prefix returns Result<Option<ContainerInfo>, sqlx::Error> — a logical miss stops masquerading as a database error, and a genuine database error stays distinguishable.
  2. Both callers (get_container_info and get_context — the only two) answer 404 via WorkspaceError::WorkspaceNotFound.

404 rather than a new error variant because load_context, two lines below in the same handler, already reports a missing workspace exactly that way — so the endpoint's 404 carries one uniform meaning whichever way the lookup fails.

no_workspace_contains_path() returns the error rather than wrapping a Result, because ApiError is large enough that a Result<_, ApiError> helper trips clippy::result_large_err under CI's -D warnings.

Compatibility

/containers/info is used by the VSCode extension. Its success path is unchanged; only the not-found path moves 500 → 404. Nothing can reasonably depend on the 500, and a client distinguishing "no workspace here" from "server broken" is the point.

Testing

619 workspace tests pass; cargo clippy --workspace --all-targets --exclude vibe-kanban-tauri -- -D warnings clean (CI's exact invocation).

New coverage:

  • a path outside every workspace resolves to Ok(None), explicitly asserting it is not an error
  • a workspace resolves from both its own root and a nested subdirectory
  • resolving against an empty workspace table is a miss, not an error
  • the miss maps to 404 and, specifically, !status.is_server_error()

/api/containers/attempt-context returned 500 for any directory that is not
inside a workspace. It was the single largest source of server errors on a
live instance -- 571 of them, and after the SQLite contention fix it was the
only remaining source.

The failure was not a database failure at all. resolve_container_ref_by_prefix
runs a fetch_all that succeeds, matches the requested path against the loaded
container refs in memory, and then reported "nothing matched" by manufacturing
sqlx::Error::RowNotFound. Both callers mapped that to ApiError::Database, which
maps to a generic 500 "An internal error occurred."

Editors and MCP clients call these endpoints with whatever directory they were
started in, so a path outside every workspace is the ordinary case rather than
an exceptional one. Return Option instead, so a logical miss stays
distinguishable from a real database error, and let both callers answer 404 --
matching load_context, which already reports a missing workspace that way in
the same handler.

Observed values that triggered it include /home/miguel and worktrees under
/var/tmp/br-lanes, none of which are workspace container refs.
@miguelrisero
miguelrisero merged commit 5746a42 into main Jul 27, 2026
7 checks passed
@miguelrisero
miguelrisero deleted the mr/attempt-context-404 branch July 27, 2026 05:39
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