fix(cli): give plan_call its own request timeout ceiling - #78
Conversation
plan_call regularly runs for about as long as the shared 15 second request timeout allows, so `calle call plan` failed with a timeout on a call that was working normally. Planning requests now use 120 seconds while the MCP session handshake and the other tools keep 15, so a genuinely hung request still fails fast. An explicit --timeout-seconds wins for every request, planning included. callMcpTool takes a per-call `timeoutSeconds` that covers the tools/call request only. The timeout error also names the ceiling that ran out, since two are now in play. Closes CALLE-AI/awesome-phone-call-agents#79
The CLI reference listed only the shared 15 second default. The troubleshooting guide did not connect `MCP request timed out for tools/call` to the request timeout at all, so a user had to find the boundary alone. The reference now carries the planning default beside the shared one. `calle --help` prints both. The guide has a new section with the symptom, the cause and the two ways to recover. Closes CALLE-AI/awesome-phone-call-agents#80
Ray-56
left a comment
There was a problem hiding this comment.
Thanks for the careful split between the session timeout and the plan_call timeout. The overall direction is sound, and local check/test/pack validation passes, but two P2 issues still block merge.
[P2] Validate the new public Core timeout override before arming setTimeout.
timeoutSeconds currently uses truthiness plus Number(), so truthy invalid or oversized values reach the timer. I reproduced:
- "120s" -> MCP request timed out for tools/call after NaNs
- Infinity -> Node resets the delay to 1 ms and reports after Infinitys
- 2147484 -> Node resets the delay to 1 ms
- Negative values clamp to one second instead of falling back
Require a finite value greater than zero and no greater than Math.floor(2_147_483_647 / 1000). When the per-call override is invalid, fall back to the already computed shared timeoutMs/config.timeoutSeconds rather than a hard-coded 15-second default. Add regression tests for a junk string, a negative value, Infinity, and one value past the maximum.
[P2] Fix the troubleshooting examples.
calle call plan --to-phone +15551234567 is incomplete because --goal is required. Add a harmless goal to both examples, for example --goal "Confirm the appointment", so the documented recovery command can actually run.
The patch changeset for @call-e/cli and @call-e/core is appropriate. After fixing these issues, please run:
- pnpm run check:versions
- pnpm check
- pnpm test
- pnpm pack:dry-run
GitHub CI has not executed for this first-time-contributor head (action_required, zero jobs), so it should be run before merge.
callMcpTool took its timeoutSeconds override on truthiness and handed Number(seconds) straight to setTimeout, so a value that looked readable could still collapse the timer. "120s" armed NaN, Infinity and 2147484 seconds overflowed the signed 32 bit delay, all three abort after 1ms, and a negative value clamped to the one second floor. The reported ceiling claimed otherwise. A duration is used only when it is finite, above zero and no greater than MAX_TIMER_SECONDS. Anything else falls back to the shared ceiling the session already computed rather than a fixed default. MAX_TIMER_SECONDS moves to @call-e/core/constants so the --timeout-seconds validator and the transport share one bound.
calle call plan requires --goal, so both recovery commands in the timeout section exited with "Missing required --goal" instead of running.
|
Both P2s are fixed on [P2] Validate the new public Core timeout override before arming setTimeout
timeoutMs: usableTimeoutMs(timeoutSeconds) ?? timeoutMs,So the per-call path never reaches for a hard-coded default. The shared session ceiling keeps its own The bound moved into I reproduced your four inputs first, against a fake
The 16ms on the first row is process warm-up on the first request of the run. The armed delay was 1ms in all three overflow cases, which is what the two TimeoutOverflowWarnings say. The last two rows are controls: a value sitting on the maximum and the planning default still arm the long timer. Regression tests, all in
Reverting only the source files while keeping the new tests: [P2] Fix the troubleshooting examplesBoth examples in I swept the whole repository for the incomplete shape with Then I ran both documented commands against an empty
Gates on
|
| gate | result |
|---|---|
pnpm run check:versions |
exit 0, package metadata and install references in sync |
pnpm check |
exit 0, 7 projects, syntax plus tsc --noEmit --strict |
pnpm test |
exit 0, 137 tests, 136 pass, 1 skipped |
pnpm pack:dry-run |
exit 0, call-e-core-0.2.5.tgz and call-e-cli-0.3.9.tgz |
The skip is the pre-existing Windows-only case from #71. The count is 137 rather than 135 because of the two new core tests. The changeset now mentions the validation, since it changes published behaviour.
One gate outside your list and outside CONTRIBUTING: pnpm run check:examples fails here with OAuthClientProvider.__init__() got an unexpected keyword argument 'timeout' from the Python example client. It fails identically at the parent commit and this branch touches nothing under examples/, so it is a local dependency mismatch rather than something the PR moved. Flagging it so the four green gates above are not read as more than they are.
On CI: refs/pull/78/head still carries one github-actions check suite with conclusion action_required and zero check runs, the first-time-contributor gate. Nothing on a fork branch can trigger it, so it needs the workflow run approved on your side. I will not chase it further.
|
@Ray-56 nudge on this one. Both P2s from your review are fixed and pushed on
Re-ran the gates at Two things a fork can't do, so flagging them here: re-requesting your review and triggering the first-time-contributor CI (still |
|
CALL-E 0.4.0 and core 0.3.0 landed the plan_call timeout and the recovery in e966ea7 and d5bf6c1, so this PR is redundant now. Closing it. Thanks for the review here. One thing from your P2 carried over. The |
Summary
calle call planfails withMCP request timed out for tools/callunder the default configuration.plan_callregularly runs for about as long as the shared 15 second request ceiling allows, so a plan that is working normally can finish just after the ceiling every other request uses. The reporter recovered by rerunning with--timeout-seconds 120.Planning now carries its own default ceiling of 120 seconds. The MCP session handshake and every other tool keep 15, so a genuinely hung request still fails fast instead of waiting two minutes.
Why not raise DEFAULT_TIMEOUT_SECONDS
Raising the shared default would fix planning by making every unresponsive request wait two minutes, the
initializehandshake included, which is what fails when a server is unreachable. The two requests want different ceilings, so the slow one gets its own:callMcpTooltakes a per-calltimeoutSecondsthat covers thetools/callrequest only.openMcpSessionkeepsconfig.timeoutSeconds.plan_callalone, incalle call plan,calle call runandcalle mcp call plan_call.DEFAULT_PLAN_TIMEOUT_SECONDSis 120 and sits beside the other CLI defaults.--timeout-secondsis the ceiling for every request, planning included, so the flag still means what it says.MAX_TIMER_SECONDS(2147483). Anything else falls back to the shared ceiling the session already computed, becausesetTimeoutcollapses a delay it cannot store into 1ms and would abort the request before it left. That bound now lives in@call-e/core/constants, so the--timeout-secondsvalidator and the transport share one number.The timeout message now names the ceiling that ran out, since two are in play:
MCP request timed out for tools/call after 120s. That covers the other half of the report, where the error gave no hint which value to change.Documentation
docs/install/troubleshooting.mdgains a section for the error string with the symptom, the cause and both ways to recover.packages/cli/docs/cli-reference.mdcarries the planning default beside the shared one.calle --helpprintsDefault: 15, 120 for plan_call.Tests
packages/core/test/tool-timeout.test.jscovers the transport: a tool call carrying a longer ceiling than the handshake, a call without an override keeping the shared ceiling,tools/listunaffected, thirteen unusable overrides falling back to the shared ceiling, four readable ones used as given up to the timer maximum and a wall clock case proving an overflowing override waits the shared ceiling instead of aborting after 1ms.packages/cli/test/cli.test.jscovers the wiring, that planning gets the longer default and that an explicit flag wins.They fail without the change. Reverting only the source files while keeping the new tests:
No CALL-E account or live call is involved. Every case drives an injected
fetchImpl.Verification
From the repository root on Node 22.22.2 with pnpm 10.18.3.
The single skip is the pre-existing Windows-only case.
AI disclosure
AI assistance (Claude, Anthropic) was used in developing this change. It is submitted under my name and I can answer for it. Verified locally before submitting: the four commands above, the pre-fix run quoted under Tests, a reproduction of every rejected timeout value against an injected
fetchImpland acalle --helprender of the new default line.Closes CALLE-AI/awesome-phone-call-agents#79
Closes CALLE-AI/awesome-phone-call-agents#80