Skip to content

fix(kanban): preserve shared workspaces across boards - #126617

Open
JoaoMarcos44 wants to merge 2 commits into
NousResearch:mainfrom
JoaoMarcos44:fix/126508-shared-workspace-ownership
Open

JoaoMarcos44 wants to merge 2 commits into
NousResearch:mainfrom
JoaoMarcos44:fix/126508-shared-workspace-ownership

Conversation

@JoaoMarcos44

@JoaoMarcos44 JoaoMarcos44 commented Sep 28, 2026 •

Copy link
Copy Markdown

Summary

Fixes #126508 by moving scratch/worktree deletion behind a workspace-reference guard at the lifecycle owner.

The destructive paths previously checked containment and parent/child activity, but they did not prove that the same resolved workspace_path was unused by another non-terminal task. Because Hermes stores named boards in separate SQLite DBs, that proof must include sibling boards as well as the current connection.

What changes

  • build a resolved snapshot of workspace paths held by non-terminal tasks
  • include the current DB plus sibling board DBs opened read-only
  • fail closed when a sibling DB cannot be inspected
  • reuse the same guard in:
    • completion/archive workspace cleanup
    • deferred parent cleanup
    • hermes kanban gc
  • emit workspace_cleanup_deferred_shared when deletion is deferred
  • compute the snapshot once per GC pass instead of rescanning live rows for every archived task

Differential from #126513

I inspected #126513 before publishing to avoid a duplicate. Its helper queries the current board connection only. A live task in another board DB can therefore reference the same resolved path without being visible to that guard.

This PR is independently implemented from current main; no commit, hunk, or code from #126513 was cherry-picked or copied. The cross-board regression is the behavioral distinction: archived task in the default board + live task in a named board + identical resolved scratch path => GC must preserve the directory.

The regression is also run with HERMES_KANBAN_DB and HERMES_KANBAN_WORKSPACES_ROOT pinned like a worker environment. Sibling-board discovery deliberately uses physical board DB files, so a process-wide DB pin cannot collapse every board lookup onto the current DB.

I did not fold in the source-side decompose fix from #103303 because #103325/#103628 already cover that separate producer-side problem.

Regression coverage

  • same-board live task keeps an archived task's shared scratch path
  • cross-board live task keeps an archived task's shared scratch path
  • the cross-board case remains protected under worker DB/workspace env pins
  • completion cleanup preserves a path used by an unrelated live task
  • deferred-parent cleanup preserves a path used by an unrelated live task
  • when the last live reference becomes terminal, a later GC removes the path

Commit structure

  1. 26dbdcd95ccf243e1425977008cd9ece1c8ab645 — root-cause implementation + focused regressions
  2. a2caf0e644d28d11913f1446d99b6d4dd56ddf21 — post-publication regression hardening for the pinned worker environment

The second commit is intentionally test-only: the implementation did not need to change after the adversarial review.

Post-publication audit

I ran 10 review passes over provenance/deduplication, exact base/head, destructive-site coverage, multi-board ownership, worker env pinning, terminal-state semantics, path identity/cross-platform behavior, read-only/fail-closed behavior, GC cost, and CI/review/main-drift state.

One material improvement came out of that audit: the cross-board test now reproduces the process-wide worker pins noted above. No additional code blocker was found. #126513 remains on its original current-board-only implementation at the time of this update, and upstream main is still e408d363393ccb72267e67bcccf4f8954b438cd9.

Validation status

The branch is based on exact upstream main commit e408d363393ccb72267e67bcccf4f8954b438cd9.

The connected environment does not expose a repository test runner, so I am not claiming local pytest/ruff results I could not execute. The first upstream Actions attempt ended in startup_failure before jobs ran, and no workflow has run for the follow-up test commit yet.

There is a narrow residual cross-DB TOCTOU window inherent to any check-then-delete design: a new task could acquire the same path after the ownership snapshot and before filesystem removal. Closing that fully would require a cross-board coordination/locking primitive, which is broader than this deletion-owner fix.

Related

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cron Cron scheduler and job management labels Sep 28, 2026
for child in boards.iterdir()
if child.is_dir() and (child / "kanban.db").is_file()
)
except OSError:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P1] Keep this guard fail-closed when board discovery itself fails.

This exception returns a partial database list. _workspace_use_snapshot() then leaves complete=True, so completion or GC can delete a workspace without checking an unreadable sibling board. That restores the data-loss path this change is intended to close.

I reproduced this by making boards.iterdir() raise while a live task in another board referenced the archived task workspace. _cmd_gc() reported that it removed one workspace and deleted the file.

Please propagate discovery failure into the snapshot completeness result, and add a regression that proves an enumeration or read failure preserves the workspace. An active workspace path that cannot be resolved should also make the snapshot incomplete.

kshitijk4poor added a commit to kshitijk4poor/hermes-agent that referenced this pull request Oct 3, 2026
Trim the salvaged tests to two: gc keeps a scratch dir a live same-board
task still uses (and records the deferred event), and cleanup refuses
the delete when sibling boards cannot be listed while a named board's
live task uses the dir (the fail-closed path NousResearch#126617 missed). The plain
same-board cleanup, cross-board and same-id cases go through the same
_workspace_in_use_by_other scan.
kshitijk4poor added a commit that referenced this pull request Oct 3, 2026
Trim the salvaged tests to two: gc keeps a scratch dir a live same-board
task still uses (and records the deferred event), and cleanup refuses
the delete when sibling boards cannot be listed while a named board's
live task uses the dir (the fail-closed path #126617 missed). The plain
same-board cleanup, cross-board and same-id cases go through the same
_workspace_in_use_by_other scan.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: kanban gc / archive / complete delete a scratch workspace still used by a non-terminal task (shared workspace_path)

3 participants