astrid-runtime/astrid · error
workspace commit for capsule
Error message
workspace commit for capsule '{id}' failed: {e} What it means
Thrown in `commit_workspace_for` when the capsule's copy-on-write workspace promote (commit) or rollback fails. The kernel wraps the underlying workspace error with the capsule id; `Ok(None)` means the capsule was not loaded, so this error only fires for a loaded capsule whose OS-level CoW workspace operation failed.
Solutions
- Inspect the wrapped source error for the filesystem-level cause (permissions, missing path, busy files).
- Close processes holding files in the capsule workspace, then retry the promote/rollback.
- If the workspace is inconsistent, reset the workspace (or re-load the capsule) and re-run the gate decision.
Example fix
// before: lose the capsule id context
outcome?;
// after: ensure workspace paths are writable and processes closed before commit
for path in workspace_paths(&capsule) {
let meta = std::fs::metadata(&path)
.expect("workspace path must exist before commit");
assert!(!meta.permissions().readonly(), "workspace must be writable");
}
kernel.commit_workspace_for(&id, &principal, true).await?; Defensive patterns
Strategy: try-catch
Validate before calling
fn workspace_ready(capsule: &Capsule) -> bool {
capsule.workspace_paths().iter().all(|p| p.exists() && !p.metadata().unwrap().permissions().readonly())
} Try / catch
match kernel.commit_workspace_for(&id, &principal, true).await {
Ok(None) => info!("capsule not loaded; nothing to commit"),
Ok(Some(_)) => info!("workspace committed"),
Err(e) if e.to_string().contains("workspace commit") => {
error!("CoW workspace op failed: {e:#}; reload capsule and retry");
},
Err(e) => return Err(e),
} Prevention
- Ensure no external process holds files open in capsule workspaces during gate approval.
- Keep workspace volumes writable and monitored for space.
- Reset/re-load capsules whose workspace state may be inconsistent.
When it happens
Trigger: Gate approve/reject invokes `commit_workspace_for` and `capsule.promote_workspace` or `capsule.rollback_workspace` errors: filesystem failure during the copy-on-write merge/discard (permissions, missing paths, files held open), or the workspace snapshot is in an inconsistent state.
Common situations: Files inside the capsule workspace locked by another process at promote time; read-only volume; workspace directory manually altered or deleted between snapshot and commit; disk exhaustion during copy-on-write materialization.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
- audit fixture
- authoritative principal store is unavailable
- cannot load capsule ' ' for unadmitted principal
- capsule ' ' has no source directory
- capsule path escaped source root
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/61bebb7d736505da.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/lib.rs:3354
/// (git-managed or No-CoW — nothing to do).
pub(crate) async fn commit_workspace_for(
&self,
id: &astrid_capsule_types::CapsuleId,
principal: &PrincipalId,
commit: bool,
) -> Result<Option<bool>, anyhow::Error> {
let capsule = { self.capsules.read().await.get_for(principal, id) };
let Some(capsule) = capsule else {
return Ok(None);
};
let outcome = if commit {
capsule.promote_workspace(principal).await
} else {
capsule.rollback_workspace(principal).await
};
outcome
.map(Some)
.map_err(|e| anyhow::anyhow!("workspace commit for capsule '{id}' failed: {e}"))
}
/// Record that a new client connection for `principal` has been established.
pub fn connection_opened(&self, principal: &PrincipalId) {
self.active_connections
.entry(principal.clone())
.or_insert_with(|| AtomicUsize::new(0))
.fetch_add(1, Ordering::Relaxed);
metrics::counter!(METRIC_CONNECTIONS_OPENED_TOTAL).increment(1);
metrics::gauge!(METRIC_ACTIVE_CONNECTIONS).increment(1.0);
}
/// Record that a client connection for `principal` has been closed.
///
/// Uses `fetch_update` for atomic saturating decrement - avoids the
/// TOCTOU window where `fetch_sub` wraps to `usize::MAX` before a
/// corrective store.
///View on GitHub (pinned to affd8760f4)