Skip to content

Validate project references, and stop lying about missing handoffs - #22

Merged
Avik-creator merged 3 commits into
mainfrom
fix/reference-validation-and-claim-404
Aug 21, 2026
Merged

Avik-creator merged 3 commits into
mainfrom
fix/reference-validation-and-claim-404

Conversation

@Avik-creator

Copy link
Copy Markdown
Owner

The two you asked me to leave. Both change an API contract, both toward what the code already does elsewhere.

1. A record could be filed against a project the caller cannot see

POST /memories and POST /sessions accepted any projectId — one owned by another account, or one that never existed — and answered 201. POST /handoffs already refused both with 400 INVALID_RELATION, using an ownedProject() helper that was right there.

Before, with two accounts:

B posting into:      own project | ANOTHER ACCOUNT'S | does not exist
  memory             201         | 201               | 201
  session            201         | 201               | 201
  handoff            201         | 400               | 400

After:

  memory             201         | 400 INVALID_RELATION | 400 INVALID_RELATION
  session            201         | 400 INVALID_RELATION | 400 INVALID_RELATION
  handoff            201         | 400 INVALID_RELATION | 400 INVALID_RELATION

Nothing leaked before this — I checked, and the project's name never surfaced anywhere in B's memory list, session list, summary, or briefing. The damage was that the record became invisible from the project it named, with a 201 saying it had worked.

The guard sits in the repositories, not the routes. MCP calls the same services, so a check in the route handlers would have covered POST /memories but not save_memory, and POST /sessions but not start_session — which is the same shape as the bug being fixed.

2. Claiming a handoff that does not exist blamed a live agent

handoff that does not exist  -> 409 "This handoff is closed or already held by a live agent."
another account's handoff    -> 409 "This handoff is closed or already held by a live agent."

Both now answer 404 Handoff not found, matching what every other endpoint returns for a record outside the workspace. The genuinely-held case still answers 409:

handoff that does not exist          -> 404 Handoff not found
another account's handoff            -> 404 Handoff not found
own handoff, first claim             -> 200 claimed
own handoff, held by another session -> 409 This handoff is closed or already held by a live agent.

A third thing, found on the way

Both handoff routes caught every exception and answered 400 INVALID_RELATION. A database outage during createHandoff was therefore reported to the caller as a bad request. The reference failure is now a typed ReferenceNotFoundError; anything else rethrows.

Verified

48/48 against a fresh PostgreSQL 17 — 46 existing plus 2 new. The cross-account matrix above was run separately, because the suite has only one account and cannot reach that case; the tests use a nonexistent id, which is the same guard.

All three commits typecheck standalone.

Note on existing data

Rows written before this may still point at projects their owner cannot see. Nothing cleans them up, and I did not add a migration to do it — worth deciding separately. The durable version of this fix is a composite foreign key on (userId, projectId), which would make the state unrepresentable rather than merely rejected at the edge; that is a schema change, so I left it.

POST /memories and POST /sessions accepted any projectId, including one owned
by another account and one that does not exist, and answered 201. The record
was then invisible from the project it named. POST /handoffs already refused
both, so the intended behaviour and its error code were already written.

The guard sits in the repositories rather than the routes because MCP calls
the same services; a check in the route handlers would not cover save_memory
or start_session.

The reference failure is now a typed error. The handoff routes previously
caught everything and answered 400 INVALID_RELATION, so a database outage was
reported to the caller as a bad request.
Claiming an id with no matching handoff answered 409 "closed or already held
by a live agent", identical to the genuinely-held case. A mistyped id sent the
caller to debug an agent that was never there. It now answers 404, which is
what every other endpoint already returns for a record outside the workspace.
Also documents both in the README and the agent guide.
@vercel

vercel Bot commented Aug 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
personal-task-dashboard Ready Ready Preview, v0 Aug 21, 2026 12:10pm

@Avik-creator
Avik-creator merged commit 56d5aa6 into main Aug 21, 2026
4 checks passed

This branch was successfully deployed

1 active deployment
Preview — 058051d1 Deployed Aug 21, 2026 by vercel[bot]
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.

2 participants