Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 62 additions & 49 deletions .agents/scripts/supervisor/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2129,6 +2129,66 @@ RULES:
# Args: task_id
# Returns: 0 on success, 1 on rebase failure, 2 on force-push failure
#######################################

#######################################
# t1072: Resolve rebase conflicts in a loop for multi-commit branches.
# When a branch has N commits and multiple conflict with main, we need
# to resolve each one and continue until the rebase completes.
# Args: git_dir task_id
# Returns: 0 on success, 1 on failure (rebase aborted)
#######################################
_resolve_rebase_loop() {
local git_dir="$1"
local task_id="$2"
Comment on lines +2141 to +2142
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This function violates the repository's shell script style guide. Line 11 requires declaring local variables and assigning values from positional parameters in separate statements to ensure exit code safety (e.g., local var; var="$1").

Suggested change
local git_dir="$1"
local task_id="$2"
local git_dir; git_dir="$1"
local task_id; task_id="$2"
References
  1. Use local var="$1" pattern in functions (declare and assign separately for exit code safety) (link)

local max_iterations=10
local iteration=0

while ((iteration < max_iterations)); do
iteration=$((iteration + 1))
log_warn "rebase_sibling_pr: rebase conflict for $task_id — attempting AI resolution (commit $iteration/$max_iterations)"

if ! resolve_rebase_conflicts "$git_dir" "$task_id"; then
log_warn "rebase_sibling_pr: AI resolution failed for $task_id at commit $iteration — aborting"
git -C "$git_dir" rebase --abort 2>>"$SUPERVISOR_LOG" || true
return 1
fi

# t1048: Check if rebase is still in progress — the AI agent
# may have already run `git rebase --continue` itself
local git_state_dir
git_state_dir="$(git -C "$git_dir" rev-parse --git-dir 2>/dev/null)"
Comment on lines +2158 to +2159
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The git_state_dir variable is calculated on each loop iteration by calling git rev-parse --git-dir. Since the .git directory path is constant within the scope of this function, this call can be moved outside and before the while loop. This would improve efficiency by avoiding redundant process execution and also remove the need for the second identical call on line 2174.

if [[ ! -d "$git_state_dir/rebase-merge" && ! -d "$git_state_dir/rebase-apply" ]]; then
# AI agent already completed the entire rebase
log_success "rebase_sibling_pr: rebase completed (AI resolved all commits) for $task_id"
return 0
fi

log_info "rebase_sibling_pr: AI resolved commit $iteration for $task_id — continuing rebase"
if git -C "$git_dir" rebase --continue 2>>"$SUPERVISOR_LOG"; then
# Rebase completed successfully — no more conflicts
log_success "rebase_sibling_pr: rebase completed after resolving $iteration conflict(s) for $task_id"
return 0
fi

# rebase --continue failed — check if it's another conflict or a real error
git_state_dir="$(git -C "$git_dir" rev-parse --git-dir 2>/dev/null)"
if [[ -d "$git_state_dir/rebase-merge" || -d "$git_state_dir/rebase-apply" ]]; then
# Still in rebase state — another commit has conflicts, loop continues
log_info "rebase_sibling_pr: commit $iteration resolved but next commit also conflicts for $task_id"
continue
fi

# rebase --continue failed and no rebase in progress — unexpected state
log_warn "rebase_sibling_pr: rebase --continue failed unexpectedly for $task_id at commit $iteration"
return 1
done

# Exhausted max iterations
log_warn "rebase_sibling_pr: exhausted $max_iterations conflict resolution attempts for $task_id — aborting"
git -C "$git_dir" rebase --abort 2>>"$SUPERVISOR_LOG" || true
return 1
}

rebase_sibling_pr() {
local task_id="$1"

Expand Down Expand Up @@ -2201,30 +2261,7 @@ rebase_sibling_pr() {
if [[ "$use_worktree" == "true" ]]; then
# Worktree is already on the branch — rebase in place
if ! git -C "$git_dir" rebase origin/main 2>>"$SUPERVISOR_LOG"; then
log_warn "rebase_sibling_pr: rebase conflict for $task_id — attempting AI resolution"
# Try AI-assisted conflict resolution before aborting
if resolve_rebase_conflicts "$git_dir" "$task_id"; then
# t1048: Check if rebase is still in progress — the AI agent
# may have already run `git rebase --continue` itself, leaving
# no rebase in progress for us to continue.
local git_state_dir
git_state_dir="$(git -C "$git_dir" rev-parse --git-dir 2>/dev/null)"
if [[ -d "$git_state_dir/rebase-merge" || -d "$git_state_dir/rebase-apply" ]]; then
log_info "rebase_sibling_pr: AI resolved conflicts for $task_id — continuing rebase"
if git -C "$git_dir" rebase --continue 2>>"$SUPERVISOR_LOG"; then
log_success "rebase_sibling_pr: rebase completed after AI resolution for $task_id"
else
log_warn "rebase_sibling_pr: rebase --continue failed after AI resolution for $task_id"
git -C "$git_dir" rebase --abort 2>>"$SUPERVISOR_LOG" || true
return 1
fi
else
# AI agent already completed the rebase — treat as success
log_success "rebase_sibling_pr: rebase completed (AI resolved and continued) for $task_id"
fi
else
log_warn "rebase_sibling_pr: AI resolution failed for $task_id — aborting"
git -C "$git_dir" rebase --abort 2>>"$SUPERVISOR_LOG" || true
if ! _resolve_rebase_loop "$git_dir" "$task_id"; then
return 1
fi
fi
Expand All @@ -2240,31 +2277,7 @@ rebase_sibling_pr() {
fi

if ! git -C "$git_dir" rebase origin/main 2>>"$SUPERVISOR_LOG"; then
log_warn "rebase_sibling_pr: rebase conflict for $task_id — attempting AI resolution"
# Try AI-assisted conflict resolution before aborting
if resolve_rebase_conflicts "$git_dir" "$task_id"; then
# t1048: Check if rebase is still in progress (see worktree path above)
local git_state_dir
git_state_dir="$(git -C "$git_dir" rev-parse --git-dir 2>/dev/null)"
if [[ -d "$git_state_dir/rebase-merge" || -d "$git_state_dir/rebase-apply" ]]; then
log_info "rebase_sibling_pr: AI resolved conflicts for $task_id — continuing rebase"
if git -C "$git_dir" rebase --continue 2>>"$SUPERVISOR_LOG"; then
log_success "rebase_sibling_pr: rebase completed after AI resolution for $task_id"
else
log_warn "rebase_sibling_pr: rebase --continue failed after AI resolution for $task_id"
git -C "$git_dir" rebase --abort 2>>"$SUPERVISOR_LOG" || true
# Return to original branch
git -C "$git_dir" checkout "${current_branch:-main}" 2>>"$SUPERVISOR_LOG" || true
return 1
fi
else
# AI agent already completed the rebase — treat as success
log_success "rebase_sibling_pr: rebase completed (AI resolved and continued) for $task_id"
fi
else
log_warn "rebase_sibling_pr: AI resolution failed for $task_id — aborting"
git -C "$git_dir" rebase --abort 2>>"$SUPERVISOR_LOG" || true
# Return to original branch
if ! _resolve_rebase_loop "$git_dir" "$task_id"; then
git -C "$git_dir" checkout "${current_branch:-main}" 2>>"$SUPERVISOR_LOG" || true
return 1
fi
Expand Down
48 changes: 42 additions & 6 deletions .agents/scripts/supervisor/state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -926,30 +926,66 @@ cmd_next() {

ensure_db

local candidates
if [[ -n "$batch_id" ]]; then
local escaped_batch
escaped_batch=$(sql_escape "$batch_id")

db -separator $'\t' "$SUPERVISOR_DB" "
candidates=$(db -separator $'\t' "$SUPERVISOR_DB" "
SELECT t.id, t.repo, t.description, t.model
FROM batch_tasks bt
JOIN tasks t ON bt.task_id = t.id
WHERE bt.batch_id = '$escaped_batch'
AND t.status = 'queued'
AND t.retries < t.max_retries
ORDER BY t.retries ASC, bt.position
LIMIT $limit;
"
LIMIT $((limit * 3));
")
else
db -separator $'\t' "$SUPERVISOR_DB" "
candidates=$(db -separator $'\t' "$SUPERVISOR_DB" "
SELECT id, repo, description, model
FROM tasks
WHERE status = 'queued'
AND retries < max_retries
ORDER BY retries ASC, created_at ASC
LIMIT $limit;
"
LIMIT $((limit * 3));
")
fi

[[ -z "$candidates" ]] && return 0

# t1073: Filter out subtasks whose earlier siblings are still in-flight.
# Subtasks (IDs like t1063.2) should run sequentially — dispatching them
# in parallel causes merge conflicts because they modify the same files.
# Skip a subtask if any sibling with a lower suffix is not yet terminal.
local count=0
while IFS=$'\t' read -r cid crepo cdesc cmodel; do
[[ "$count" -ge "$limit" ]] && break

# Check if this is a subtask (contains a dot)
if [[ "$cid" == *.* ]]; then
local parent_id="${cid%.*}"
local suffix="${cid##*.}"

# Check if any earlier sibling (same parent, lower suffix) is non-terminal
local earlier_active
earlier_active=$(db "$SUPERVISOR_DB" "
SELECT count(*) FROM tasks
WHERE id LIKE '$(sql_escape "$parent_id").%'
AND id != '$(sql_escape "$cid")'
AND CAST(REPLACE(id, '$(sql_escape "$parent_id").', '') AS INTEGER) < $suffix
AND status NOT IN ('verified','cancelled','deployed','complete');
" 2>/dev/null || echo "0")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Suppressing stderr with 2>/dev/null and defaulting to 0 with || echo "0" is risky. It can mask underlying database errors (e.g., a malformed query, DB corruption), causing the script to proceed with incorrect assumptions. This could lead to subtasks being dispatched out of order. This also violates the repository style guide (line 50), which prohibits blanket error suppression. It would be safer to let the script fail on a database error so the problem can be investigated.

References
  1. 2>/dev/null is acceptable ONLY when redirecting to log files, not blanket suppression (link)


if [[ "$earlier_active" -gt 0 ]]; then
log_info " cmd_next: deferring $cid — earlier sibling(s) of $parent_id still active"
continue
fi
fi

printf '%s\t%s\t%s\t%s\n' "$cid" "$crepo" "$cdesc" "$cmodel"
count=$((count + 1))
done <<<"$candidates"
Comment on lines +962 to +988
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This while loop executes a database query for every candidate subtask. This is a classic N+1 query problem and can be inefficient, especially if the number of candidate tasks is large. Consider fetching the status of all potentially relevant sibling tasks in a single query before the loop, then performing the filtering logic in memory within the loop. This would significantly reduce the number of database round-trips.


return 0
}
Loading