Skip to content

fix(warmup): import scan deps synchronously to avoid agents-SDK import race - #1163

Open
thehashes wants to merge 2 commits into
usestrix:mainfrom
thehashes:fix/import-warmup-thread-race
Open

fix(warmup): import scan deps synchronously to avoid agents-SDK import race#1163
thehashes wants to merge 2 commits into
usestrix:mainfrom
thehashes:fix/import-warmup-thread-race

Conversation

@thehashes

Copy link
Copy Markdown

Summary

start_import_warmup() pre-imports the heavy scan dependencies on a background
daemon thread to overlap the cost with CLI startup I/O. This races the main
thread and crashes the scan at bootstrap on some hosts.

The warm-up imports strix.core.runner, which pulls in the agents SDK, whose
package graph has internal circular imports. CPython breaks import cycles by
exposing a partially initialized module, and that partial state is visible
across threads. When the warm-up thread and the main thread (warm_up_llm
imports agents.model_settings / agents.models.interface) import that graph
concurrently, they intermittently observe each other's partial agents package
and crash.

Symptom

Every scan aborts during _bootstrap_scan -> warm_up_llm with one of:

KeyError: 'agents'
ImportError: cannot import name 'AgentOutputSchemaBase' from partially
initialized module 'agents.agent_output' (most likely due to a circular import)

This reproduces reliably on some environments (observed consistently on WSL2 /
Kali, Python 3.12 and 3.13, with the current pinned openai-agents 0.19.x). The
per-module import lock does not prevent it, because the import system
deliberately hands out partial modules mid-cycle to avoid deadlock.

Fix

Import the warm-up modules synchronously on the calling thread instead of on a
background daemon thread. Warm-up only runs on the scan path, which eagerly
imports all of these modules anyway, so importing here adds no net work; it only
removes the unsound concurrent-import overlap.

  • start_import_warmup() now performs a run-once synchronous import guarded by
    the existing lock and returns None (its Thread return value was never
    used).
  • Module and function docstrings updated to explain why the background thread
    was unsafe.

Testing

  • Before: strix -n -t <target> ... aborts at bootstrap with KeyError: 'agents'.
  • After: the same command starts cleanly, connects configured MCP servers, and
    runs to completion.
  • start_import_warmup() has no other callers that use its return value, and
    there is no existing test that depends on the daemon-thread behaviour.

…t race

start_import_warmup() ran the heavy dependency pre-import on a background
daemon thread to overlap it with CLI startup I/O. That races the main
thread: the warm-up imports strix.core.runner, which pulls in the agents
SDK, whose package graph has internal circular imports. CPython breaks
import cycles by exposing a partially initialized module, and that partial
state is visible across threads, so when the warm-up thread and the main
thread (warm_up_llm imports agents.model_settings / agents.models.interface)
import that graph concurrently they intermittently crash with:

  KeyError: 'agents'
  ImportError: cannot import name 'AgentOutputSchemaBase' from partially
    initialized module 'agents.agent_output' (most likely due to a circular
    import)

This reproduces reliably on some hosts (e.g. WSL2) and aborts every scan at
bootstrap. The per-module import lock does not help, because the import
system intentionally hands out partial modules mid-cycle to avoid deadlock.

Warm-up only runs on the scan path, which eagerly imports all of these
modules anyway, so importing synchronously on the calling thread adds no net
work; it only removes the unsound concurrent-import overlap. Replace the
daemon thread with a synchronous, run-once import guarded by the existing
lock. start_import_warmup() now returns None (its Thread return value was
never used).
@greptile-apps

greptile-apps Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR makes scan dependency warmup synchronous and run-once to prevent concurrent imports from exposing partially initialized Agents SDK modules.

  • Replaces the daemon thread state with a _warmed flag protected by the existing lock.
  • Performs heavy imports directly on the caller and changes the helper return type to None.
  • Expands documentation explaining the import race and synchronous behavior.

Confidence Score: 4/5

The PR appears safe to merge, with a non-blocking cold-start regression for help, version, update, and setup invocations that should be addressed by moving synchronous warmup onto the confirmed scan path.

The import-race fix is coherent, but synchronous _warm() currently runs before argument parsing, forcing lightweight non-scan commands to wait for all heavy scan dependencies.

Files Needing Attention: strix/llm/warmup.py and strix/interface/main.py

Important Files Changed

Filename Overview
strix/llm/warmup.py The synchronous import removes the described cross-thread race, but the existing pre-parse call site now imposes heavy scan imports on several non-scan CLI paths.
Prompt To Fix All With AI
### Issue 1
strix/llm/warmup.py:69
**Synchronous imports delay non-scan commands**

`_warm(modules)` now blocks the caller, but the sole call occurs before argument parsing determines whether a scan will run. Consequently, cold `--help`, `--version`, `--update`, and interactive setup invocations wait for the full heavy scan dependency graph before responding; move the synchronous warmup onto the confirmed scan path.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(warmup): import scan deps synchronou..." | Re-trigger Greptile

Comment thread strix/llm/warmup.py
start_import_warmup() ran before parse_arguments(), so cold --help,
--version, --update and interactive setup paid the full scan import
graph. Now that warm-up is synchronous the early placement buys nothing;
move the call to the top of _bootstrap_scan(), which only runs when a
scan actually proceeds (not args.needs_setup).
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