Repository navigation
fix(mcp): never write to the production DataEdge after a failed billing lookup - #732
Merged
Merged
Conversation
…ng lookup A single 5 s startup billing call that timed out made a phrase-configured server fall back to the PRODUCTION default DataEdge for its whole lifetime, even with TOTALRECLAW_SERVER_URL on staging (3.5.1-rc.1 smoke: writes on prod, empty staging recall). - Startup retries the lookup with backoff (2 x 8 s). Registration now runs first so a new account's lookup can succeed. - Still unresolved: the state stays unresolved; every write goes through writeConfigFor, which re-runs the lookup (3 x 10 s, one in-flight per state) and throws DataEdgeUnresolvedError (write not sent) if it fails. - buildSubgraphOverrides refuses an unresolved state as defence in depth. - Successful lookups and TOTALRECLAW_DATA_EDGE_ADDRESS behave as before; bundle mode keeps failing closed at startup, now after retries. - tests/dataedge-lazy-resolve.test.ts: timeout -> no write to the fallback address; later success -> staging address; retry/backoff; concurrency; env override; static guard that all write sites use writeConfigFor. Addresses p-diogo/totalreclaw-internal#689. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bd3G1BkVMFSXkcdAQ7nFax
A refused write (#689 DataEdgeUnresolvedError) was only logged to stderr; the tool result said success:false with no reason. Add error to the response and to each failed result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bd3G1BkVMFSXkcdAQ7nFax
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses p-diogo/totalreclaw-internal#689. L2, MCP-only (
mcp/src,mcp/tests,mcp/CHANGELOG.md).Problem
At startup the server makes one billing lookup (
/v1/billing/status) to learn which DataEdge contract belongs to its relay. In phrase (mnemonic) mode, if that single 5-second call timed out, the server fell back to the production default DataEdge (0xC445…) for the rest of its life, even whenTOTALRECLAW_SERVER_URLwas staging. In the 3.5.1-rc.1 staging smoke, a cold staging relay sent a throwaway account's writes to production (tx0xd9297d84…), and recall on staging then found nothing.Change
STARTUP_BILLING_RETRY: 2 attempts, 8 s each, 1 s apart). Phrase-mode registration now runs before the lookup, so a newly created account can resolve at first start.chainResolved: falseand no DataEdge, and recall still works. All 5 on-chain write sites now get their config fromwriteConfigFor(state). That function re-runs the lookup (WRITE_BILLING_RETRY: 3 attempts, 10 s each; concurrent writes share one lookup). On success it fills in chain and DataEdge for the rest of the process. On failure it throwsDataEdgeUnresolvedError: the write is not sent, and the error says nothing was stored and how to recover.buildSubgraphOverridesthrows if it is handed an unresolved state, so a future write site that skipswriteConfigForstill can't reach the production default.TOTALRECLAW_DATA_EDGE_ADDRESSstill wins and counts as resolved.totalreclaw_rememberandtotalreclaw_debriefused to log a failed batch only to stderr, and their result saidsuccess:falsewith no reason. They now includeerroron the response and on each failed result, so the agent can see the "not sent" message.Tests
mcp/tests/dataedge-lazy-resolve.test.ts(13 tests):buildSubgraphOverridesthrows on its own.index.tsgoes throughwriteConfigFor, and the old "proceeding without a confirmed DataEdge" fallback is gone.End to end against real staging, local build of this branch: a new harness script (
qa-cold-relay-check.cjs, internal) runs the server through a local proxy to staging. The proxy holds/v1/billing/statuspast the client timeout.rememberis refused with the "was NOT sent" error, and no batch is submitted.dataEdge=0xE7a4…, andrememberreturns tx0x21c41430…. The receipt check says GO on the staging DataEdge, with no production log; the staging subgraph has 1/1.Local results:
tscclean; full MCP suite 49 suites / 844 passed; URL guard OK; phrase-safety dist scan clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01Bd3G1BkVMFSXkcdAQ7nFax