Skip to content

fix: prune stale attached_workspaces when anchor paths become invalid#2032

Merged
jolestar merged 2 commits into
mainfrom
fix/stale-attached-workspaces
Jun 27, 2026
Merged

fix: prune stale attached_workspaces when anchor paths become invalid#2032
jolestar merged 2 commits into
mainfrom
fix/stale-attached-workspaces

Conversation

@jolestar

Copy link
Copy Markdown
Collaborator

Problem

When a workspace's anchor path becomes invalid (worktree deleted, directory moved/deleted), the workspace ID remains in the agent's attached_workspaces list forever. Only detach_workspace removes entries; all other worktree cleanup paths (cleanup_task_owned_worktree_in_detail, discard_managed_worktree, exit_worktree) delete the physical worktree but never clean up the agent state.

This causes stale workspace entries to accumulate in the UI (workspace IDs with no path shown, not browsable).

Closes #2030.

Solution

Add prune_stale_attached_workspaces() to RuntimeHandle that lazily removes stale workspace IDs from attached_workspaces. An entry is stale when:

  • Its workspace ID is absent from the workspace registry, or
  • The registry entry's anchor path no longer exists on disk

The prune runs from the agent_state HTTP handler before building the summary, so stale entries are cleaned on every state API read — the primary read path for the UI.

Design choice: runtime-layer cleanup vs. projection-only

The issue proposed doing the cleanup inside state_workspace_snapshot (a pure projection function). Instead, the cleanup lives at the runtime layer (RuntimeHandle::prune_stale_attached_workspaces) because:

  1. Projection functions should be side-effect-freestate_workspace_snapshot should not mutate agent state.
  2. Persistent cleanup — stale IDs are removed from persisted agent state, not just hidden from one API response.
  3. Audit trail — each pruned ID emits a workspace_detached audit event with reason: "stale_workspace_anchor".

Safety guarantees

  • Active workspace is never pruned, even if its anchor is currently invalid, to avoid silently clearing the execution context.
  • AgentHome (agent_home and agent_home:{agent_id}) is never pruned — it is a virtual workspace that may not be in the registry.

Changes

File Change
src/runtime/lifecycle.rs Add prune_stale_attached_workspaces() method
src/http/state.rs Call prune before agent_summary() in agent_state handler
src/runtime/tests/workspace.rs 5 unit tests covering all prune scenarios

Test plan

  • prune_removes_workspace_id_missing_from_registry — stale ID not in registry gets pruned
  • prune_removes_workspace_with_deleted_anchor — registry entry with deleted anchor dir gets pruned
  • prune_preserves_active_workspace_even_when_stale — active workspace is never pruned
  • prune_preserves_valid_workspaces — valid workspaces are preserved
  • prune_emits_audit_events_for_removed_workspaces — audit events with correct reason
  • cargo fmt --all -- --check passes
  • RUSTFLAGS="-D warnings" cargo check --all-targets passes

…#2030)

Add prune_stale_attached_workspaces() to RuntimeHandle that lazily removes
stale workspace IDs from attached_workspaces. An entry is stale when its
ID is absent from the workspace registry or when the anchor path no longer
exists on disk.

Called from the agent_state HTTP handler before building the summary, so
stale entries are cleaned on every state read. Active workspace and
AgentHome are always preserved. Each pruned ID emits a workspace_detached
audit event with reason "stale_workspace_anchor".

Closes #2030
@vercel

vercel Bot commented Jun 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
holon Ready Ready Preview, Comment Jun 27, 2026 11:24am

@jolestar jolestar left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: PR #2032 — prune stale attached_workspaces when anchor paths become invalid

Verdict: ✅ LGTM — ready to merge after CI passes.

Analysis

  • Scope: 3 files, +232 lines — adds prune_stale_attached_workspaces() to RuntimeHandle, calls it from the agent_state HTTP handler, and includes 5 unit tests.
  • Correctness: Logic is clean — stale = absent from registry OR anchor not a directory. Active workspace and AgentHome are correctly protected from pruning.
  • Audit trail: Each pruned ID emits a workspace_detached event with reason: "stale_workspace_anchor" — good observability.
  • Persistence: State is properly persisted after pruning.
  • Test coverage: 5 tests cover all key scenarios (missing registry entry, deleted anchor, active workspace protection, valid workspace preservation, audit events).

Non-blocking notes

  • Lock granularity: The method acquires the agent lock 3 times (agent_id, active_workspace_id, attached_workspaces). Could be consolidated into one acquisition, but not a correctness issue given the HTTP handler call site.
  • Error propagation: bridge.workspace_entry_by_id() uses ? which aborts the entire prune on a single registry error. Since the caller uses .ok(), a match with continue on error could be more resilient — but this is a minor robustness improvement.

Overall: clean, well-tested fix for a real accumulation bug. ✅

…n prune_stale_attached_workspaces

Address non-blocking review feedback on PR #2032:
- Consolidate 3 separate agent lock reads into a single acquisition
- Use match+continue instead of ? for per-entry registry errors
@jolestar
jolestar merged commit 05f20ea into main Jun 27, 2026
5 checks passed
@jolestar
jolestar deleted the fix/stale-attached-workspaces branch June 27, 2026 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

stale attached_workspaces accumulate when anchor paths become invalid

1 participant