Skip to content

[Bug]: Kanban scratch workspace cleanup can delete real source directories #28818

@nvt-pankajsharma

Description

@nvt-pankajsharma

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/cliCLI entry point, hermes_cli/, setup wizardtype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions