Skip to content

Conversation

@marcusquinn
Copy link
Owner

@marcusquinn marcusquinn commented Feb 9, 2026

Summary

  • Adds SQLite-backed worktree ownership registry that tracks which PID owns each worktree
  • worktree-helper.sh remove and clean refuse to touch worktrees owned by other live processes
  • supervisor-helper.sh registers ownership on dispatch and checks before cleanup
  • Prevents the root cause from 2026-02-09: supervisor force-removed a worktree belonging to a parallel interactive session

Changes

shared-constants.sh (+170 lines)

  • _wt_sql_escape() — SQL escape helper
  • register_worktree() — register PID ownership with optional task/batch/session metadata
  • unregister_worktree() — remove ownership entry
  • check_worktree_owner() — query owner info
  • is_worktree_owned_by_others() — returns 0 if owned by another live PID
  • prune_worktree_registry() — clean stale entries (dead PIDs, missing dirs)

worktree-helper.sh (+135 lines)

  • cmd_add registers ownership on creation
  • cmd_remove checks ownership before removal, supports --force override
  • cmd_clean skips worktrees owned by other active sessions
  • New cmd_registry command (list/prune subcommands)

supervisor-helper.sh (+37 lines)

  • create_task_worktree registers ownership with --task metadata
  • create_task_worktree refuses to clean stale worktrees owned by others
  • cleanup_task_worktree checks ownership before removal, unregisters after
  • cmd_cleanup prunes stale registry entries

AGENTS.md

  • Updated worktree ownership rule with registry reference

worktree.md

  • Added "Ownership Safety (t189)" section with commands and explanation

Testing

  • All 295 smoke tests pass (0 failures)
  • ShellCheck zero new violations
  • Functional test: register → check → is_owned_by_others → unregister → prune all work correctly
  • Pre-existing test failures in dispatch-worktree-evaluate unchanged (11 vs 12 on base)

Subtasks

  • t189.1 Add worktree ownership registry
  • t189.2 Add "in use" detection to worktree cleanup
  • t189.3 Add AGENTS.md rule (already existed, enhanced with registry ref)
  • t189.4 Scope batch cleanup to batch-owned worktrees only

Summary by CodeRabbit

Release Notes

  • New Features

    • Added worktree ownership registry to prevent accidental removal by other sessions; ownership is automatically tracked on creation and enforced during cleanup operations.
    • New registry command to view and prune ownership records with process status checks.
    • Added --force flag to override ownership checks when needed.
  • Documentation

    • Updated worktree safety documentation with ownership registry details and usage examples.

…l (t189)

Three layers of protection against the root cause (supervisor force-removing
a worktree belonging to a parallel interactive session):

1. Ownership registry (SQLite-backed, shared-constants.sh):
   - register_worktree/unregister_worktree track PID ownership
   - is_worktree_owned_by_others checks if owner PID is alive
   - prune_worktree_registry cleans stale entries (dead PIDs, missing dirs)

2. In-use detection in worktree-helper.sh:
   - cmd_add registers ownership on creation
   - cmd_remove refuses removal if owned by another live process
   - cmd_clean skips worktrees owned by other active sessions
   - cmd_registry provides list/prune subcommands
   - --force flag available for explicit override

3. Supervisor integration (supervisor-helper.sh):
   - create_task_worktree registers ownership with --task metadata
   - cleanup_task_worktree checks ownership before removal
   - cmd_cleanup prunes stale registry entries
   - Stale worktree cleanup refuses to touch owned worktrees

Also updated AGENTS.md ownership rule and worktree.md documentation.
@gemini-code-assist
Copy link

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

Walkthrough

A SQLite-backed worktree ownership registry system is introduced across shell scripts to track which process/session owns each worktree. The registry prevents unintended cleanup of worktrees managed by other live processes and includes commands to view and prune stale entries.

Changes

Cohort / File(s) Summary
Documentation
.agents/AGENTS.md, .agents/workflows/worktree.md
Updated documentation to describe the new ownership registry mechanism (t189), including registry location, registration workflows, removal restrictions, and pruning behavior.
Registry Core Infrastructure
.agents/scripts/shared-constants.sh
Added SQLite-backed registry constants (WORKTREE_REGISTRY_DIR, WORKTREE_REGISTRY_DB), database initialization (_init_registry_db), SQL escaping helper (_wt_sql_escape), and public API functions: register_worktree, unregister_worktree, check_worktree_owner, is_worktree_owned_by_others, and prune_worktree_registry.
Ownership Integration
.agents/scripts/supervisor-helper.sh, .agents/scripts/worktree-helper.sh
Integrated ownership registration/unregistration into create/cleanup workflows; added ownership checks before destructive operations (remove/clean) with early-exit paths when conflicts detected; added cmd_registry command to view/prune registry entries; extended remove with --force flag to override ownership checks; wired registry pruning into cleanup entry points.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Script
    participant WH as worktree-helper.sh
    participant SC as shared-constants.sh
    participant DB as SQLite DB
    participant FS as Filesystem

    Client->>WH: add worktree
    WH->>FS: create worktree (git worktree add)
    FS-->>WH: success
    WH->>SC: register_worktree(path, branch)
    SC->>DB: INSERT into worktree_owners
    DB-->>SC: ownership recorded
    SC-->>WH: registered
    WH-->>Client: worktree ready

    Client->>WH: remove worktree
    WH->>SC: check_worktree_owner(path)
    SC->>DB: SELECT owner from worktree_owners
    DB-->>SC: {pid, session, ...}
    SC->>SC: is_worktree_owned_by_others()?
    alt Owned by Other Session
        SC-->>WH: return owner info
        WH-->>Client: error: owned by other session
    else Owned by Current Session
        WH->>FS: remove worktree
        FS-->>WH: success
        WH->>SC: unregister_worktree(path)
        SC->>DB: DELETE from worktree_owners
        DB-->>SC: unregistered
        SC-->>WH: done
        WH-->>Client: worktree removed
    end
Loading
sequenceDiagram
    participant Task as Task Dispatcher
    participant SH as supervisor-helper.sh
    participant SC as shared-constants.sh
    participant DB as SQLite DB
    participant WH as worktree-helper.sh

    Task->>SH: create_task_worktree
    SH->>WH: create worktree via wt/git
    WH-->>SH: success
    SH->>SC: register_worktree(path, branch, task_id, batch_id, session_id)
    SC->>DB: INSERT with task metadata
    DB-->>SC: ownership recorded
    SC-->>SH: registered
    SH-->>Task: worktree ready

    Task->>SH: cleanup_task_worktree
    SH->>SC: check_worktree_owner(path)
    SC->>DB: SELECT owner, task_id from worktree_owners
    DB-->>SC: owner metadata
    alt Owned by Other Session
        SC-->>SH: return owner info
        SH-->>Task: defer cleanup
    else Owned by Current Session
        SH->>WH: remove/clean worktree
        WH-->>SH: success
        SH->>SC: unregister_worktree(path)
        SC->>DB: DELETE from worktree_owners
        DB-->>SC: unregistered
        SH->>SC: prune_worktree_registry()
        SC->>DB: DELETE stale entries
        DB-->>SC: pruned
        SC-->>SH: done
        SH-->>Task: cleanup complete
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related issues

Possibly related PRs

Poem

🔐 Safe worktrees dwell in registry'd care,
Cross-sessions clash no more in despair,
Ownership tracked with SQLite's might,
Stale entries pruned in the night,
Cleanup now blessed with safety's fair share ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: adding worktree ownership safety to prevent cross-session removal, with task reference (t189).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/t189

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 44 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Mon Feb 9 00:38:22 UTC 2026: Code review monitoring started
Mon Feb 9 00:38:22 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 44

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 44
  • VULNERABILITIES: 0

Generated on: Mon Feb 9 00:38:25 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 9, 2026

@marcusquinn marcusquinn merged commit e1e03be into main Feb 9, 2026
9 of 11 checks passed
@marcusquinn marcusquinn deleted the feature/t189 branch February 9, 2026 00:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @.agents/scripts/shared-constants.sh:
- Around line 625-646: The current lookup of owner_pid uses "|| echo """ which
masks sqlite3 failures and causes the function to incorrectly return
safe-to-remove; change the logic to detect sqlite3 failure explicitly: capture
the sqlite3 output into owner_pid, check sqlite3's exit status (or test for
command failure) and if sqlite3 failed, print a warning to stderr (including
$WORKTREE_REGISTRY_DB and any sqlite error if available) and return 0 (NOT safe
to remove); keep the existing semantics for an empty owner_pid (no owner =>
return 1) and for a dead owner (use kill -0 and call unregister_worktree
"$wt_path"), so references to owner_pid, WORKTREE_REGISTRY_DB,
unregister_worktree, and kill -0 should be used to locate and update the code.

In @.agents/scripts/supervisor-helper.sh:
- Around line 2801-2809: The condition that detects a worktree owned by another
session (is_worktree_owned_by_others) must not return success; change the block
in the cleanup logic so you still capture owner info using the local
var="$(...)" pattern (e.g. local stale_owner_info;
stale_owner_info=$(check_worktree_owner "$worktree_path" || echo "unknown")),
keep the log_warn call, still echo the path for the caller to inspect if needed,
but return a non‑zero status (e.g. return 1 or another distinct code) instead of
return 0 so cmd_dispatch and callers can detect and skip/defer dispatch rather
than reusing/stomping the worktree.
- Around line 2862-2873: The ownership check currently returns success (0) when
skipping cleanup, which causes callers like cmd_cleanup and cleanup_after_merge
to wrongly treat the worktree as cleaned; change the behavior so the block
returns a distinct non-zero status (e.g., return 2) to signal an ownership
conflict and prevent callers from clearing state, and also adopt the local
assignment pattern for owner_info (use local owner_info="$(check_worktree_owner
"$worktree_path" || echo "unknown")") to follow shell guidelines; keep the
log_warn call but ensure callers handle the non-zero exit to preserve
DB/worktree state.

In @.agents/scripts/worktree-helper.sh:
- Around line 213-214: The call to register_worktree in the worktree creation
flow doesn't check its return value, so failures (e.g., sqlite3 unavailable)
leave the worktree unregistered and vulnerable to deletion; update the caller
around register_worktree "$path" "$branch" to capture its exit status and, on
non-zero return, emit a clear warning via the existing logging facility (and
optionally fail the operation) so users know registration didn’t complete; also
ensure register_worktree (and related helpers referenced in shared-constants.sh)
return non-zero on error so the caller can detect and act on failures.
🧹 Nitpick comments (6)
.agents/scripts/shared-constants.sh (3)

519-522: Missing explicit return statement.

Per coding guidelines, shell functions should include explicit returns.

Proposed fix
 _wt_sql_escape() {
     local val="$1"
     echo "${val//\'/\'\'}"
+    return 0
 }

As per coding guidelines, "Include explicit returns in shell scripts".


515-516: Registry constants are not readonly, unlike every other constant in this file.

All other constants in shared-constants.sh are declared readonly. These two are mutable, which means any sourcing script could accidentally overwrite them, potentially redirecting registry operations to an unexpected path.

Proposed fix
-WORKTREE_REGISTRY_DIR="${HOME}/.aidevops/.agent-workspace"
-WORKTREE_REGISTRY_DB="${WORKTREE_REGISTRY_DIR}/worktree-registry.db"
+readonly WORKTREE_REGISTRY_DIR="${HOME}/.aidevops/.agent-workspace"
+readonly WORKTREE_REGISTRY_DB="${WORKTREE_REGISTRY_DIR}/worktree-registry.db"

525-539: DB initialization silences errors — consider logging.

If sqlite3 is not installed or the directory isn't writable, this will fail silently, and all downstream operations will too. This aligns with the log_stderr pattern already established earlier in this file (line 317). Using it here would provide visibility without disrupting the flow.

Proposed fix
 _init_registry_db() {
     mkdir -p "$WORKTREE_REGISTRY_DIR" 2>/dev/null || true
-    sqlite3 "$WORKTREE_REGISTRY_DB" "
+    log_stderr "registry_init" sqlite3 "$WORKTREE_REGISTRY_DB" "
         CREATE TABLE IF NOT EXISTS worktree_owners (
             worktree_path TEXT PRIMARY KEY,
             branch        TEXT,
             owner_pid     INTEGER,
             owner_session TEXT DEFAULT '',
             owner_batch   TEXT DEFAULT '',
             task_id       TEXT DEFAULT '',
             created_at    TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
         );
-    " 2>/dev/null || true
+    " || true
     return 0
 }

Note: log_stderr is already defined earlier in this file (line 317) and is recommended for sqlite3 calls per the guidelines comment on line 299.

.agents/scripts/worktree-helper.sh (2)

286-306: Unnecessary export and dual-variable pattern for force flag.

force_remove (local, line 287) and WORKTREE_FORCE_REMOVE (exported, line 305) track the same intent. Since the check on line 352 is in the same function, the local variable suffices — no child processes read WORKTREE_FORCE_REMOVE. The export also leaks the flag into the environment for any subsequent commands.

Proposed simplification
     # Parse arguments
     while [[ $# -gt 0 ]]; do
         case "$1" in
             --force|-f) force_remove=true; shift ;;
             *) target="$1"; shift ;;
         esac
     done
     
     if [[ -z "$target" ]]; then
         echo -e "${RED}Error: Path or branch name required${NC}"
         echo "Usage: worktree-helper.sh remove <path|branch> [--force]"
         return 1
     fi
-
-    # Export for ownership check
-    if [[ "$force_remove" == "true" ]]; then
-        export WORKTREE_FORCE_REMOVE="true"
-    fi

Then at line 352:

-        if [[ "${WORKTREE_FORCE_REMOVE:-}" != "true" ]]; then
+        if [[ "$force_remove" != "true" ]]; then

581-623: cmd_registry is clean and well-structured.

The list/prune subcommands, alive/dead PID status display, and early exits are all well-handled. The return 0 at the end covers all branches.

One small note: the * case (line 618-620) prints usage but returns 0. Consider returning 1 for unrecognized subcommands to match the pattern in main() (line 732-734).

Proposed fix
         *)
             echo "Usage: worktree-helper.sh registry [list|prune]"
+            return 1
             ;;
     esac
     return 0
.agents/workflows/worktree.md (1)

385-410: All command syntax verified, but consider consolidating ownership guidance per single source of truth principle.

All documented commands and registry path are technically correct:

  • worktree-helper.sh registry list and registry prune subcommands exist and match documentation
  • worktree-helper.sh remove <branch> --force flag is implemented
  • Registry path ~/.aidevops/.agent-workspace/worktree-registry.db matches WORKTREE_REGISTRY_DB constant

However, per the learnings and coding guidelines, AGENTS.md is the authoritative single source of truth for all instructions and operational guidance. AGENTS.md already contains a comprehensive worktree ownership section (marked CRITICAL). Consider consolidating this worktree.md section to avoid split guidance: move operational procedures into AGENTS.md with specific command examples, or establish clear cross-references between AGENTS.md (principles) and worktree.md (procedures).

Comment on lines +563 to +574
sqlite3 "$WORKTREE_REGISTRY_DB" "
INSERT OR REPLACE INTO worktree_owners
(worktree_path, branch, owner_pid, owner_session, owner_batch, task_id)
VALUES
('$(_wt_sql_escape "$wt_path")',
'$(_wt_sql_escape "$branch")',
$$,
'$(_wt_sql_escape "$session_id")',
'$(_wt_sql_escape "$batch_id")',
'$(_wt_sql_escape "$task_id")');
" 2>/dev/null || true
return 0
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Silent failure on registration leaves worktrees unprotected.

If the INSERT OR REPLACE fails (disk full, permissions, missing sqlite3), the function returns 0 (success) and the caller proceeds as if the worktree is registered. A subsequent is_worktree_owned_by_others call will find no entry and allow removal — defeating the safety goal.

Consider propagating the sqlite3 exit code so the caller can at least log a warning that ownership registration failed.

Proposed fix
     sqlite3 "$WORKTREE_REGISTRY_DB" "
         INSERT OR REPLACE INTO worktree_owners
             (worktree_path, branch, owner_pid, owner_session, owner_batch, task_id)
         VALUES
             ('$(_wt_sql_escape "$wt_path")',
              '$(_wt_sql_escape "$branch")',
              $$,
              '$(_wt_sql_escape "$session_id")',
              '$(_wt_sql_escape "$batch_id")',
              '$(_wt_sql_escape "$task_id")');
-    " 2>/dev/null || true
+    " 2>/dev/null || {
+        print_shared_warning "Failed to register worktree ownership for: $wt_path"
+        return 1
+    }
     return 0
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sqlite3 "$WORKTREE_REGISTRY_DB" "
INSERT OR REPLACE INTO worktree_owners
(worktree_path, branch, owner_pid, owner_session, owner_batch, task_id)
VALUES
('$(_wt_sql_escape "$wt_path")',
'$(_wt_sql_escape "$branch")',
$$,
'$(_wt_sql_escape "$session_id")',
'$(_wt_sql_escape "$batch_id")',
'$(_wt_sql_escape "$task_id")');
" 2>/dev/null || true
return 0
sqlite3 "$WORKTREE_REGISTRY_DB" "
INSERT OR REPLACE INTO worktree_owners
(worktree_path, branch, owner_pid, owner_session, owner_batch, task_id)
VALUES
('$(_wt_sql_escape "$wt_path")',
'$(_wt_sql_escape "$branch")',
$$,
'$(_wt_sql_escape "$session_id")',
'$(_wt_sql_escape "$batch_id")',
'$(_wt_sql_escape "$task_id")');
" 2>/dev/null || {
print_shared_warning "Failed to register worktree ownership for: $wt_path"
return 1
}
return 0

Comment on lines +625 to +646
local owner_pid
owner_pid=$(sqlite3 "$WORKTREE_REGISTRY_DB" "
SELECT owner_pid FROM worktree_owners
WHERE worktree_path = '$(_wt_sql_escape "$wt_path")';
" 2>/dev/null || echo "")

# No owner registered
[[ -z "$owner_pid" ]] && return 1

# We own it
[[ "$owner_pid" == "$$" ]] && return 1

# Owner process is dead — stale entry, safe to remove
if ! kill -0 "$owner_pid" 2>/dev/null; then
# Clean up stale entry
unregister_worktree "$wt_path"
return 1
fi

# Owner process is alive and it's not us — NOT safe to remove
return 0
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail-open on SQLite errors silently bypasses ownership safety.

If sqlite3 is unavailable, the DB is corrupt, or the query fails for any reason, owner_pid becomes "" (via || echo ""), and the function returns 1 ("safe to remove"). This silently degrades the entire safety feature to a no-op — exactly the scenario this PR exists to prevent.

For a protective mechanism, fail-closed is the safer default: if you can't confirm ownership status, refuse removal and let --force handle it.

Proposed fail-closed approach
 is_worktree_owned_by_others() {
     local wt_path="$1"
 
     [[ ! -f "$WORKTREE_REGISTRY_DB" ]] && return 1
 
     local owner_pid
     owner_pid=$(sqlite3 "$WORKTREE_REGISTRY_DB" "
         SELECT owner_pid FROM worktree_owners
         WHERE worktree_path = '$(_wt_sql_escape "$wt_path")';
-    " 2>/dev/null || echo "")
+    " 2>/dev/null)
+
+    # If sqlite3 failed entirely ($? != 0 and empty result), check if sqlite3 is functional
+    if [[ -z "$owner_pid" ]] && ! command -v sqlite3 &>/dev/null; then
+        print_shared_warning "sqlite3 not available — ownership check cannot run"
+        return 1  # No sqlite3 means registry was never populated; safe fallback
+    fi
 
     # No owner registered
     [[ -z "$owner_pid" ]] && return 1

At minimum, consider logging a warning when sqlite3 fails so operators can diagnose why the registry isn't protecting worktrees.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
local owner_pid
owner_pid=$(sqlite3 "$WORKTREE_REGISTRY_DB" "
SELECT owner_pid FROM worktree_owners
WHERE worktree_path = '$(_wt_sql_escape "$wt_path")';
" 2>/dev/null || echo "")
# No owner registered
[[ -z "$owner_pid" ]] && return 1
# We own it
[[ "$owner_pid" == "$$" ]] && return 1
# Owner process is dead — stale entry, safe to remove
if ! kill -0 "$owner_pid" 2>/dev/null; then
# Clean up stale entry
unregister_worktree "$wt_path"
return 1
fi
# Owner process is alive and it's not us — NOT safe to remove
return 0
}
local owner_pid
owner_pid=$(sqlite3 "$WORKTREE_REGISTRY_DB" "
SELECT owner_pid FROM worktree_owners
WHERE worktree_path = '$(_wt_sql_escape "$wt_path")';
" 2>/dev/null)
# If sqlite3 failed entirely ($? != 0 and empty result), check if sqlite3 is functional
if [[ -z "$owner_pid" ]] && ! command -v sqlite3 &>/dev/null; then
print_shared_warning "sqlite3 not available — ownership check cannot run"
return 1 # No sqlite3 means registry was never populated; safe fallback
fi
# No owner registered
[[ -z "$owner_pid" ]] && return 1
# We own it
[[ "$owner_pid" == "$$" ]] && return 1
# Owner process is dead — stale entry, safe to remove
if ! kill -0 "$owner_pid" 2>/dev/null; then
# Clean up stale entry
unregister_worktree "$wt_path"
return 1
fi
# Owner process is alive and it's not us — NOT safe to remove
return 0
}
🤖 Prompt for AI Agents
In @.agents/scripts/shared-constants.sh around lines 625 - 646, The current
lookup of owner_pid uses "|| echo """ which masks sqlite3 failures and causes
the function to incorrectly return safe-to-remove; change the logic to detect
sqlite3 failure explicitly: capture the sqlite3 output into owner_pid, check
sqlite3's exit status (or test for command failure) and if sqlite3 failed, print
a warning to stderr (including $WORKTREE_REGISTRY_DB and any sqlite error if
available) and return 0 (NOT safe to remove); keep the existing semantics for an
empty owner_pid (no owner => return 1) and for a dead owner (use kill -0 and
call unregister_worktree "$wt_path"), so references to owner_pid,
WORKTREE_REGISTRY_DB, unregister_worktree, and kill -0 should be used to locate
and update the code.

Comment on lines +2801 to +2809
# Ownership check (t189): refuse to clean worktrees owned by other sessions
if [[ -d "$worktree_path" ]] && is_worktree_owned_by_others "$worktree_path"; then
local stale_owner_info
stale_owner_info=$(check_worktree_owner "$worktree_path" || echo "unknown")
log_warn "Cannot clean stale worktree $worktree_path — owned by another active session (owner: $stale_owner_info)" >&2
# Return existing path — let the caller decide
echo "$worktree_path"
return 0
fi
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid reusing a worktree owned by another live session

Line 2806 returns the existing path with success, so cmd_dispatch will keep going and may stomp another session’s worktree. Return a distinct non‑zero code and let callers defer/skip dispatch instead of reusing the path. Also align the local assignment style for guideline compliance.

🔧 Suggested fix
-        if [[ -d "$worktree_path" ]] && is_worktree_owned_by_others "$worktree_path"; then
-            local stale_owner_info
-            stale_owner_info=$(check_worktree_owner "$worktree_path" || echo "unknown")
+        if [[ -d "$worktree_path" ]] && is_worktree_owned_by_others "$worktree_path"; then
+            local stale_owner_info="$(check_worktree_owner "$worktree_path" || echo "unknown")"
             log_warn "Cannot clean stale worktree $worktree_path — owned by another active session (owner: $stale_owner_info)" >&2
-            # Return existing path — let the caller decide
-            echo "$worktree_path"
-            return 0
+            # Signal ownership conflict; caller should defer/skip dispatch
+            return 2
         fi

As per coding guidelines, "Use local var="$1" pattern in shell scripts".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Ownership check (t189): refuse to clean worktrees owned by other sessions
if [[ -d "$worktree_path" ]] && is_worktree_owned_by_others "$worktree_path"; then
local stale_owner_info
stale_owner_info=$(check_worktree_owner "$worktree_path" || echo "unknown")
log_warn "Cannot clean stale worktree $worktree_path — owned by another active session (owner: $stale_owner_info)" >&2
# Return existing path — let the caller decide
echo "$worktree_path"
return 0
fi
# Ownership check (t189): refuse to clean worktrees owned by other sessions
if [[ -d "$worktree_path" ]] && is_worktree_owned_by_others "$worktree_path"; then
local stale_owner_info="$(check_worktree_owner "$worktree_path" || echo "unknown")"
log_warn "Cannot clean stale worktree $worktree_path — owned by another active session (owner: $stale_owner_info)" >&2
# Signal ownership conflict; caller should defer/skip dispatch
return 2
fi
🤖 Prompt for AI Agents
In @.agents/scripts/supervisor-helper.sh around lines 2801 - 2809, The condition
that detects a worktree owned by another session (is_worktree_owned_by_others)
must not return success; change the block in the cleanup logic so you still
capture owner info using the local var="$(...)" pattern (e.g. local
stale_owner_info; stale_owner_info=$(check_worktree_owner "$worktree_path" ||
echo "unknown")), keep the log_warn call, still echo the path for the caller to
inspect if needed, but return a non‑zero status (e.g. return 1 or another
distinct code) instead of return 0 so cmd_dispatch and callers can detect and
skip/defer dispatch rather than reusing/stomping the worktree.

Comment on lines 2862 to 2873
if [[ ! -d "$worktree_path" ]]; then
# Directory gone — clean up registry entry if any
unregister_worktree "$worktree_path"
return 0
fi

# Ownership check (t189): refuse to remove worktrees owned by other sessions
if is_worktree_owned_by_others "$worktree_path"; then
local owner_info
owner_info=$(check_worktree_owner "$worktree_path" || echo "unknown")
log_warn "Skipping cleanup of $worktree_path — owned by another active session (owner: $owner_info)"
return 0
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Propagate ownership‑conflict as a non‑success code

Line 2873 returns 0 even though cleanup is skipped, so callers (e.g., cmd_cleanup, cleanup_after_merge) will likely clear the DB/worktree state anyway. That loses tracking of a still‑owned worktree. Return a distinct code and let callers preserve state when ownership blocks cleanup; also use the local assignment pattern.

🔧 Suggested fix
-    if is_worktree_owned_by_others "$worktree_path"; then
-        local owner_info
-        owner_info=$(check_worktree_owner "$worktree_path" || echo "unknown")
+    if is_worktree_owned_by_others "$worktree_path"; then
+        local owner_info="$(check_worktree_owner "$worktree_path" || echo "unknown")"
         log_warn "Skipping cleanup of $worktree_path — owned by another active session (owner: $owner_info)"
-        return 0
+        return 2
     fi

As per coding guidelines, "Use local var="$1" pattern in shell scripts".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [[ ! -d "$worktree_path" ]]; then
# Directory gone — clean up registry entry if any
unregister_worktree "$worktree_path"
return 0
fi
# Ownership check (t189): refuse to remove worktrees owned by other sessions
if is_worktree_owned_by_others "$worktree_path"; then
local owner_info
owner_info=$(check_worktree_owner "$worktree_path" || echo "unknown")
log_warn "Skipping cleanup of $worktree_path — owned by another active session (owner: $owner_info)"
return 0
if [[ ! -d "$worktree_path" ]]; then
# Directory gone — clean up registry entry if any
unregister_worktree "$worktree_path"
return 0
fi
# Ownership check (t189): refuse to remove worktrees owned by other sessions
if is_worktree_owned_by_others "$worktree_path"; then
local owner_info="$(check_worktree_owner "$worktree_path" || echo "unknown")"
log_warn "Skipping cleanup of $worktree_path — owned by another active session (owner: $owner_info)"
return 2
🤖 Prompt for AI Agents
In @.agents/scripts/supervisor-helper.sh around lines 2862 - 2873, The ownership
check currently returns success (0) when skipping cleanup, which causes callers
like cmd_cleanup and cleanup_after_merge to wrongly treat the worktree as
cleaned; change the behavior so the block returns a distinct non-zero status
(e.g., return 2) to signal an ownership conflict and prevent callers from
clearing state, and also adopt the local assignment pattern for owner_info (use
local owner_info="$(check_worktree_owner "$worktree_path" || echo "unknown")")
to follow shell guidelines; keep the log_warn call but ensure callers handle the
non-zero exit to preserve DB/worktree state.

Comment on lines +213 to +214
# Register ownership (t189)
register_worktree "$path" "$branch"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Registration return value not checked — unprotected worktree on failure.

If register_worktree fails (e.g., sqlite3 unavailable), the worktree is created but never registered in the ownership DB. A subsequent clean or remove from another session won't see an owner and will proceed with deletion — the exact problem this PR aims to solve.

Consider at least logging a warning so the user knows the safety net isn't active.

Proposed fix
     # Register ownership (t189)
-    register_worktree "$path" "$branch"
+    if ! register_worktree "$path" "$branch"; then
+        print_warning "Failed to register worktree ownership — safety checks may not protect this worktree"
+    fi

This depends on register_worktree propagating errors (see the related comment in shared-constants.sh).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Register ownership (t189)
register_worktree "$path" "$branch"
# Register ownership (t189)
if ! register_worktree "$path" "$branch"; then
print_warning "Failed to register worktree ownership — safety checks may not protect this worktree"
fi
🤖 Prompt for AI Agents
In @.agents/scripts/worktree-helper.sh around lines 213 - 214, The call to
register_worktree in the worktree creation flow doesn't check its return value,
so failures (e.g., sqlite3 unavailable) leave the worktree unregistered and
vulnerable to deletion; update the caller around register_worktree "$path"
"$branch" to capture its exit status and, on non-zero return, emit a clear
warning via the existing logging facility (and optionally fail the operation) so
users know registration didn’t complete; also ensure register_worktree (and
related helpers referenced in shared-constants.sh) return non-zero on error so
the caller can detect and act on failures.

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.

1 participant