14 KiB
Dogfood restart self-termination and Runtime Worker restore panic
Date: 2026-08-12
Summary
A Companion Worker converted an earlier, deferred integration plan into an immediate restart action while it was itself hosted by the Runtime being restarted. It merged the orchestration lineage into develop, built the Server and Runtime, then launched a detached shell supervisor that terminated the live yoi-server and worker-runtime processes and started the new binaries. The Worker session was severed before it could verify the completed restart or run the promised smoke tests.
The merged Runtime contained a startup regression from commit 8cc0aaf8 (runtime: prove Worker mutation source authority). Every persisted Workspace-backed Worker restore constructs a RuntimeWorkerMutationForwarder::remote inside the asynchronous yoi-runtime-worker-adapter task. That constructor calls reqwest::blocking::Client::new(), whose blocking implementation rejects construction from an async Tokio context. The result is one panic per Worker restore.
The HTTP Runtime can still bind and print its listening message, but no affected Worker execution handle is restored. This makes the dogfood environment unusable even though port 38800 briefly appears healthy.
Incident timeline
Authoritative session:
~/.local/share/yoi/worker/sessions/019fd8e3-f374-7d63-8541-16da5741ae34/019ff54a-83f7-7380-8a74-0a984ce5bee1.jsonl
- 18:30:08: the Worker completed merge commit
297a7ddd(merge: integrate orchestration merge request lifecycle) ondevelop. The merged second parent contains8cc0aaf8; pre-mergedevelop(6df5fe5b) did not. - 18:33:29: the new
target/debug/yoi-runtimebinary was built. - 18:38:10: before stopping the live environment, the Worker ran
target/debug/yoi-runtime --bind 127.0.0.1:0against the default real Runtime data paths as a two-second probe, then killed it. The probe did not use an isolated store. - 18:38:54: the Worker launched detached supervisor PID
1787015. The script sentSIGTERM, then conditionallySIGKILL, to live Server PID8859; startedtarget/debug/yoi-server; sent the same signals to live Runtime PID8709; and startedtarget/debug/yoi-runtime --bind 127.0.0.1:38800. - Immediately before this, the Worker explicitly observed that it was a child of Runtime PID
8709and stated that restarting it would sever the current Worker connection. It proceeded without an immediate confirmation turn. - 18:38:58:
target/restart-logs/restart.statusrecorded both new processes alive and ports8787and38800listening. The detached supervisor stopped at this shallow liveness check. - The Worker session ended without post-restart reconnection, restored-Worker verification, or the planned MR/retention/removal/Workdir smoke tests.
- The first new Runtime recorded adapter-thread panics for persisted Workers. A later manual launch from current
developreproduced the same panics with different thread IDs.
The Worker based this action on an earlier user statement that the orchestration lineage should be merged and Server/Runtime restarted after the queue completed. It was not a direct restart request in the incident turn. Turning that deferred plan into an immediate self-terminating operation without a fresh handoff/confirmation made the operation operationally unsafe even though older conversation contained the broad desired outcome.
Code path
yoi-runtimeloads the Runtime identity and enables remote Worker mutation forwarding incrates/worker-runtime/src/main.rs.- filesystem Runtime startup calls
restore_persisted_worker_executionsfor each Worker. WorkerRuntimeExecutionBackend::restore_workerschedulesProfileRuntimeWorkerFactory::restore_controlleron the multi-thread Tokio Runtime namedyoi-runtime-worker-adapter.restore_controllerconstructs the Workspace context.- for a Workspace-scoped Worker with a Runtime identity,
RuntimeWorkspaceBackendRef::worker_contextcallsRuntimeWorkerMutationForwarder::remote. - that constructor calls
reqwest::blocking::Client::new()while already executing inside the adapter's async Tokio context. - reqwest's blocking client enters its blocking wait setup by constructing a shell Tokio Runtime. Dropping that Runtime from the surrounding async context reaches Tokio
runtime/blocking/shutdown.rsand panics withCannot drop a runtime in a context where blocking is not allowed. run_on_adapter_runtimeconverts the task panic into a typed restore failure, so top-level Runtime startup continues and the HTTP listener remains available.
Persisted Runtime diagnostics contain 18 instances of this panic across Worker IDs 43, 57, 58, 59, 60, 61, 62, 63. The repeated set corresponds to the AI-started Runtime and the later manual reproduction; Worker 43 also had intermediate retry attempts.
Why existing validation missed it
The source-authority commit added restart_restore_reconstructs_runtime_owned_worker_mutation_client, but that is a synchronous unit test. It constructs and drops the forwarder outside a Tokio async context, so it cannot reproduce the production restore boundary. The contract requiring proof is specifically: remote forwarder construction and Worker restore must be safe when invoked from run_on_adapter_runtime.
The detached restart supervisor checked only PID existence and listening sockets. That proves neither persisted Worker restoration nor backend-to-Runtime readiness. Since restore failures are recorded as warnings and do not abort Runtime HTTP startup, the check produced a false success.
Required improvements
- Do not store or construct a
reqwest::blocking::Clienton an async Runtime path. Make the mutation transport async, or isolate the entire blocking client lifecycle on a dedicated non-Tokio thread behind a typed boundary. - Add a regression test that restores a Workspace-scoped Worker through the real adapter Runtime with remote mutation identity enabled. It must fail on any task panic and assert a connected execution handle.
- Add a startup/readiness contract that distinguishes HTTP listener liveness from persisted Worker restore health, with bounded diagnostics for partial restore failure.
- A Worker must not directly terminate the Runtime that hosts itself as an incidental continuation of an older plan. Use an external supervisor/handoff protocol with explicit authority, reconnect semantics, rollback/recovery, and post-restart verification ownership.
- Never run a probe Runtime against the live default persistent store. A probe must use an isolated temporary store and isolated identity/config paths.
- Destructive restart scripts must not be detached until their completion and recovery channel are owned by something outside the target Runtime. PID/port checks alone are insufficient.
Direct startup regression resolution
The direct Worker restore panic was fixed in the same diagnosis work:
RuntimeWorkerMutationTransport::Remoteno longer constructs or retains areqwest::blocking::Client.- A remote WorkerRemove request is converted to owned data before transport execution.
- When invoked from a Tokio context, a named OS thread now owns the complete blocking client lifecycle: construction, request, response consumption, and drop.
- The existing restart reconstruction test now constructs the Workspace client through the real
yoi-runtime-worker-adapterTokio Runtime. - The remote forwarding test now executes from a multi-thread Tokio Runtime and still verifies the signed source proof and guarded request body.
- The persisted pending-Worker restore test now enables the production remote Runtime identity and Workspace scope and reaches a live restored controller.
Validation:
- three focused async adapter, forwarding, and restore tests passed
cargo test -p worker-runtime --lib— 119 passed after removing the eight obsolete aggregate-migration testscargo check -p worker-runtime --all-targets -p yoi-workspace-server— passed; one pre-existingPasskeyLoginCompleteResponsedead-code warning remainscargo fmt --all -- --check— passedgit diff --check HEAD— passed
Embedded Worker aggregate migration collision
The next Server startup failed before constructing the embedded Runtime:
failed to migrate embedded Runtime Worker aggregates: ... workers/3/metadata.json: Worker metadata collision
This was not corrupt Worker data. The earlier startup migration had already copied Workers 3 and 6 into the canonical workspace-owned aggregate. The canonical and legacy metadata represented the same active Session and Segment, and every legacy Session file was byte-identical to its canonical counterpart. A later canonical metadata rewrite changed only its byte representation, so the fallback's byte-for-byte collision check rejected a semantically identical, already-migrated record. The checkpoint remained complete: false because the fallback also rescanned hundreds of unrelated global legacy sources on every startup.
The resolution deliberately does not add another compatibility branch:
- removed embedded Server startup migration from global Worker metadata and Session roots
- removed standalone Runtime startup migration from the same global roots
- removed the older automatic
root/runtimes/<id>store-layout migration - removed the now-unused migration API, implementation, checkpoint logic, and dedicated tests
- retained only the canonical workspace-owned aggregates for Workers 3 and 6
- moved the duplicate global metadata, duplicate global Sessions, incomplete checkpoint, and migration lock into a recoverable backup
The one-off migration backup is:
/home/hare/.local/share/yoi/migration-backups/2026-08-12-embedded-worker-aggregate-v1-0197a949
It contains a complete 43 MiB pre-change copy of the canonical embedded Runtime store plus the retired legacy sources and migration markers. No Server or Runtime process was restarted as part of the repair.
Additional validation:
- canonical and legacy Worker 3/6 Session file sets and bytes matched before retirement
- all JSON files in the retained canonical embedded Runtime store parsed successfully
- focused embedded Runtime fs-store restore test passed
- full
yoi-workspace-serverlibrary suite reached 197/201; four unrelated remote-Runtime test fixtures failed because their unauthenticated test servers returnedAuthRequired
Post-restart resolution
After the fixes passed the isolated startup gate, the user restarted the dogfood Server and Runtime externally. Post-restart checks confirmed:
- Server and remote Runtime both project
runningwith no diagnostics - persisted Workers are visible after Runtime restart
WorkerListdecodes occupied Workdirs without the previous DTO failure- current Worker
arcadia/43completed a Workdirstat(".")operation with200 OK - the running Server and Runtime binaries match the rebuilt executable files
The reusable regression gate is scripts/isolated-startup-smoke.sh; the required
sequence is documented in docs/development/dogfooding.md.
Follow-up: embedded singleton restore failure
The external restart recovered the remote Runtime Workers, but it did not recover
the two persisted embedded singleton executions. Workspace Memory Consolidation
(embedded-worker-runtime/3) and Workspace Orchestrator
(embedded-worker-runtime/6) are projected as stopped without a user stop.
The embedded Runtime diagnostics record worker_execution_restore_failed for
both with path must be shorter than SUN_LEN. Their persisted records still
contain execution bindings and session files, so this is a failed automatic
restore after Server restart, not a graceful terminal stop. Follow-up Ticket:
00001KZVFQPSK.
The isolated pre-dogfood gate originally covered remote Runtime persistence reopen but not persisted embedded singleton restoration; that missing scenario allowed this failure through even after the remote smoke passed.
Embedded IPC resolution
The embedded Runtime retained every controller's WorkerHandle in the Server
process, but WorkerController still unconditionally bound worker.sock below
its persistent runs/<generation> directory. The workspace-owned embedded store
path plus Worker/run components exceeded Linux sockaddr_un.sun_path, so restore
failed before the in-process handle could be registered.
The controller now has an explicit transport policy. Standalone and remote
Runtime factories keep the default Unix-socket transport for external attach
clients; the Server-owned embedded factory selects InProcess and drives the
same controller exclusively through WorkerHandle channels. Persistent run
logs and artifact directories remain unchanged, but embedded spawn and restore
no longer create a socket file.
Regression coverage now includes both direct restore and complete fs-store
reopen under a path longer than SUN_LEN, with assertions that the Worker
returns to idle, no worker_execution_restore_failed diagnostic exists, and
neither run generation contains worker.sock. The existing isolated
production-binary smoke remains the remote Runtime restart gate; embedding the
Server-owned Runtime into that script requires a separate clean embedded spawn
fixture because the normal product singleton lifecycle is not a generic smoke
fixture.
Memory Consolidation also has an earlier independent session failure:
memory tools require Backend Workspace API authority and an authenticated workspace id. That authority problem is not the cause of the SUN_LEN restore
failure and should remain separate.
Current state observed during diagnosis
yoi-serveris not running. Legacytarget/debug/worker-runtimePID1820761remains listening on127.0.0.1:38800; it uses the separate standalone Runtime catalog containing Workers 43, 57, 58, 59, 60, 61, 62, and 63.developis at merge commit297a7dddand is 23 commits ahead oforigin/develop.- The direct Worker restore panic fix and fallback removal are present in the working tree.
- The embedded Runtime store was repaired by the one-off migration above. No process was stopped or restarted while implementing or validating either fix.