commit 1a247c5f3629bd0cff6c343865d6082d2a3cf2ae
Author: Coder Agent <coder@agents.omni>
Date: Wed Feb 11 22:19:04 2026
Omni/Ide: auto-recover broken role worktrees in loop startup
- add workspace health check and broken-worktree auto-recovery path
- preserve broken workspace dirs as *.broken-<timestamp> before repair
- recreate role worktree with force-add when admin metadata is stale/missing
- add loop --name option so branch prefix is explicit during recovery
- update setup loop hints/docs for --name and recovery behavior
Task-Id: t-588
diff --git a/Omni/Ide/DEV_REVIEW_RELEASE.md b/Omni/Ide/DEV_REVIEW_RELEASE.md
index 0f5f8dfa..3fdd38fa 100644
--- a/Omni/Ide/DEV_REVIEW_RELEASE.md
+++ b/Omni/Ide/DEV_REVIEW_RELEASE.md
@@ -9,6 +9,7 @@ This workflow runs three independent agent loops in dedicated worktrees.
- Branch cleanup is dry-run by default.
- Worktree directories are never removed automatically.
- Loop auto-stashes dirty workspace state by default (can disable with `--no-auto-stash`).
+- Loop auto-recovers broken role worktrees by preserving the broken directory and re-adding the worktree.
## 1) Create dedicated worktrees
@@ -36,6 +37,9 @@ Omni/Ide/dev-review-release.sh setup-worktrees --root _/worktrees/my-flow --name
Omni/Ide/dev-review-release.sh loop --role dev
Omni/Ide/dev-review-release.sh loop --role review
Omni/Ide/dev-review-release.sh loop --role integrator
+
+# If setup used a custom branch prefix, pass it to loops
+Omni/Ide/dev-review-release.sh loop --role dev --root _/worktrees/my-flow --name my-flow
```
Optional flags:
diff --git a/Omni/Ide/README.md b/Omni/Ide/README.md
index a9c44699..bee6725f 100644
--- a/Omni/Ide/README.md
+++ b/Omni/Ide/README.md
@@ -67,6 +67,7 @@ Examples:
Omni/Ide/dev-review-release.sh setup-worktrees
Omni/Ide/dev-review-release.sh setup-worktrees --root _/worktrees/t-587 --name t-587
Omni/Ide/dev-review-release.sh loop --role dev
+Omni/Ide/dev-review-release.sh loop --role dev --root _/worktrees/t-587 --name t-587
Omni/Ide/dev-review-release.sh loop --role review
Omni/Ide/dev-review-release.sh loop --role integrator
diff --git a/Omni/Ide/dev-review-release.sh b/Omni/Ide/dev-review-release.sh
index 7e107e30..9b76a426 100755
--- a/Omni/Ide/dev-review-release.sh
+++ b/Omni/Ide/dev-review-release.sh
@@ -42,6 +42,7 @@ Loop options:
--role ROLE Required. dev | review | integrator
--root PATH Worktree root (default: _/worktrees/dev-review-release)
--base BRANCH Base branch for worktrees (default: live)
+ --name NAME Branch prefix for role worktrees (default: basename of --root)
--interval N Poll interval in seconds (default: 20)
--provider NAME Optional agentd provider (claude-code|anthropic|openrouter|ollama)
--parent ID Restrict loop to tasks whose parent matches ID (epic scope)
@@ -143,6 +144,44 @@ workspace_for_role() {
esac
}
+workspace_is_valid_git_repo() {
+ local workspace="$1"
+ git -C "$workspace" rev-parse --is-inside-work-tree >/dev/null 2>&1
+}
+
+ensure_role_workspace() {
+ local root="$1"
+ local role="$2"
+ local base_branch="$3"
+ local worktree_name="$4"
+
+ local workspace branch
+ workspace="$(workspace_for_role "$root" "$role")"
+ branch="$(branch_for_worktree "$worktree_name" "$role")"
+
+ if workspace_is_valid_git_repo "$workspace"; then
+ return 0
+ fi
+
+ if [[ -e "$workspace" ]]; then
+ local stamp backup
+ stamp="$(date +%Y%m%d-%H%M%S)"
+ backup="${workspace}.broken-${stamp}"
+ log "Detected broken workspace at $workspace; preserving it as $backup"
+ mv "$workspace" "$backup"
+ fi
+
+ if git -C "$REPO_ROOT" show-ref --verify --quiet "refs/heads/$branch"; then
+ log "Recreating $role workspace at $workspace from branch $branch"
+ git -C "$REPO_ROOT" worktree add -f "$workspace" "$branch"
+ else
+ log "Recreating $role workspace at $workspace with branch $branch from $base_branch"
+ git -C "$REPO_ROOT" worktree add -f -b "$branch" "$workspace" "$base_branch"
+ fi
+
+ workspace_is_valid_git_repo "$workspace"
+}
+
branch_for_worktree() {
local worktree_name="$1"
local role="$2"
@@ -843,9 +882,9 @@ Worktrees ready under: $root
Worktree branch prefix: $worktree_name
Start loops in tmux (one window each):
- Omni/Ide/dev-review-release.sh loop --role dev --root "$root"
- Omni/Ide/dev-review-release.sh loop --role review --root "$root"
- Omni/Ide/dev-review-release.sh loop --role integrator --root "$root"
+ Omni/Ide/dev-review-release.sh loop --role dev --root "$root" --name "$worktree_name"
+ Omni/Ide/dev-review-release.sh loop --role review --root "$root" --name "$worktree_name"
+ Omni/Ide/dev-review-release.sh loop --role integrator --root "$root" --name "$worktree_name"
EOF
}
@@ -853,6 +892,7 @@ loop_cmd() {
local role=""
local root_rel="$DEFAULT_WORKTREE_ROOT"
local base_branch="$DEFAULT_BASE_BRANCH"
+ local worktree_name=""
local interval="$DEFAULT_INTERVAL_SECONDS"
local provider=""
local task_filter=""
@@ -880,6 +920,10 @@ loop_cmd() {
base_branch="$2"
shift 2
;;
+ --name)
+ worktree_name="$2"
+ shift 2
+ ;;
--interval)
interval="$2"
shift 2
@@ -946,18 +990,23 @@ loop_cmd() {
exit 1
fi
+ if [[ -z "$worktree_name" ]]; then
+ worktree_name="$(default_worktree_name "$root_rel")"
+ fi
+ validate_worktree_name "$worktree_name"
+
local root workspace
root="$(resolve_path "$root_rel")"
workspace="$(workspace_for_role "$root" "$role")"
- if ! git -C "$workspace" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
- echo "Workspace missing: $workspace" >&2
- echo "Run setup first: Omni/Ide/dev-review-release.sh setup-worktrees --root \"$root\"" >&2
+ if ! ensure_role_workspace "$root" "$role" "$base_branch" "$worktree_name"; then
+ echo "Workspace missing or unrecoverable: $workspace" >&2
+ echo "Run setup: Omni/Ide/dev-review-release.sh setup-worktrees --root \"$root\" --name \"$worktree_name\"" >&2
exit 1
fi
log "Starting $role loop"
- log "workspace=$workspace base=$base_branch interval=${interval}s dry_run=$dry_run auto_stash_dirty=$auto_stash_dirty max_retries=$max_retries max_task_cost=$max_task_cost task_filter=${task_filter:-<none>} parent_filter=${parent_filter:-<none>}"
+ log "workspace=$workspace base=$base_branch name=$worktree_name interval=${interval}s dry_run=$dry_run auto_stash_dirty=$auto_stash_dirty max_retries=$max_retries max_task_cost=$max_task_cost task_filter=${task_filter:-<none>} parent_filter=${parent_filter:-<none>}"
while true; do
set +e