Skip to content
Cockpit0.29.0
A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live

I2145

A tool call that may have run is never sent a second time

Where it stands

devtask.issue
Readiness
complete completion is recorded and every required evidence token is present
Status
DONE
Can start
not now
Milestone
Milestone status
VERIFY
Wave
0
Waiting on
none
Holds back
none
Behind it in all
0
Evidence
2 of 2
A move writes the repository: it stamps one field of this issue's record and appends one event to the ledger, and a commit will carry both.
The same moves from a terminal

majordomus plan start I2145

majordomus plan verify I2145

majordomus plan done I2145

What it is

Objective
Make the stdio bridge resend a message only when the server provably did not receive or run it, and report every other failure as the error it is, so no tool call executes twice.
Why
The bridge waits REQUEST_TIMEOUT, 60 s, for an answer (apps/majordomus-cli/src/mcp/bridge.rs:25). A timeout is Unreachable, which makes Session::failover re-elect (apps/majordomus-cli/src/commands/mcp.rs:229-311); when the election finds the same server as Peer, the bridge reinitializes and calls b.handle(&message) again (apps/majordomus-cli/src/commands/mcp.rs:301, and :283 after a takeover), so the same tools/call is sent twice. A slow repository_mutation tool (there are six, plus executions.start, which runs any capability) can therefore run twice. Any 4xx other than 404, such as 413, 403 or 400, also triggers a re-election (apps/majordomus-cli/src/mcp/bridge.rs:352): a message over the 1 MiB body limit (apps/majordomus-cli/src/http/server.rs:20, the 413 at :227-232) becomes 413, then a resend, then -32603 'shared server unavailable', which names the wrong cause. The bridge is synchronous under a mutex (apps/majordomus-cli/src/commands/mcp.rs:218). Gemini allows a 120 s call timeout against the bridge's 60 s. Baseline audit of 2026-10-09 at master 1fb8d6be44, lifecycle gap 2 (ranked second after authorization) and the payload-limit row.
Now
A timed-out or rejected tool call is re-sent after a re-election, whatever its effect.
Wanted
failover resends only on Sent::SessionLost, a refused connection, or a tool whose declared execution hints say idempotent; otherwise the client receives -32603 with 'outcome unknown' and the request id. A 4xx other than 404 or 409 is answered as that error, with no re-election.
Done when
unknown: the record declares no `completion`
Risk
unknown: the record declares no `risk`

Acceptance

  • A mutating tool call that outlasts the bridge timeout is executed exactly once, proved by a test tool that counts its invocations
  • A 413 for an oversized message reaches the client as payload too large, and no election follows
  • A read-only tool call that timed out is retried and returns its answer
  • docs/MCP.md states when the bridge resends
Validation
bin/majordomus plan validatecargo test --test mcp_shared --test shared_units
Evidence required
timed_out_mutation_runs_once_testrejected_request_is_not_reelected_test
Evidence present
timed_out_mutation_runs_once_testrejected_request_is_not_reelected_test

Scope

Touches
apps/majordomus-cli/src/commands/mcp.rsapps/majordomus-cli/src/mcp/bridge.rsapps/majordomus-cli/src/capabilityapps/majordomus-cli/tests/mcp_shared.rsapps/majordomus-cli/tests/shared_units.rsdocs/MCP.md
Not
unknown: the record declares no `non_scope`
Depends on
none
Priority
p0
Profile
implementation
Parallel safe
false
Owner
unknown: the record declares no `owner`

The work so far

Branches
none
Merged
none
Commits
0
Trunk
master
Sessions
none
Created
2026-10-09
Updated
2026-10-14T08:01:55Z
Started
2026-10-14T08:01:47Z
Verified
unknown: the record declares no `verified_at`
Completed
2026-10-14T08:01:55Z

Synchronisation

Adapter
scripts/github-sync
Repository
unknown: the derived plan's header carries no repository
External issue
unknown: a live GitHub state; this executable makes no network call, so only `scripts/github-sync --check` can answer it
State there
unknown: a live GitHub state; this executable makes no network call, so only `scripts/github-sync --check` can answer it

Findings

Nothing is wrong with these records.