Skip to content

Add FT_TIMEOUT_MULTIPLIER env var to scale engine call timeouts#159

Open
aaronwestphal wants to merge 1 commit into
afar1:mainfrom
aaronwestphal:aaron/timeout-multiplier
Open

Add FT_TIMEOUT_MULTIPLIER env var to scale engine call timeouts#159
aaronwestphal wants to merge 1 commit into
afar1:mainfrom
aaronwestphal:aaron/timeout-multiplier

Conversation

@aaronwestphal
Copy link
Copy Markdown

@aaronwestphal aaronwestphal commented May 27, 2026

Why

Engine call timeouts (DEFAULT_TIMEOUT in src/engine.ts, the per-depth timeoutMs values in src/adjacent/prompts.ts, the wiki-compile timeout in src/md.ts) are sized for a baseline local CLI with no startup overhead.

In environments where the configured engine has heavy startup cost — MCP servers, SessionStart hooks, high reasoning effort, packaged plugin/skill loaders — the deadlines fire before the child has responded. The failure mode is non-deterministic claude/codex timed out after Ns even on small prompts.

Concrete repro from my setup: ft possible run --depth quick --engine claude against a 30-file repo. Claude Code with my normal MCP fleet + SessionStart hooks takes ~140s to return the first candidate-generation pass, so quick (120s) deterministically aborts at the "Generating candidates" step. standard (180s) is on the edge. deep (300s) is the only reliable depth, which defeats the point of having tiers.

What

Single uniform multiplier applied at the engine layer so every caller (classification, wiki compile, Possible runs) inherits it without per-site changes.

export FT_TIMEOUT_MULTIPLIER=2   # doubles every engine deadline
ft possible run --seed <id> --repo .

Default is 1 (no change in behavior). Non-numeric or non-positive values fall back to 1 silently. Read on every call so tests and one-off env overrides take effect without re-importing the module.

Diff shape

  • src/engine.ts — add timeoutMultiplier() and resolveTimeout() helpers, replace opts.timeout ?? DEFAULT_TIMEOUT at the two consumption sites (invokeEngine and invokeEngineAsync). +23 / -2.
  • tests/engine-invoke.test.ts — two new tests using the existing shEngine shell fake: one pins the scaling behavior (multiplier 5 lets a 500ms sleep complete inside a 200ms caller timeout), one pins input validation ('not-a-number', '0', '-2', '' all fall back to multiplier 1).
  • README.md — new "Engine timeouts" subsection adjacent to the existing proxy-env-var paragraph.

No default change. Opt-in only.

Verification

> fieldtheory@1.3.20 test
ℹ tests 545
ℹ pass 545
ℹ fail 0

The existing invokeEngineAsync: timeout kills the child promptly test is unchanged and still passes — with no env var set, multiplier is 1 and 200ms stays 200ms.

Why a multiplier and not per-tier env vars

Considered FT_ENGINE_TIMEOUT_MS / FT_DEPTH_TIMEOUT_QUICK_MS / FT_DEPTH_TIMEOUT_STANDARD_MS / FT_DEPTH_TIMEOUT_DEEP_MS / etc. Rejected as too much surface for a single failure mode (children that are uniformly slower than the author's reference machine). One knob, documented in one place, scales the existing tier shape rather than letting users invert it accidentally.

Engine call timeouts (DEFAULT_TIMEOUT in src/engine.ts, the per-depth
timeoutMs values in adjacent/prompts.ts, the wiki-compile timeout in
md.ts) are sized for a baseline local CLI with no startup overhead. In
environments where the configured engine has heavy startup cost — MCP
servers, SessionStart hooks, high reasoning effort — the deadlines fire
before the child has responded, producing non-deterministic "claude/
codex timed out after Ns" failures even on small prompts.

The fix is a single uniform multiplier applied at the engine layer so
every caller (classify, wiki compile, Possible runs) inherits it
without changes. Read on every call so tests can scope the override.
Non-numeric or non-positive values fall back to 1 silently.

No default change — opt-in only.

  export FT_TIMEOUT_MULTIPLIER=2   # doubles every timeout
  ft possible run --seed <id> --repo .

Two new tests pin the scaling behavior and the input-validation
fallback. All 545 tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant