{"record":{"id":"ea14ae4619c6f569","repo":"xai-org/grok-build","slug":"process-scope-already-closed-fetch-killed","errorCode":null,"errorMessage":"process scope already closed; fetch killed{}","messagePattern":"process scope already closed; fetch killed(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/codegen/xai-grok-workspace/src/restore_fetch.rs","lineNumber":379,"sourceCode":"                let _ = child.wait_timeout(FETCH_KILL_WAIT);\n                return Err(err).context(\"creating fetch process group\");\n            }\n        };\n        if let Err(err) = group.attach_std(&child) {\n            let _ = child.kill();\n            let _ = child.wait_timeout(FETCH_KILL_WAIT);\n            return Err(err).context(\"attaching fetch to process group\");\n        }\n        let group = Arc::new(group);\n        if !global_process_scope().register(&group) {\n            let mut spawned = Self {\n                child: Some(child),\n                group,\n                stderr: Some(stderr),\n                abandoned: false,\n            };\n            let shutdown = spawned.shutdown();\n            bail!(\n                \"process scope already closed; fetch killed{}\",\n                format_shutdown_suffix(shutdown.as_ref())\n            );\n        }\n        Ok(Self {\n            child: Some(child),\n            group,\n            stderr: Some(stderr),\n            abandoned: false,\n        })\n    }\n\n    fn wait_success(&mut self, timeout: Duration, spec: &str) -> Result<()> {\n        let child = self.child.as_mut().context(\"fetch child already reaped\")?;\n        match child.wait_timeout(timeout) {\n            Ok(Some(status)) => {\n                self.child.take();\n                let stderr = self.take_stderr();","sourceCodeStart":361,"sourceCodeEnd":397,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-workspace/src/restore_fetch.rs#L361-L397","documentation":"FetchChild::spawn registers the spawned git fetch's process group with the global ProcessScope after starting the child. If the process scope has already been closed (global shutdown/teardown ran), registration fails; spawn immediately shuts the just-started fetch down (killing the process group) and throws this error, with a suffix (format_shutdown_suffix) reporting how the kill went. It signals 'the runtime is shutting down, this fetch never ran'.","triggerScenarios":"Calling any restore-fetch path that spawns git fetch (directly or through run_with_deadline / idle_withheld_while_producer_in_flight-style flows) after the global process scope was closed: the process is shutting down, a Ctrl-C/shutdown hook fired between spawn and register, or a teardown path races a still-running restore that attempts one more fetch.","commonSituations":"User hits Ctrl-C while a restore is mid-flight and the operation attempts one final fetch; process exit handlers/teardown (kill_all) run concurrently with a lingering restore task; long-running daemons closing the scope during graceful shutdown while worker threads still call fetch.","solutions":["Stop retrying fetches once shutdown is signalled: check the shutdown/cancellation flag before invoking restore-fetch paths and exit the loop gracefully","Treat this error as benign during shutdown — swallow/log it and let the interrupted operation be retried after restart","Serialize teardown: ensure the process scope is only closed after all restore workers have joined, or guard fetch calls with the same lifetime as the scope","Rerun the restore after restart; nothing was fetched (the child was killed immediately), so no partial state needs cleanup"],"exampleFix":"// before: one last fetch races scope shutdown\nensure_commits_reachable(repo, &head, &base)?;\n// after: skip fetch work once the scope is closing\nif process_scope_is_closed() {\n    tracing::info!(\"shutdown in progress; skipping restore fetch\");\n    return Ok(EnsureCommitsOutcome::SkippedInvalidOid);\n}\nensure_commits_reachable(repo, &head, &base)?;","handlingStrategy":"try-catch","validationCode":"fn can_start_fetch() -> bool {\n    !shutdown_requested() && process_scope_is_open()\n}\n// call before restore work: if (!can_start_fetch()) { log::info!(\"shutting down; skipping fetch\"); return; }","typeGuard":null,"tryCatchPattern":"match ensure_commits_reachable(repo, &head, &base) {\n    Err(e) if e.to_string().starts_with(\"process scope already closed\") => {\n        // shutdown raced the fetch; the child was killed and nothing was fetched\n        tracing::info!(\"restore fetch aborted by shutdown: {e:#}\");\n        Ok(()) // benign during teardown; retry after restart\n    }\n    Err(e) => Err(e),\n    Ok(outcome) => Ok(outcome),\n}","preventionTips":["Check the shutdown/cancellation flag before each fetch in restore loops and exit early","Join all restore worker threads before closing the global ProcessScope so teardown never races in-flight fetches","Make fetch attempts cancellable via a shared token checked between commits","After this error, simply rerun the restore post-restart — the killed fetch leaves no partial state"],"tags":["git","shutdown","process-management","race-condition","lifecycle"],"backgroundTag":"process-scope-closed","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}