fix(sdk): reuse authored CLI probes for each run so concurrent f.llm never outlives its lease (#561) - #576
Conversation
The regression added by #517 declares `cwd` on a spec it hands to `preflight`, and used an `os.tmpdir()` directory — an absolute path. #566 landed one commit earlier and made an absolute `cwd` a compile refusal (`agent-cwd.ts`: a declared `cwd` is run-root-relative, the same rule `relayflowd_core::spec::is_run_root_relative_path` applies at the kernel boundary). Each PR was green alone; together they are not, and `main` at e30226c fails this test with `invalid_spec` where it expects `gate_path_unscanned`. The fixture now makes its directory inside the run root and declares the relative name. Nothing else moves: the warning, `ok`, the real `AgentWorker` dispatch, the empty scan snapshot, the journaled JSON output and the lowered gate command are asserted exactly as before. Reverting this file to its e30226c bytes fails the case and restoring it passes; both captures are in evidence/561/artifact-gates-{red,green}.txt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…vidence The repository check on this branch failed with 30 tests across five files. None came from #561. Each is classified by reverting this branch's three source files to origin/main (e30226c), re-running and restoring: * artifact-gates (1) — main is red; #566 and #517 conflict semantically. Fixed in the preceding commit. * live-kernel (7) — this sandbox's HOME declares "type": "commonjs" above the checkout, so testdata/preflight's extensionless ESM fixture CLIs load as CommonJS and emit nothing, silently. Local setup only. * authored-node-runtime — Bun 1.3.6 where every workflow pins 1.4.0. * hosted-extension (22) — unprivileged user namespaces denied to this container; bwrap cannot run even once installed. The mutation is re-run at this head. The mutated run reproduces the issue's exact signature — lease_expired on a first attempt that never heartbeated, retry, second attempt success — and the restore is byte-identical by SHA-256. The live-Claude repro could not be re-run: this environment's claude is no longer authenticated. The capture says so rather than the acceptance box claiming a pass it cannot show. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The committed mutation transcripts were captured before the artifact-gate fix (1ccaaed) landed. Re-run `evidence/561/mutation.patch` against the current head so the transcript matches the bytes a reviewer checks out, and record the restore with `git diff --exit-code` plus a sha256sum. The failing capture carries the issue's exact journal shape at both capacity 1 and the default: attempt 1 completes `lease_expired` with `wallclock_ms: 30011`, a `retry_backoff` sleep follows, and attempt 2 succeeds. Restored, all five cases pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Shepherd decision (2026-09-24): I compared this branch with duplicate #564. #576 is the safer starting point: its exact head 909a1d2 typechecks ( |
Session-Id: 01a0d409-e854-7540-a76e-a2f9cd136946
# Conflicts: # packages/sdk/tests/artifact-gates.test.ts Session-Id: 01a0d409-e854-7540-a76e-a2f9cd136946
Session-Id: 01a0d409-e854-7540-a76e-a2f9cd136946
|
Addressed Cursor exact-head findings from 06528e6: (1) sync probe spawnSync now uses stdio [ignore,pipe,pipe], preserving the previous non-blocking stdin contract; (2) checkAuthoredFlow accepts both the positional ProjectConfig API and the legacy {projectConfig, probeCache, communicationChecked} options shape. Local SDK typecheck/typecheck:tests and communication/authored-preflight/cli-probe/preflight-cache tests pass. Pushed 8ae646aa3a5c7f625331478a9eb6bbf35aa12b0d; awaiting fresh exact-head CI. |
Session-Id: 01a0d409-e854-7540-a76e-a2f9cd136946
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3656b13. Configure here.
Session-Id: 01a0d409-e854-7540-a76e-a2f9cd136946
khaliqgant
left a comment
There was a problem hiding this comment.
Independent exact-head review of 2dab6ae: focused SDK typecheck/typecheck:tests passed; communication-preflight (13), authored-preflight (3), cli-probe (6), preflight-run-cache (1) passed; required validate 36058480705, packed-consumer 36058480717, guard 36058478311, linux-x64-artifact 36058480822 all passed. Async probes preserve stdin isolation and legacy/options-bag compatibility. No substantive blocker found.

Closes #561.
The stale dispatch
authoredWorkerRunnerruns the declarative preflight (checkAuthoredFlow)per authored
f.llm/f.agentcall, and that preflight real-probes the CLI —claude auth statusand friends — synchronously. Nine concurrent callstherefore spawned nine blocking probes on the one Node event loop.
WorkerSlotswas never the problem: it bounds admission correctly, andoverflow calls do wait before
run.start. The damage is done to the calls thatwere admitted. While the remaining probes block the loop, the worker cannot
service its dispatch or send a heartbeat, so the kernel sees a lease started and
never renewed. At +30s it journals
lease_expired, retries, and the worker'swithWorkerLeasethrowsAgent lease is already expiredon the stale dispatchit finally picks up.
The fix
Give each authored run one
cliProbeCache(and onereadProjectConfig) sharedacross its calls — exactly what declarative preflight already does for a whole
flow, where a single
preflight()call caches probes across every step. Thecache is per run and holds refusals as well as passes, keyed on CLI, model and
resolution source (
packages/sdk/tests/preflight-run-cache.test.tspins that).One probe is paid; the other eight calls resolve from cache and reach the worker
well inside the lease.
Three files change, 22 lines net:
packages/sdk/src/preflight.ts— accept an optionalcliProbeCache; exportCliProbeOutcome.packages/sdk/src/cli/check.ts— thread it throughcheckAuthoredFlow.packages/sdk/src/authored-worker-step.ts— one cache per runner.Also in this branch:
tests/artifact-gates.test.tsdeclared an absolutemkdtempSynccwd, which #566 turned into a compile refusal one commit before#517 added the fixture.
mainis red at that merge, on this branch and anyother cut from it. The fixture now makes its directory inside the run root and
declares the relative name; no assertion changed.
Acceptance
f.llmcalls against the real kernelwith a fake CLI, no
lease_expiredin any child journal, at capacity 1 andthe default —
packages/sdk/tests/authored-probe-cache.test.ts. It readsevery child journal through
journalReadand asserts exactly onestep.attempt.startedper run, and that peak concurrent sessions neverexceed
capacity.fifth
checkAuthoredFlowargument (evidence/561/mutation.patch) fails allfive cases, and the journal shows the reported shape verbatim — attempt 1
"completionReason":"lease_expired"with"wallclock_ms":30011, aretry_backoffsleep, attempt 2"completionReason":"success". Restoring(
git diff --exit-codeclean, sha256b9cd463d9f1ee274175a0b659119262c0ba5b18f1afc11b866c3e0f7f34023b7) returns5 passed. Captures:
evidence/561/mutation-red-at-head.txt,evidence/561/mutation-green-at-head.txt, walked through inevidence/561/README.md.evidence/561/live-default.txtandevidence/561/live-one.txt, both exit 0, journals asserted byevidence/561/check-live-journals.py. Captured while this environment hadauthenticated Claude. It no longer has it, so this was not re-confirmed
at head;
evidence/561/live-one-recheck-unauthenticated.txtis theunauthenticated re-run, which still shows the mechanism from outside (one
1.04s probe, then eight refusals in 0.01–0.12s). The kernel-backed regression
covers the same behaviour with a fake CLI and needs no credentials.
Promise.all— not done here.examples/prompt-labdoes not exist in this repository; it lives onfeat/examples-prompt-lab. It needs a follow-up there once this ships(
evidence/561/prompt-lab.txt).Check status
.relayflow/check.shis green except for 22 cases intests/hosted-extension-{isolation,protocol}.test.ts, which need a realbubblewrap sandbox. This container denies unprivileged user namespaces and the
AppArmor sysctl cannot be relaxed from inside it, so installing bubblewrap only
changes the error. Unrelated to this change and classified in full, with the
commands, in
.relayflow/repair-notes.md.🤖 Generated with Claude Code
Note
Medium Risk
Changes the preflight path on every authored LLM/agent worker step and timing-sensitive lease behavior; mitigated by kernel-backed regression tests and mutation verification, but still core execution infrastructure.
Overview
Fixes worker lease expiry when many concurrent
f.llm/f.agentcalls each ran synchronous CLI auth/model probes before dispatch. Nine probes on one Node loop blocked heartbeats; admitted steps hitlease_expired(~30s) and staleWorkerLeaseLostError.Per authored run, a new
authoredPreflightpath shares one probe cache (and project config read), dedupes in-flight probes, and runs live checks viaprobeCliAsyncso the event loop can serve worker I/O. Probe implementation moves tocli/cli-probe.ts;checkAuthoredFlowaccepts an optional probe cache / options bag for the same facts declarative preflight already caches per flow.Adds
authored-probe-cache(and related) tests plusevidence/561/transcripts (baseline, mutation patch, live repro) documenting the failure shape and verification.Reviewed by Cursor Bugbot for commit 2dab6ae. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Fixes concurrent
f.llm/f.agentruns each paying a synchronous CLI auth probe before worker admission. Slow probes blocked the worker's event loop, so issued 30-second leases were never heartbeated and expired, raisingAgent lease is already expiredon a stale dispatch.Each authored run now probes once and shares results across its calls. A new
authoredPreflightkeeps a per-run probe cache (keyed on CLI, model, and resolution source; failures cached too) and reads project config once. Probe logic moved intocli/cli-probe.ts; live-flow probes run asynchronously viaprobeCliAsyncso the loop yields, and asynchronous probes isolate stdin from concurrent calls.checkAuthoredFlownow takes an options bag, and authored option bags without aprojectConfigkey are still detected and never become config. The synchronous public API is unchanged.Bug Fixes
artifact-gatesdeclared an absolute tempcwd, which is now a compile refusal; the fixture's directory is created inside the run root with a relative name. The merge withmainresolved the conflict with the revert of thesummary.mdworking file.Tests
authored-probe-cacheruns 9 concurrent LLM calls against the real kernel with a fake CLI at capacities 1 and 4, asserting nolease_expiredand one probe per run.authored-parallel-llm,authored-preflight,cli-probe, andpreflight-run-cachecover the new cache, sync/async probe drivers, and cache key shape.lease_expiredon attempt 1 across all five cases;evidence/561/holds the transcripts.Written for commit 2dab6ae. Summary will update on new commits.