Skip to content

fix: Windows compatibility (claude-cli provider, rapidfuzz wheel) and interview batch timeout - #26

Open
jcartagenamartinez-legaltech wants to merge 5 commits into
amadad:mainfrom
jcartagenamartinez-legaltech:fix/windows-compat-and-interview-timeout
Open

fix: Windows compatibility (claude-cli provider, rapidfuzz wheel) and interview batch timeout#26
jcartagenamartinez-legaltech wants to merge 5 commits into
amadad:mainfrom
jcartagenamartinez-legaltech:fix/windows-compat-and-interview-timeout

Conversation

@jcartagenamartinez-legaltech

Copy link
Copy Markdown

Summary

Three independent fixes discovered while running MiroFish end-to-end on
Windows 11 for a real investor-facing market simulation suite (multiple
runs, 100-185 agents each, LLM_PROVIDER=claude-cli):

  • Windows compatibility for the claude-cli provider (app/utils/llm_client.py):
    • cwd="/tmp" is POSIX-only and raises WinError 267 on Windows. Fixed with
      tempfile.gettempdir().
    • The prompt was passed as a trailing argv element, which blows past
      Windows' command-line length limit once a few input files are attached
      (WinError 206, "filename or extension is too long"). Fixed by passing
      the prompt via stdin instead. Also bumped the subprocess timeout
      300s → 600s and pinned encoding="utf-8", errors="replace" (Windows'
      default locale encoding, cp1252, was raising UnicodeDecodeError on
      non-ASCII content).
  • rapidfuzz Windows wheel (pyproject.toml / uv.lock): the resolved
    version (3.14.4) has no win_amd64 wheel, so uv sync fails outright on
    Windows. Pinned via [tool.uv] override-dependencies to 3.13.0, the last
    version with a Windows wheel.
  • Interview batch timeout (app/services/graph_tools.py): the
    post-simulation interview step (interview_agents, used by the report
    generator) hardcoded a 180s timeout for a batch of up to 5 agents × 2
    platforms of individual LLM calls. With CLI-based providers this
    consistently produced 0/N successful interviews — even on otherwise
    healthy runs — because the failure is swallowed as non-fatal and the
    report silently falls back to no interview data. Raised to 600s to match
    the LLM client's own per-call ceiling.

Why this matters

Without these three fixes, MiroFish does not run at all on Windows with the
claude-cli provider (setup fails at uv sync, then every LLM call fails
with WinError 267/206), and even once running, the interview step of
every report silently produces no data.

Testing

Ran the full pipeline (mirofish run --files ... --requirement ... --platform parallel --max-rounds 15) end to end on Windows 11 across several
simulations (100–185 agent profiles each), producing complete
verdict.json/report.md/visuals, and confirmed the interview step now
returns non-empty results after the timeout fix (previously 0/N on every
affected run).

Scope / non-goals

Kept intentionally minimal and surgical — no refactors, no new
abstractions, no behavior change on non-Windows platforms beyond the
(strictly larger) timeouts.

🤖 Generated with Claude Code

Two issues prevented the claude-cli LLM provider from running on
Windows:

- cwd="/tmp" is a POSIX-only path; on Windows this raises
  WinError 267 (invalid directory name). Replaced with
  tempfile.gettempdir() for both the claude and codex subprocess
  calls, which resolves correctly on all platforms.

- The prompt was passed as a trailing argv element
  (["claude", "-p", "--output-format", "json", prompt]). Large
  prompts (tens of KB, common once a few input files are attached)
  exceed Windows' command-line length limit and raise
  WinError 206 ("filename or extension is too long"). Passing the
  prompt via stdin instead removes this ceiling. Bumped the
  subprocess timeout from 300s to 600s to give slower/CLI-based
  providers headroom, and set encoding="utf-8", errors="replace"
  explicitly since Windows' default locale encoding (cp1252) was
  raising UnicodeDecodeError on non-ASCII input/output.

Tested on Windows 11 running full simulations (100+ agents,
15 rounds) end to end with LLM_PROVIDER=claude-cli.
rapidfuzz's latest release at time of writing (3.14.4) has no
win_amd64 wheel published, which makes `uv sync` fail outright on
Windows (no sdist build toolchain assumed). Overriding to 3.13.0,
the latest version with a Windows wheel, unblocks installation.

This can be relaxed/removed once upstream publishes Windows wheels
for a newer version again.
interview_agents() (used by the report generator to interview the
top N most-active agents post-simulation) sends a batch IPC command
with a hardcoded 180s timeout covering up to N agents x 2 platforms
of individual LLM interview calls.

With CLI-based LLM providers this was consistently timing out with
0/N successful interviews on runs with 100+ agent profiles, even
though the simulation itself completed normally — the report would
silently fall back to "(no interview data)" since the failure is
caught as non-fatal by the caller.

180s doesn't leave enough headroom when each individual call can
legitimately take up to the LLM client's own per-call ceiling (600s
in llm_client.py, e.g. for claude-cli subprocess spawns). Raised to
match that ceiling.
Addresses self-review findings on the original three commits:

- Single CLI_CALL_TIMEOUT_SECONDS constant governs both providers and
  every timeout error message (the claude handler still said "300s"
  after the ceiling moved to 600; codex was left at 180 with no
  encoding handling — same cp1252 hazards the claude path already
  fixed).
- Interview batch budget now scales with the batch instead of a flat
  600s: one full per-call ceiling per interviewed agent. A fixed
  budget equals the worst case of a single call and still starves
  multi-agent batches.
- rapidfuzz override scoped with sys_platform == 'win32' marker so
  macOS/Linux keep the resolver's pick; pyproject now documents why
  the pin exists and when to drop it (the missing wheels are
  specifically Windows cp312, not all of win_amd64).
- Provider binaries resolved via shutil.which for a clear early error
  when the CLI is missing (and to tolerate npm-style .cmd shims on
  Windows).
- Warn when undecodable bytes were replaced with U+FFFD instead of
  corrupting output silently.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
jcartagenamartinez-legaltech added a commit to jcartagenamartinez-legaltech/mirofish-cli that referenced this pull request Jul 19, 2026
- Document the ExecutionPolicy Bypass invocation (stock Windows blocks
  .ps1 execution outright — it is the first command the target user
  will run) in both the script header and the README.
- Fail fast with a clear message when stdin is not interactive and
  --yes/-Yes was not passed, instead of dying on EOF at the uv prompt.
- On Windows, explain the known uv sync failure mode (rapidfuzz >=3.14
  without cp312 wheels, fix proposed in amadad#26) and its workaround
  (uv python pin 3.11) instead of a bare "failed".
- Read .env back with the same BOM-less UTF-8 used to write it
  (Get-Content -Raw on PS 5.1 decodes BOM-less files as ANSI and would
  corrupt non-ASCII values on the read-modify-write cycle).
- Gate ANSI colors on a real TTY and honor NO_COLOR; replace the
  fragile grep-the-header --help with an explicit usage() heredoc.
- Show the claude CLI version on Windows too (parity with setup.sh).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@jcartagenamartinez-legaltech

Copy link
Copy Markdown
Author

Pushed a hardening commit after running an adversarial review pass over my own diff (multi-agent panel + manual verification):

  • Single CLI_CALL_TIMEOUT_SECONDS constant now governs both providers and every timeout message (the claude handler still said "300s"; codex was left at 180s without the UTF-8 handling).
  • The interview batch budget scales with the number of interviewed agents instead of a flat 600s (a flat budget equals the worst case of a single call).
  • The rapidfuzz override is now scoped with sys_platform == 'win32' so macOS/Linux keep the resolver's pick, and the pin is documented in pyproject.toml with its exit condition. To be precise: the missing wheels are specifically Windows cp312 (cp311 wheels exist for 3.14.4).
  • Provider binaries resolve via shutil.which (clear early error when missing; tolerates npm-style .cmd shims on Windows), and U+FFFD substitutions are logged instead of silent.

Happy to split or adjust anything.

- LLMTimeoutError (RuntimeError subclass) raised on TimeoutExpired and
  excluded from the x3 retry loop: a prompt that exhausts the 600s
  ceiling will almost certainly exhaust it again, so retrying turned
  one slow call into ~30 minutes of blocked wall clock.
- _resolve_cli cached with lru_cache: the binary does not move during
  the process lifetime, so the per-request PATH scan was waste.
  (Failures are not cached: lru_cache does not memoize exceptions.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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