Evidence
src/interfaces/mcp/bridge.ts:228-247 (forward):
try {
return await op(client);
} catch (err) {
if (err instanceof McpError) throw new McpError(err.code, bareMessage(err), err.data);
try {
const again = await reconnect(`${what} failed: ${errMsg(err)}`);
const result = await op(again); // same req.params (bridge.ts:250)
Why it's a bug
A transport failure after the daemon has committed the call but before the response reaches the bridge (the documented trigger: engram update restarting the daemon) re-issues the identical tools/call. remember/remember_batch are mostly absorbed by consolidation dedup and ingest_turn is an upsert, but a retried forget memory_id performs the delete and then surfaces the second attempt's "memory not found" error to the host, and a retried commitments_update / remember with a superseded/conflict outcome can differ on the second run. There is no request id or idempotency key on the daemon side. Narrow window, so P3.
Suggested direction
Retry only tools/list / ping and calls whose failure happened before the request was sent (e.g. ECONNREFUSED on connect), or forward an idempotency key the daemon dedups for a short window; otherwise return the transport error to the host and let it decide.
Related: #58, #60.
Evidence
src/interfaces/mcp/bridge.ts:228-247(forward):Why it's a bug
A transport failure after the daemon has committed the call but before the response reaches the bridge (the documented trigger:
engram updaterestarting the daemon) re-issues the identicaltools/call.remember/remember_batchare mostly absorbed by consolidation dedup andingest_turnis an upsert, but a retriedforget memory_idperforms the delete and then surfaces the second attempt's "memory not found" error to the host, and a retriedcommitments_update/rememberwith asuperseded/conflict outcome can differ on the second run. There is no request id or idempotency key on the daemon side. Narrow window, so P3.Suggested direction
Retry only
tools/list/pingand calls whose failure happened before the request was sent (e.g.ECONNREFUSEDon connect), or forward an idempotency key the daemon dedups for a short window; otherwise return the transport error to the host and let it decide.Related: #58, #60.