Skip to content

fix(mcp): credit the agent on the board, let it tidy up, and survive a restart - #57

Merged
praxagent merged 1 commit into
mainfrom
fix/attribution-and-reconnect
Jul 26, 2026
Merged

fix(mcp): credit the agent on the board, let it tidy up, and survive a restart#57
praxagent merged 1 commit into
mainfrom
fix/attribution-and-reconnect

Conversation

@praxagent

Copy link
Copy Markdown
Owner

Three things found by connecting a real MCP client and using it.

The board said a human did it

Cards filed over MCP were credited to the person reading the board — which defeats most of what a shared board is for.

Credentials now carry a display label — "Claude Code" by default, overridable so Codex users don't see the wrong tool — and every mutation passes it as the author, editor or comment actor. An unlabelled key falls back to its own name rather than to "human": a key identifier on a card is ugly; crediting the wrong party is worse.

A comment that silently vanished

comment_on_task sent {comment, author}. The Library API reads {text, actor}. Comments were accepted, reported successful, and arrived empty and unattributed — a silent wrong result, worse than an error because nothing tells you to look.

There was a test asserting the broken shape. It checked what our code sent rather than what the API reads, so it passed for as long as the feature was broken. Rewritten against the contract with a note saying so — that's the more useful lesson than the bug.

delete_task added: an agent that files a card should be able to withdraw it. Requires task.write, respects space scope, refreshes the board, and its description says to only remove cards it created or was asked to — a tool the agent can see is a tool it will try.

A tab that never came back

Reported as a WebSocket 502. The 502 was transienttailscale serve answers 502 while the upstream restarts, which is every deploy. But the reason a tab stayed dead is ours: backoff was 1000 × 2^attempts with no ceiling and a hard stop at 10 attempts.

Attempt Wait
7 2 min
9 8.5 min
10 gives up permanently
  • Capped at 30s with jitter. Backing off protects the server; waiting minutes to notice it came back protects nobody.
  • Never gives up. A server down now is usually back inside a minute, and a tab left open overnight should be alive in the morning. "Refresh the page" is the app admitting it stopped trying.
  • Refetch on reconnect after a drop. Nothing replays missed events, so a recovered tab showed pre-gap state — the dangerous kind of stale, because it looks fine. Same class as the no-polling Kanban.

Verification

294 backend + 37 frontend tests green, tsc clean. Prax half is praxagent/prax#186.

…a restart

Three things found by connecting a real client and using it.

THE BOARD SAID A HUMAN DID IT

Cards filed over MCP were credited to the person reading the board. Credentials
now carry a display label — "Claude Code" by default, overridable so Codex users
do not see the wrong tool — and every mutation passes it as the author, editor
or comment actor. An unlabelled key falls back to its own name rather than to
"human": a key identifier on a card is ugly, crediting the wrong party is worse.

A COMMENT THAT SILENTLY VANISHED

comment_on_task sent {comment, author}. The Library API reads {text, actor}. So
comments were accepted, reported as successful, and arrived EMPTY and
unattributed — a silent wrong result, which is worse than an error because
nothing tells you to look.

There was a test asserting the broken shape. It checked what our code sent
rather than what the API reads, so it passed for as long as the feature was
broken. Rewritten against the contract, with a note saying so, because that is
the more useful lesson than the bug.

delete_task added: an agent that files a card should be able to withdraw it.
Requires task.write, respects space scope, refreshes the board like any other
mutation, and its description says to only remove cards it created or was asked
to — a tool the agent can see is a tool it will try.

A TAB THAT NEVER CAME BACK

Reported as a WebSocket 502. The 502 itself was transient (tailscale serve
answers 502 while the upstream restarts, which is every deploy) — but the reason
a tab stayed dead is ours: backoff was 1000 * 2^attempts with NO ceiling and a
hard stop at 10. Attempt 7 waits 2 minutes, attempt 9 waits 8.5, then it gives
up permanently and tells you to refresh.

- Capped at 30s with jitter. Backing off protects the server; waiting minutes to
  notice it came back protects nobody, and lockstep retries from several tabs
  hit a server that is still coming up.
- Never gives up. A server down now is usually back inside a minute, and a tab
  left open overnight should be alive in the morning. "Refresh the page" is the
  app admitting it stopped trying.
- Refetch on a reconnect that followed a drop. Nothing replays missed events, so
  a recovered tab was showing pre-gap state — the dangerous kind of stale,
  because it looks fine. This is the same class as the no-polling Kanban.

294 backend + 37 frontend tests green, tsc clean.
@praxagent
praxagent merged commit 7714399 into main Jul 26, 2026
1 check passed
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