Hmbown/CodeWhale · error · anyhow::Error

Persistent service {id} has no releasable process id

Error message

Persistent service {id} has no releasable process id

What it means

Committing persistent ownership requires each service to expose a releasable OS process id from its `ShellChild` handle. This error means `shell.child` is gone or carries no pid while the job is still flagged PersistPending and Running — the child handle was dropped or reaped, so ownership cannot transfer and the pending process-group registration cannot be cleaned. It is a state-invariant failure inside the manager, not an input error.

Source

Thrown at crates/tui/src/tools/shell.rs:2896

            let shell = self
                .processes
                .get_mut(id)
                .ok_or_else(|| anyhow!("Persistent service {id} disappeared before commit"))?;
            shell.poll();
            if shell.status != ShellStatus::Running {
                return Err(anyhow!(
                    "Persistent service {id} exited before ownership transfer (status {:?}, exit code {:?})",
                    shell.status,
                    shell.exit_code
                ));
            }
            if shell
                .child
                .as_ref()
                .and_then(ShellChild::process_id)
                .is_none()
            {
                return Err(anyhow!(
                    "Persistent service {id} has no releasable process id"
                ));
            }
        }

        let mut receipts = Vec::with_capacity(ids.len());
        for id in ids {
            let mut shell = self
                .processes
                .remove(&id)
                .ok_or_else(|| anyhow!("Persistent service {id} disappeared during commit"))?;
            let pid = shell
                .child
                .as_ref()
                .and_then(ShellChild::process_id)
                .ok_or_else(|| anyhow!("Persistent service {id} lost its process id"))?;
            unregister_pending_persistent_process_group(pid);
            shell.ownership = ShellOwnership::Released;

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Only create persist:true jobs through the normal spawn path so a child handle always exists
  2. Do not hand-construct BackgroundShell values outside ShellManager
  3. Treat occurrence as an internal bug: report the task_id with a reproduction
  4. After the failure, verify no orphaned process group remains (the pending registration was not unregistered)
Defensive patterns

Strategy: try-catch

Try / catch

match manager.commit_persistent_services() {
    Ok(receipts) => receipts,
    Err(err) if err.to_string().contains("no releasable process id") => {
        // internal invariant: child handle gone while flagged Running — report as a bug
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: A PersistPending BackgroundShell whose `child` is None (hand-constructed values, test seeds) or whose child was reaped before the commit pass while status still reads Running.

Common situations: Test fixtures inserting shells without children; platform-specific reaping paths that clear the pid without flipping the status.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/c0dba9fb337916a6. Report an issue: GitHub.