An exception escaping a non-interactive cycle re-raised before the status
handling, so a dying child stayed 'running' and its parent waited out the
timeout on a completion report the child could no longer send. Set the
terminal status and wake the parent on the way out too.
A tool call for a name Strix does not register raised ModelBehaviorError
from the SDK turn resolver, which nothing retries: the root agent's raise
tore down the whole scan and a sub-agent died before its status was set.
Opt into the SDK's tool_not_found_behavior="return_error_to_model" so the
unknown call comes back as a tool result and the agent self-corrects.
The setting landed in openai-agents 0.19.0, which requires openai>=2.45,
so both pins move.
verify_dependency does blocking HTTP (requests.post) and was called directly from
the async create_dependency_report path — a slow/unreachable provider would block
the shared agent event loop for the request timeout, stalling every concurrent
agent (worst case two sequential calls on the clean-version path).
- Offload the call via asyncio.to_thread so the event loop stays free.
- Tighten the provider timeout 20s -> 10s (named OsvProvider._TIMEOUT_S): a single
advisory lookup shouldn't hold a worker thread that long, and it fails open fast.
- Add an async test asserting the verifier runs on a worker thread (not the main/
event-loop thread) and the reject still propagates.
Addresses the Greptile review comment on #963. 38 report/dep tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Refine the empty-result handling: when the provider returns no advisories for the
installed version, distinguish two cases via a new AdvisoryProvider.knows_package
(version-less query):
- provider KNOWS the package (advisories on other versions) but none affect this
version -> CONFIDENT out-of-range -> reject (e.g. minimist 1.2.6 cited for
CVE-2021-44906, which was fixed in 1.2.6 — OSV knows minimist, 0 affect 1.2.6).
- provider doesn't know the package at all -> genuine coverage gap (private /
vendored / provider lag) -> still fail-open, emit.
Without this the most common dependency false positive — a real CVE cited against
a version that's already patched past its range — slipped through as a 'coverage
gap'. Surfaced by a live scan: a weak model, coerced into over-claiming patched
minimist CVEs, had them emitted; now they're correctly rejected while the genuinely
in-range lodash CVEs still emit.
Also refactor OsvProvider to a shared _query helper (used by both affecting and
knows_package). 18 tests.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
An in-range (real) finding was emitted silently, giving a scan log no evidence the
version-range check ran. Add an INFO line on the confirmed-emit path, mirroring the
reject / coverage-gap logs.
* perf(cli): fast startup — lazy heavy imports + onedir standalone build
* perf(cli): drop legacy single-file compat from install/self-update
* perf(cli): simplify — drop constants module and extra lazy-import refactors
* refactor(update): strix --update just re-runs the install script
* perf(cli): drop packaging/install/update changes; deepen lazy imports instead
Reverts the onedir build, install.sh, and self-update changes so release
mechanics stay untouched. Startup cost is addressed purely by deferring
heavy imports (agents/openai, config.models, report state/writer, docker)
until a scan actually runs; DEFAULT_MAX_TURNS moves to strix.config.settings
so argparse no longer pulls the agents SDK.
---------
Co-authored-by: Ahmed Allam <ahmed39652003@gmail.com>
One tool was doing three jobs (wait on the user, wait on other agents, and
- wrongly - wait for a long-running command), so the driver had to guess which
one an agent meant and used parent_id as the proxy: the root waits for a human,
everyone else waits for agents. That proxy is wrong, since the user can message
any agent from the TUI's agent tree.
Tool identity now carries the intent, and the coordinator records it as a
wait_kind that survives snapshot/restore:
respond_to_user -> wait_kind="user", never auto-resumed (root or not)
wait_for_agents -> wait_kind="agents", auto-resumed on a 300s timer
recovery exhaust -> wait_kind="stalled"
respond_to_user fuses the message and the yield into one call, so there is no
way to answer and then forget to stop - the two-step that gpt-4o-mini skipped
2/2 in live testing. Plain text still renders as before.
Auto-resume is also bounded now: an agent that re-parks after every timeout
burned a model turn every 300s for the rest of the scan (and, since parked
children notify their parent, spammed the parent's inbox on the same cycle).
After _MAX_IDLE_AUTO_RESUMES it stays parked until a real message arrives.
The user can message any agent from the TUI, not only the root, so the
justification is that the parent is an agent with no other way to learn
the child parked - not that the child has no human resumer.
Parking is self-service only for the root, which the user is watching.
A parked child owes its parent a report it can no longer send, so the
parent would wait out its full timeout for nothing.
A mutual wait between two agents resolves only when both hit their cap,
so the ceiling is the worst-case idle burn. Name the constants instead of
repeating the literal, and align the interactive auto-resume timeout.
An exhausted agent parked in 'waiting' got a fresh nudge budget on every
600s auto-resume, so a wedged agent could nudge-park-nudge indefinitely.
Track the count on the coordinator, snapshot it, and reset it only on
real input or an explicit lifecycle tool.
Interactive turns ended by plain text left the agent parked in 'waiting'
forever. Require an explicit lifecycle tool in both modes and nudge a
text-only turn back into a tool call, bounded by a recovery limit.
A dependency-CVE false positive is a factual question — is the installed version
actually in the advisory's affected range? — not a code-reasoning one. Today
create_dependency_report files whatever the agent passes; an agent can report a
CVE against a version that's already patched past the range, or mis-attribute a
CVE to the wrong package. This adds an opt-in deterministic check (no LLM) right
before the dependency report is persisted, as a sibling of the existing
dedup-reject: ask an advisory provider which advisories affect the exact installed
version; if the cited CVE/GHSA isn't among them, reject the finding as out-of-range.
- strix/report/dep_verify.py: AdvisoryProvider protocol + OsvProvider (OSV.dev
/v1/query). No new dependency — uses requests (already core).
- Provider-pluggable via DepVerifySettings: STRIX_DEP_VERIFY (default off),
STRIX_DEP_VERIFY_PROVIDER (osv|none), STRIX_OSV_URL (point at a self-hosted OSV
mirror for air-gapped / data-residency deployments — identical query contract).
- Fail-open + asymmetric (never suppress a real finding): rejects ONLY on a
definitive, non-empty provider answer that omits the cited advisory. Provider
unreachable/error, empty result (coverage gap), missing fields, non-CVE/GHSA id,
unknown ecosystem -> emit.
- Off by default; when disabled the dependency path is unchanged.
Tests inject a fake provider (no network) + cover provider resolution and the OSV
response parse. 16 tests.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
LiteLLM treats provider-qualified metadata lookups as an auth path.
Use the underlying model slug so context sizing cannot block the scan
loop in a device-code poll.
Adds an integration test that drives Runner.run_streamed against a
non-streaming gateway through _NonStreamingModel: the synthetic terminal
event feeds the runner, which executes the tool call and continues to a
final answer over two non-streaming turns. Removes the README env-var note.
Some OpenAI-compatible gateways don't support Server-Sent Events (or
deliver them unreliably), but the SDK run loop Strix uses only issues
streamed requests, so such a gateway fails every turn. Add an opt-in
LLM_DISABLE_STREAMING setting that wraps the resolved model in
_NonStreamingModel: each turn makes one non-streaming get_response and
replays the completed result as a single terminal stream event, so tool
calls, usage, and the rest of the agent loop are unchanged. Subscription
(ChatGPT) models are always streamed and are not wrapped.