Fix: every tool call fails in multi-base mode with two or more bases - #5
Conversation
handleCallTool() read getBaseInfo() after runWithBase() had returned, so the AsyncLocalStorage store was already unset. With two or more bases ClientRegistry.resolve(undefined) has no default and throws "Multiple bases available", turning every successful tool call into an error. Capture the base info inside the handler scope instead, and use it in both the success and the error log line. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TXnsxX58RpvMWysyjqjrKJ
) #5 fixed the one place that read the client after runWithBase() had returned. This makes the next such mistake announce itself instead of hiding. ContextualClient resolved through registry.resolve(getStore()), which could not tell "no scope at all" from "in a scope, no base named" — both arrived as undefined. With a single base the out-of-scope read then quietly succeeded via the registry default; with two or more it threw `Multiple bases available … Specify "base" parameter`, blaming the caller for omitting an argument they had supplied. That combination is what let the defect survive five months: the configuration that would have exposed it is not the one we run. The store now carries { base } instead of the bare name, so an absent store is detectable, and reading outside the scope throws a message that names the actual error — the same way regardless of how many bases are configured. Claude-Session: https://claude.ai/code/session_01Tn4FT2sVqoFz1QdpNDNAy7 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Danke für den PR — Analyse, Repro-Tabelle und Regressionstests, das war so gut wie fertig zum Mergen. Ich habe den Bug unabhängig nachgestellt und kann bestätigen, dass es exakt so war wie beschrieben: mit zwei Bases fallen alle 22 Tools um, Ist als 1.6.4 released. Aufbauend darauf wirft Entschuldige die Verzögerung: bei Fork-PRs müssen wir die Workflows manuell freigeben, das ist untergegangen. |
Symptom
With two or more entries in
SEATABLE_BASES, everytools/callfails with:even when a valid
baseargument is supplied. With exactly one entry inSEATABLE_BASESthe identical call works.Root cause
handleCallTool()runs the tool handler insidecontextualClient.runWithBase(...), but readsthis.client.getBaseInfo()for the log line after that scope has returned (src/mcp/server.ts, success path andcatchpath). At that point theAsyncLocalStoragestore isundefined, soContextualClient'sclientgetter callsregistry.resolve(undefined)— which only has adefaultNamewhen exactly one base is configured and otherwise throwsMultiple bases available. The failure is in logging after the work, not in the data path, which is why it is invisible with a single base and why it discards a result that was already correct.Evidence
Reproduced against
main(1.6.1) over stdio with a fake SeaTable API (app-access-token + metadata endpoints only), callinglist_tableswith{ base: "<first base>" }:SEATABLE_BASESMultiple bases available (…)The two-base run fetches its data exactly once and gets it — the handler succeeded and the answer was thrown away afterwards.
Fix
Capture the base info inside the
runWithBasescope (in afinallyaround the handler, so it covers the error path too) and use that value in both log lines. Both sites needed it: without thecatch-path fix, real errors in multi-base mode still surface as this misleading message instead of the actual failure.The capture is defensively wrapped so a genuinely unresolvable base (missing or unknown
baseargument) still yields the original error rather than one from logging; when the base does resolve,dtable_uuidandapp_nameare logged as before.Tests
Two regression tests in
tests/server.spec.tsusing a two-baseContextualClient: a tool call succeeds, and the completion log line carries the resolved base'sdtable_uuid/app_name. Both fail onmainand pass with this change.npm test(315 tests),npm run lintandnpm run typecheckpass.npm run formatreports pre-existing style warnings across the repo (including these two files before this change); no reformatting was done.Single-base mode is unaffected —
resolve(undefined)still returns the default client there.