hermes - ✅(Solved) Fix [Bug]: Kanban scratch workspace cleanup can delete real source directories [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#28818Fetched 2026-05-20 04:01:43
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Participants
Timeline (top)
labeled ×3cross-referenced ×2referenced ×1

Kanban task completion can delete a real user source directory when a board or task workspace points at an existing project folder but the task uses workspace_kind = scratch.

The worker documentation says scratch is disposable and gets garbage-collected. That behavior is expected. The safety problem is that Hermes currently allows a user-provided/default workdir outside Hermes-managed scratch space to be treated as scratch without warning or blocking, so a misconfiguration can turn normal task completion into data loss.

Error Message

  • Log a warning/error when scratch cleanup is rejected.

Root Cause

  • Real source checkouts can be deleted if they are configured as a scratch workspace.
  • Sibling workers spawned in the same workspace then fail with FileNotFoundError / os.getcwd() because their cwd has been removed.
  • Task logs may not show an explicit rm -rf, making the root cause hard to diagnose.

Fix Action

Fix / Workaround

  1. Create a kanban task without explicitly setting a persistent workspace kind such as dir:/path/to/real/source or worktree.
  2. Dispatch and complete the task.
  3. Completion cleanup treats /path/to/real/source as disposable scratch and may remove it.

P0 / critical because the failure mode is data loss. There is a workaround (dir:<path> or worktree), but the current configuration path is too easy to misuse and the result is severe.

PR fix notes

PR #28819: fix(kanban): refuse to rmtree workspace_path outside managed scratch root (#28818)

Description (problem / solution / changelog)

What does this PR do?

Fixes a data-loss vector in kanban scratch cleanup (#28818, marked P0 in the issue body). A board's default_workdir (set via hermes kanban boards set-default-workdir <board> <path>) is copied into tasks.workspace_path for tasks created without an explicit workspace_kind. Those tasks default to workspace_kind='scratch', so when the task completes, _cleanup_workspace runs shutil.rmtree(wp, ignore_errors=True) — unconditionally deleting the user's real source tree as if it were disposable scratch storage.

The fix adds a containment guard: _cleanup_workspace now only deletes paths under a kanban-managed scratch root. A managed root is either HERMES_KANBAN_WORKSPACES_ROOT (the worker-side override the dispatcher injects into worker env at kanban_db.py:5236) or anywhere under <kanban_home>/kanban/, which covers both the legacy default-board root (<kanban_home>/kanban/workspaces) and per-board roots (<kanban_home>/kanban/boards/<slug>/workspaces) returned by workspaces_root(). Anything else gets a warning log and is left alone, so a misconfigured default_workdir can no longer destroy user data.

Mirrors workspaces_root()'s full input precedence (HERMES_KANBAN_WORKSPACES_ROOT > computed-from-board) so cleanup containment lines up exactly with the dispatcher's worker-env injection.

Related Issue

Fixes #28818

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • hermes_cli/kanban_db.py — add _is_managed_scratch_path(p) helper that checks p against HERMES_KANBAN_WORKSPACES_ROOT and <kanban_home>/kanban/. _cleanup_workspace now gates the existing shutil.rmtree call on that check and emits a _log.warning instead of deleting when the path is out of scope.
  • tests/hermes_cli/test_kanban_db.py — 5 new tests covering the regression and the supporting helper: managed scratch dirs still get cleaned up; user source trees outside the managed roots survive task completion (with .git and a sentinel file preserved); HERMES_KANBAN_WORKSPACES_ROOT override paths are still eligible for cleanup; per-board scratch dirs under <kanban_home>/kanban/boards/<slug>/workspaces are recognised as managed; arbitrary user paths are rejected.

How to Test

  1. Repro the original bug (without the fix):
    hermes kanban boards set-default-workdir my-board /path/to/real/source
    hermes kanban add "do something" --board my-board   # no --workspace-kind
    hermes kanban complete <task_id>                    # /path/to/real/source is rmtree'd
  2. With the fix applied, the same sequence leaves /path/to/real/source intact and emits a warning: Refusing to remove out-of-scratch workspace for task <id>: /path/to/real/source (workspace_kind='scratch' but path is outside any kanban-managed workspaces root).
  3. Focused tests:
    uv run --with pytest --with pytest-xdist --with pytest-asyncio python3 -m pytest tests/hermes_cli/test_kanban_db.py -v
    163 tests pass locally, including the 5 new ones. Regression-guard: temporarily bypassing the new _is_managed_scratch_path(wp) check makes test_cleanup_workspace_refuses_path_outside_scratch_root fail with User source tree must not be deleted by scratch cleanup, confirming the test catches the data-loss bug.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run focused tests for the touched code and all pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15.x

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — docstrings on _is_managed_scratch_path and _cleanup_workspace explain the containment rule and reference #28818
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A (no new config keys)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guidePath.is_relative_to + Path.resolve(strict=False) are portable; no platform-specific code paths
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Sibling code paths that may need the same fix

Sibling code paths that may need the same fix:

  • create_task (hermes_cli/kanban_db.py:1469-1474) — inherits board.default_workdir into workspace_path while leaving workspace_kind at its default 'scratch'. Defaulting workspace_kind='dir' when a default_workdir is inherited would also prevent the bad state at the source. Intentionally left out of this PR's scope to keep the diff small and the behavior change conservative — happy to widen.
  • hermes kanban boards set-default-workdir (CLI handler in hermes_cli/kanban.py) — could emit a warning when the target exists and is outside <kanban_home>/kanban/, surfacing the misconfiguration before any task runs. Also a candidate for widening.

The current containment guard is sufficient on its own to close the data-loss vector — the two siblings would harden the same surface but are not required for the fix to be safe.

Changed files

  • hermes_cli/kanban_db.py (modified, +55/-2)
  • tests/hermes_cli/test_kanban_db.py (modified, +91/-0)

PR #14: fix(kanban): guard scratch workspace cleanup against deleting real source dirs (#28818)

Description (problem / solution / changelog)

🟠 Merge order: 11 / 12 — larger safety guard, but protective

Closes #28818 (P2 — data loss prevention)

Problem

Kanban task completion could shutil.rmtree() a real source directory when workspace_kind=scratch pointed at a non-Hermes-managed path.

Fix

  • Refuse to delete paths containing .git
  • Only delete under the Hermes-managed scratch root (scratch_workspaces_dir())
  • Log warnings when cleanup is rejected

Risk assessment

FactorRating
Lines changed27/-4
New codeSafety checks + resolve() + relative_to()
Side effects⚠️ Some legitimate scratch workspaces outside the canonical root won't be auto-cleaned anymore (safe by design — log a warning)
Revert complexityEasy

Testing notes

  • Create a scratch workspace under ~/.hermes/kanban/workspaces/ → should be cleaned up
  • Point a task workspace at a real source dir → should be skipped with warning
  • Point at a dir with .git → should be skipped

Files changed

  • hermes_cli/kanban_db.py (+27/-4)

Changed files

  • hermes_cli/kanban_db.py (modified, +27/-4)

Code Example

# complete_task(...)
_cleanup_workspace(conn, task_id)

---

if kind != "scratch" or not path:
    return

wp = Path(path)
if wp.is_dir():
    shutil.rmtree(wp, ignore_errors=True)

---

workspace_kind TEXT NOT NULL DEFAULT 'scratch'

---

hermes kanban boards set-default-workdir my-board /path/to/real/source

---

~/.hermes/kanban/workspaces/...

---

workspace_kind = scratch
workspace_path = /path/to/real/source

---

shutil.rmtree("/path/to/real/source", ignore_errors=True)
RAW_BUFFERClick to expand / collapse

This was generated by AI during triage.

Summary

Kanban task completion can delete a real user source directory when a board or task workspace points at an existing project folder but the task uses workspace_kind = scratch.

The worker documentation says scratch is disposable and gets garbage-collected. That behavior is expected. The safety problem is that Hermes currently allows a user-provided/default workdir outside Hermes-managed scratch space to be treated as scratch without warning or blocking, so a misconfiguration can turn normal task completion into data loss.

Impact

  • Real source checkouts can be deleted if they are configured as a scratch workspace.
  • Sibling workers spawned in the same workspace then fail with FileNotFoundError / os.getcwd() because their cwd has been removed.
  • Task logs may not show an explicit rm -rf, making the root cause hard to diagnose.

Relevant Code Path

In hermes_cli/kanban_db.py, successful task completion calls workspace cleanup:

# complete_task(...)
_cleanup_workspace(conn, task_id)

Cleanup removes scratch workspaces:

if kind != "scratch" or not path:
    return

wp = Path(path)
if wp.is_dir():
    shutil.rmtree(wp, ignore_errors=True)

The task schema defaults to scratch:

workspace_kind TEXT NOT NULL DEFAULT 'scratch'

Reproduction Shape

  1. Create or use a board whose default workdir points at an existing real source directory, for example:
hermes kanban boards set-default-workdir my-board /path/to/real/source
  1. Create a kanban task without explicitly setting a persistent workspace kind such as dir:/path/to/real/source or worktree.
  2. Dispatch and complete the task.
  3. Completion cleanup treats /path/to/real/source as disposable scratch and may remove it.

Expected Behavior

Hermes should never silently delete a user source directory because it was assigned as a task workspace.

For non-Hermes-managed paths, Hermes should either:

  • reject workspace_kind = scratch, or
  • require explicit confirmation, or
  • coerce/direct users to dir:<absolute-path> or worktree.

Scratch cleanup should only delete directories Hermes itself created under a known Hermes scratch root, such as:

~/.hermes/kanban/workspaces/...

Actual Behavior

If a task has:

workspace_kind = scratch
workspace_path = /path/to/real/source

then successful task completion can call:

shutil.rmtree("/path/to/real/source", ignore_errors=True)

Suggested Fix

Add guardrails before shutil.rmtree():

  • Only delete scratch workspaces under the configured Hermes kanban workspaces root.
  • Refuse to delete paths containing .git or obvious source-control metadata.
  • Refuse to delete paths outside ~/.hermes/kanban/workspaces unless they carry a Hermes-owned marker file.
  • Log a warning/error when scratch cleanup is rejected.
  • Consider making board default_workdir imply persistent dir:<path> semantics rather than scratch semantics.
  • Consider warning in boards set-default-workdir if the target exists and is outside Hermes-managed workspace storage.

Regression Tests

Please add tests for:

  • Hermes-created scratch workspace under kanban workspaces root is cleaned up.
  • Existing user source directory outside the scratch root is not removed.
  • Any path containing .git is not removed by scratch cleanup.
  • Board default_workdir cannot accidentally become destructively cleanup-eligible by default.

Severity

P0 / critical because the failure mode is data loss. There is a workaround (dir:<path> or worktree), but the current configuration path is too easy to misuse and the result is severe.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix [Bug]: Kanban scratch workspace cleanup can delete real source directories [2 pull requests, 1 participants]