{"record":{"id":"128b8aa79613710b","repo":"astrid-runtime/astrid","slug":"daemon-is-still-starting-after-timeout-secs-seco","errorCode":null,"errorMessage":"Daemon is still starting after {timeout_secs} seconds; it was left running. Check logs or run `astrid status` / retry later.{}","messagePattern":"Daemon is still starting after (.+?) seconds; it was left running\\. Check logs or run `astrid status` / retry later\\.(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/astrid-cli/src/commands/daemon.rs","lineNumber":134,"sourceCode":"    let mut child = cmd\n        .spawn()\n        .context(\"Failed to spawn background Kernel daemon\")?;\n\n    // Poll for the readiness sentinel instead of the socket file.\n    // The readiness file is written only after load_all_capsules()\n    // completes (including await_capsule_readiness()), so the accept\n    // loop is guaranteed to be running by the time we connect.\n    let timeout_secs = configured_spawn_timeout_secs(workspace_root);\n    match wait_for_ready(ready_path, &mut child, timeout_secs).await {\n        ReadyWaitOutcome::Ready => Ok(child),\n        ReadyWaitOutcome::ChildExited(status) => {\n            anyhow::bail!(\"Daemon exited prematurely ({status}).{}\", log_hint());\n        },\n        ReadyWaitOutcome::StillRunning => {\n            // Do not SIGKILL a live first cutover (layout-1 audit import\n            // can outlive the wait). Disown and tell the operator.\n            disown_if_still_running(child);\n            anyhow::bail!(\n                \"Daemon is still starting after {timeout_secs} seconds; it was left running. Check logs or run `astrid status` / retry later.{}\",\n                log_hint()\n            );\n        },\n    }\n}\n\nfn ephemeral_daemon_command(daemon_bin: &Path, workspace_root: &Path) -> std::process::Command {\n    let mut cmd = std::process::Command::new(daemon_bin);\n    cmd.arg(\"--ephemeral\")\n        .arg(\"--workspace\")\n        .arg(workspace_root)\n        .env(\n            \"ASTRID_WORKSPACE_STATE_DIR\",\n            crate::workspace_layout::current().state_dir_name(),\n        );\n    cmd\n}","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/daemon.rs#L116-L152","documentation":"If the freshly spawned daemon is still alive but has not written its readiness sentinel within the configured spawn timeout, wait_for_ready returns StillRunning and this error is raised. The CLI deliberately does not kill the child (a first-run layout-1 audit import can outlive the wait); it disowns the process and tells the operator to retry later rather than losing a healthy boot in progress.","triggerScenarios":"spawn_daemon -> spawn_daemon_inner with wait_for_ready exhausting timeout_secs while the daemon process keeps running — typically a slow first cutover/audit import, a large workspace, or an overloaded machine.","commonSituations":"First run on a big existing workspace where layout migration/import takes minutes; slow disk or container CPU throttling; spawn timeout configured too low (configured_spawn_timeout_secs); readiness file path on a filesystem with delayed visibility (e.g. some network mounts).","solutions":["Wait and retry: the daemon was left running; run `astrid status` until it reports ready, then re-run your command.","Increase the configured spawn timeout (the env/config knob behind configured_spawn_timeout_secs) on slow machines or large workspaces.","Check the daemon log to confirm it is performing a first cutover/import rather than deadlocked.","If the daemon is actually wedged (no log progress), kill it and run `astrid restart` for a clean boot."],"exampleFix":"# before: default timeout too small for a big first import\n$ astrid start   # error after 30s\n# after\n$ ASTRID_SPAWN_TIMEOUT_SECS=600 astrid start  # or: wait and `astrid status`","handlingStrategy":"retry","validationCode":"// Confirm the readiness file is merely slow (not a failed boot) before retrying\nfn boot_alive_and_pending(ready_path: &Path, pid_path: &Path) -> bool {\n    recorded_daemon_pid_is_alive() && !ready_path.exists()\n}","typeGuard":null,"tryCatchPattern":"match spawn_daemon_inner(ready_path, announce, None).await {\n    Err(e) if e.to_string().contains(\"still starting after\") => {\n        // Daemon was disowned, not killed. Poll readiness ourselves with a longer budget.\n        for _ in 0..120 {\n            if ready_path.exists() { return ensure_daemon_workspace_matches(None).await; }\n            tokio::time::sleep(Duration::from_secs(5)).await;\n        }\n        anyhow::bail!(\"daemon still not ready after extended wait; check logs or run `astrid status`\")\n    },\n    other => other,\n}","preventionTips":["Increase the spawn timeout (configured_spawn_timeout_secs) on slow machines or large first-run imports.","Expect slow first cutover/audit imports; poll `astrid status` instead of immediately restarting.","Watch the daemon boot log to confirm import progress before assuming a hang.","Only kill the disowned daemon if the log shows no progress for an extended period."],"tags":["daemon","timeout","startup","slow-boot"],"backgroundTag":"daemon-startup-timeout","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}