{"record":{"id":"092f8cda66211b92","repo":"astrid-runtime/astrid","slug":"fuse-lease-is-expired","errorCode":null,"errorMessage":"FUSE lease is expired","messagePattern":"FUSE lease is expired","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-storage-provider-fuse/src/service.rs","lineNumber":216,"sourceCode":"    if parent.start_identity.is_none() {\n        bail!(\"FUSE service parent start identity is required on Linux\");\n    }\n    Ok(())\n}\n\nfn validate_lease(lease: &StorageMountLeaseV1) -> Result<()> {\n    if lease.lease_token.len() < 16\n        || lease.lease_token.len() > 4096\n        || lease.lease_token.chars().any(char::is_control)\n    {\n        bail!(\"invalid FUSE callback token\");\n    }\n    let now = SystemTime::now()\n        .duration_since(UNIX_EPOCH)\n        .context(\"read system clock\")?\n        .as_secs();\n    if lease.expires_at_epoch_secs < now {\n        bail!(\"FUSE lease is expired\");\n    }\n    if !lease.resource_path.is_absolute() || !lease.callback_path.is_absolute() {\n        bail!(\"FUSE lease paths must be absolute\");\n    }\n    if lease.callback_path != lease.resource_path.join(\"control.sock\") {\n        bail!(\"FUSE callback path is not the kernel lease endpoint\");\n    }\n    platform_fs::validate_private_directory(&lease.resource_path)\n        .context(\"validate private FUSE lease resource\")?;\n    platform_fs::verify_no_redirects(&lease.resource_path)\n        .context(\"reject redirected FUSE lease resource\")?;\n    let manifest_path = lease.resource_path.join(\"lease.json\");\n    platform_fs::validate_private_file(&manifest_path)\n        .context(\"validate private FUSE lease manifest\")?;\n    let manifest = std::fs::read(&manifest_path).context(\"read FUSE lease manifest\")?;\n    if manifest.len() > 64 * 1024 {\n        bail!(\"FUSE lease manifest exceeds the bounded size\");\n    }","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage-provider-fuse/src/service.rs#L198-L234","documentation":"Every mount lease carries an expiry timestamp (`expires_at_epoch_secs`). Before starting the FUSE service, the helper compares it to the current UNIX time and refuses to mount with a lease that has already expired, ensuring stale grants cannot create mounts.","triggerScenarios":"`validate_lease` computes `now` from `SystemTime::now()` and finds `lease.expires_at_epoch_secs < now` during `validate_launch`.","commonSituations":"Clock skew between the machine issuing the lease and the machine mounting (VM/container clock drift); a cached lease.json reused after its TTL; suspended/resumed laptop with a short-lived lease; lease generated with a wrong (past) expiry.","solutions":["Request a fresh lease from the broker and retry the mount.","Fix system clock (NTP sync) if skew or resume drift caused the mismatch.","Re-issue the lease with a longer TTL for slow/unreliable startup paths.","Check that the expiry epoch is computed in seconds (not millis) when the lease is created.","Don't cache lease.json across runs — reload it at mount time."],"exampleFix":"// before\nexpires_at_epoch_secs: now + 60, // expires during slow startup\n// after\nexpires_at_epoch_secs: now + 3600, // longer TTL\n// or: refresh the lease before mounting\nlet lease = broker.refresh_lease(&mount_id).await?;","handlingStrategy":"validation","validationCode":"let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs();\nif lease.expires_at_epoch_secs < now + 60 {\n    lease = broker.refresh_lease(&lease).await?; // refresh if near expiry\n}","typeGuard":"fn lease_is_current(lease: &StorageMountLeaseV1) -> bool {\n    let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0);\n    lease.expires_at_epoch_secs >= now\n}","tryCatchPattern":"match mount(&lease).await {\n    Err(e) if e.to_string().contains(\"lease is expired\") => {\n        let fresh = broker.refresh_lease(&lease).await?;\n        mount(&fresh).await\n    }\n    other => other,\n}","preventionTips":["Refresh the lease before mounting instead of reusing cached lease.json","Enable NTP time sync to avoid clock skew","Issue leases with TTL large enough for slow startup","Verify expiry is in epoch seconds, not milliseconds"],"tags":["fuse","lease","expiry","time"],"backgroundTag":"token-expired","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}