t1146: Add batch-cleanup mode to reduce worktree/PR overhead for chore tasks#1770
t1146: Add batch-cleanup mode to reduce worktree/PR overhead for chore tasks#1770marcusquinn merged 2 commits intomainfrom
Conversation
… tasks (t1146) Groups #chore tasks with ~<=15m estimates into a single dispatch instead of N separate worktrees and PRs. Reduces overhead by ~80% for cleanup batches. Changes: - Add batch-cleanup-helper.sh: scans for eligible tasks, dispatches single worker - Add Strategy 5 to cmd_auto_pickup in cron.sh: triggers batch-cleanup during pulse - Add 'batch-cleanup' command to supervisor-helper.sh router and usage docs Eligibility criteria: #chore tag + ~<=15m estimate + unclaimed + unblocked Trigger: >=2 eligible tasks found during auto-pickup pulse Savings: N*(worktree+PR+CI+merge) → 1*(worktree+PR+CI+merge)
WalkthroughThis PR introduces a comprehensive batch-cleanup system that scans TODO.md for eligible chore tasks, validates dependencies and estimates, and dispatches them as grouped batches. Integration points are added to the supervisor CLI and auto-pickup scheduler. Changes
Sequence Diagram(s)sequenceDiagram
participant Cron as Cron Scheduler
participant Supervisor as supervisor-helper.sh
participant Scanner as batch-cleanup-helper.sh<br/>(scan)
participant TODO as TODO.md
participant Dispatcher as batch-cleanup-helper.sh<br/>(dispatch)
participant AiCli as resolve_ai_cli<br/>(AI Worker)
participant Db as Supervisor DB
Cron->>Supervisor: cmd_auto_pickup
Supervisor->>Scanner: batch-cleanup scan
Scanner->>TODO: Read & parse `#chore` tasks
TODO-->>Scanner: Task list with estimates
Scanner->>Scanner: Validate eligibility<br/>(estimate ≤ 15m, no assignee/started,<br/>dependencies unblocked)
Scanner->>Db: Check task status
Db-->>Scanner: Eligible task IDs
Scanner-->>Supervisor: Task count & IDs
alt Eligible Tasks ≥ 2
Supervisor->>Dispatcher: batch-cleanup dispatch
Dispatcher->>Dispatcher: Build worker prompt<br/>with task descriptions
Dispatcher->>AiCli: Execute batch with task CSV
AiCli->>AiCli: Apply all changes<br/>in single batch
AiCli->>Db: Update supervisor DB
AiCli-->>Dispatcher: Return status & summary
Dispatcher-->>Supervisor: BATCH_CLEANUP_COMPLETE
else Fewer Than 2 Tasks
Supervisor-->>Cron: No dispatch needed
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
Summary of ChangesHello @marcusquinn, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a new batch-cleanup mode designed to optimize the handling of small, routine chore tasks. By consolidating multiple eligible tasks into a single worktree and pull request, it aims to drastically cut down on the repetitive overhead associated with individual task processing, thereby improving overall efficiency and resource utilization within the supervisor system. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Wed Feb 18 18:52:01 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
There was a problem hiding this comment.
Code Review
This pull request introduces an excellent optimization for handling simple chore tasks by batching them into a single worktree and pull request. The new batch-cleanup-helper.sh script is well-structured and the integration into the supervisor's auto-pickup logic is clean. My review focuses on improving the shell scripting quality within the new helper script, suggesting more efficient alternatives to some command pipelines and ensuring strict adherence to the repository's style guide, particularly regarding error stream handling. These changes will enhance performance and maintainability.
|
|
||
| # Extract blocked-by: field | ||
| local blocked_by | ||
| blocked_by=$(echo "$task_line" | grep -oE 'blocked-by:[^ ]+' | sed 's/blocked-by://' || true) |
There was a problem hiding this comment.
This command pipeline uses echo, grep, and sed to extract the blocked-by value, creating three separate processes. This can be made more efficient by using a single sed command to perform the extraction.
| blocked_by=$(echo "$task_line" | grep -oE 'blocked-by:[^ ]+' | sed 's/blocked-by://' || true) | |
| blocked_by=$(echo "$task_line" | sed -nE 's/.*blocked-by:([^ ]+).*/\1/p') |
|
|
||
| # Check if dependency is complete ([x]) in TODO.md | ||
| local dep_line | ||
| dep_line=$(grep -E "^[[:space:]]*- \[x\] ${dep} " "$todo_file" 2>/dev/null | head -1 || true) |
There was a problem hiding this comment.
The style guide (line 50) prohibits blanket suppression of stderr with 2>/dev/null. To adhere to the guide and improve debuggability, please redirect stderr to the supervisor log file.
| dep_line=$(grep -E "^[[:space:]]*- \[x\] ${dep} " "$todo_file" 2>/dev/null | head -1 || true) | |
| dep_line=$(grep -E "^[[:space:]]*- \[x\] ${dep} " "$todo_file" 2>>"$SUPERVISOR_LOG" | head -1 || true) |
References
- Line 50:
2>/dev/nullis acceptable ONLY when redirecting to log files, not blanket suppression. This comment points out a violation where stderr is suppressed instead of being logged. (link)
|
|
||
| # Find all pending #chore tasks | ||
| local chore_tasks | ||
| chore_tasks=$(grep -E '^[[:space:]]*- \[ \] (t[0-9]+(\.[0-9]+)*) .*#chore' "$todo_file" 2>/dev/null || true) |
There was a problem hiding this comment.
The style guide (line 50) prohibits blanket suppression of stderr with 2>/dev/null. To adhere to the guide and improve debuggability, please redirect stderr to the supervisor log file.
| chore_tasks=$(grep -E '^[[:space:]]*- \[ \] (t[0-9]+(\.[0-9]+)*) .*#chore' "$todo_file" 2>/dev/null || true) | |
| chore_tasks=$(grep -E '^[[:space:]]*- \[ \] (t[0-9]+(\.[0-9]+)*) .*#chore' "$todo_file" 2>>"$SUPERVISOR_LOG" || true) |
References
- Line 50:
2>/dev/nullis acceptable ONLY when redirecting to log files, not blanket suppression. This comment points out a violation where stderr is suppressed instead of being logged. (link)
| [[ -z "$line" ]] && continue | ||
|
|
||
| local task_id | ||
| task_id=$(echo "$line" | grep -oE 't[0-9]+(\.[0-9]+)*' | head -1) |
There was a problem hiding this comment.
This command pipeline uses echo, grep, and head to extract the task ID. This can be done more efficiently within bash itself using a regular expression match, avoiding the overhead of creating multiple subprocesses.
| task_id=$(echo "$line" | grep -oE 't[0-9]+(\.[0-9]+)*' | head -1) | |
| task_id=$([[ "$line" =~ (t[0-9]+(\.[0-9]+)*) ]] && echo "${BASH_REMATCH[1]}") |
|
|
||
| # Check estimate — must be <=15m | ||
| local estimate | ||
| estimate=$(echo "$line" | grep -oE '~[0-9]+(\.[0-9]+)?[mh]' | head -1 || true) |
There was a problem hiding this comment.
This command pipeline uses echo, grep, and head to extract the estimate. This can be done more efficiently within bash itself using a regular expression match, avoiding the overhead of creating multiple subprocesses.
| estimate=$(echo "$line" | grep -oE '~[0-9]+(\.[0-9]+)?[mh]' | head -1 || true) | |
| estimate=$([[ "$line" =~ (~[0-9]+(\.[0-9]+)?[mh]) ]] && echo "${BASH_REMATCH[1]}") |
| local task_id | ||
| for task_id in "${task_ids[@]}"; do | ||
| local task_line | ||
| task_line=$(grep -E "^[[:space:]]*- \[ \] ${task_id} " "$todo_file" 2>/dev/null | head -1 || true) |
There was a problem hiding this comment.
The style guide (line 50) prohibits blanket suppression of stderr with 2>/dev/null. To adhere to the guide and improve debuggability, please redirect stderr to the supervisor log file.
| task_line=$(grep -E "^[[:space:]]*- \[ \] ${task_id} " "$todo_file" 2>/dev/null | head -1 || true) | |
| task_line=$(grep -E "^[[:space:]]*- \[ \] ${task_id} " "$todo_file" 2>>"$SUPERVISOR_LOG" | head -1 || true) |
References
- Line 50:
2>/dev/nullis acceptable ONLY when redirecting to log files, not blanket suppression. This comment points out a violation where stderr is suppressed instead of being logged. (link)
…1146) The dispatch command calls resolve_ai_cli() which is defined in supervisor/dispatch.sh, but batch-cleanup-helper.sh only sourced _common.sh and shared-constants.sh. This would cause a runtime failure when attempting to dispatch a batch-cleanup worker. Verified: scan, status, help, and dispatch --dry-run all work correctly.
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Wed Feb 18 18:59:12 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
|
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.agents/scripts/batch-cleanup-helper.sh:
- Around line 74-80: The fractional-hour handling uses "frac * 6" which treats
the fraction as a single decimal digit and breaks for multi-digit fractions;
update the branch that matches "$estimate" to compute minutes correctly by
interpreting the fractional string as a proper decimal (e.g., build "0.$frac" or
compute frac/(10^len) ) and then calculate total minutes as hours*60 +
decimal_fraction*60 using a reliable tool (awk or bc) or integer math with the
fraction length; adjust the echo to output that computed minutes and keep the
same match on BASH_REMATCH to locate the code.
- Around line 355-366: The worker is launched with nohup using dispatch_cmd and
inherits the caller CWD so file references in worker_prompt (e.g., TODO.md,
todo/) will fail under cron; update the dispatch to cd into the repository
before launching the worker (either prepend a "cd '${repo}' &&" to the
dispatched command or generate a small wrapper script like
dispatch_decomposition_worker in cron.sh that does an explicit cd then exec the
ai_cli command), ensuring the code that builds dispatch_cmd (and the nohup
invocation) runs from the repo and still writes the worker PID to
BATCH_CLEANUP_PID_FILE.
- Around line 190-198: The existing check using the SUPERVISOR_DB/db/sql_escape
lookup mistakenly allows tasks with status "complete" to pass through; change
the conditional around the variable existing (result of db ... SELECT status
...) so that it treats "complete" as a skipped/post-completion state (e.g. test
for [[ -n "$existing" && ( "$existing" == "cancelled" || "$existing" ==
"complete" ) ]] and then log_info " $task_id: already tracked in supervisor
(status: $existing) — skipping" and continue). Update the condition in
batch-cleanup-helper.sh where existing is set to ensure complete tasks are
skipped.
- Around line 91-120: The cron job fails because cron.sh calls an undefined
is_task_blocked(); copy the dependency-check logic from is_task_unblocked() in
batch-cleanup-helper.sh into supervisor/_common.sh as a single shared function
named is_task_blocked() (invert the boolean semantics so it returns 0 when the
task IS blocked and 1 when unblocked), ensure it extracts the blocked-by: field,
splits commas, trims spaces, and checks for completed "[x]" entries in TODO.md
the same way, then update cron.sh and batch-cleanup-helper.sh to source
supervisor/_common.sh and replace their local is_task_unblocked()/calls with the
new is_task_blocked() implementation.
In @.agents/scripts/supervisor/cron.sh:
- Around line 554-555: Fix the path resolution for the batch-cleanup helper:
change how batch_cleanup_helper is built so it uses
"${SCRIPT_DIR}/batch-cleanup-helper.sh" (not
"${SCRIPT_DIR}/../batch-cleanup-helper.sh") so the -x check for
batch_cleanup_helper succeeds in the cron context; also add an error log when
the helper is not found/ not executable (matching the behavior used in
supervisor-helper.sh) so the Strategy 5 block doesn't silently fail. Reference:
variable SCRIPT_DIR, variable batch_cleanup_helper and the Strategy 5 block in
cron.sh.
| if [[ "$estimate" =~ ^([0-9]+)\.([0-9]+)h$ ]]; then | ||
| local hours="${BASH_REMATCH[1]}" | ||
| local frac="${BASH_REMATCH[2]}" | ||
| # Convert fractional hours: 0.5h = 30m, 1.5h = 90m | ||
| echo $((hours * 60 + frac * 6)) | ||
| return 0 | ||
| fi |
There was a problem hiding this comment.
Fractional hour parsing is incorrect for multi-digit decimals.
frac * 6 assumes a single-digit tenths value (e.g., ~0.5h → 5 * 6 = 30). Multi-digit fractions break: ~0.25h yields 25 * 6 = 150m instead of 15m, and ~0.75h yields 450m instead of 45m.
In practice, batch-cleanup targets ≤15m tasks so fractional hours are unlikely to appear, but the function advertises general support in its docstring (line 53).
Proposed fix using proper decimal arithmetic
if [[ "$estimate" =~ ^([0-9]+)\.([0-9]+)h$ ]]; then
local hours="${BASH_REMATCH[1]}"
local frac="${BASH_REMATCH[2]}"
- # Convert fractional hours: 0.5h = 30m, 1.5h = 90m
- echo $((hours * 60 + frac * 6))
+ # Convert fractional hours using awk for correct decimal handling
+ # e.g., 0.25h = 15m, 0.5h = 30m, 1.5h = 90m
+ awk "BEGIN { printf \"%d\", ${hours}.${frac} * 60 }"
return 0
fi📝 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.
| if [[ "$estimate" =~ ^([0-9]+)\.([0-9]+)h$ ]]; then | |
| local hours="${BASH_REMATCH[1]}" | |
| local frac="${BASH_REMATCH[2]}" | |
| # Convert fractional hours: 0.5h = 30m, 1.5h = 90m | |
| echo $((hours * 60 + frac * 6)) | |
| return 0 | |
| fi | |
| if [[ "$estimate" =~ ^([0-9]+)\.([0-9]+)h$ ]]; then | |
| local hours="${BASH_REMATCH[1]}" | |
| local frac="${BASH_REMATCH[2]}" | |
| # Convert fractional hours using awk for correct decimal handling | |
| # e.g., 0.25h = 15m, 0.5h = 30m, 1.5h = 90m | |
| awk "BEGIN { printf \"%d\", ${hours}.${frac} * 60 }" | |
| return 0 | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/batch-cleanup-helper.sh around lines 74 - 80, The
fractional-hour handling uses "frac * 6" which treats the fraction as a single
decimal digit and breaks for multi-digit fractions; update the branch that
matches "$estimate" to compute minutes correctly by interpreting the fractional
string as a proper decimal (e.g., build "0.$frac" or compute frac/(10^len) ) and
then calculate total minutes as hours*60 + decimal_fraction*60 using a reliable
tool (awk or bc) or integer math with the fraction length; adjust the echo to
output that computed minutes and keep the same match on BASH_REMATCH to locate
the code.
| is_task_unblocked() { | ||
| local task_line="$1" | ||
| local todo_file="$2" | ||
|
|
||
| # Extract blocked-by: field | ||
| local blocked_by | ||
| blocked_by=$(echo "$task_line" | grep -oE 'blocked-by:[^ ]+' | sed 's/blocked-by://' || true) | ||
|
|
||
| if [[ -z "$blocked_by" ]]; then | ||
| return 0 # No dependencies — unblocked | ||
| fi | ||
|
|
||
| # Check each dependency | ||
| local dep | ||
| IFS=',' read -ra deps <<<"$blocked_by" | ||
| for dep in "${deps[@]}"; do | ||
| dep="${dep// /}" | ||
| [[ -z "$dep" ]] && continue | ||
|
|
||
| # Check if dependency is complete ([x]) in TODO.md | ||
| local dep_line | ||
| dep_line=$(grep -E "^[[:space:]]*- \[x\] ${dep} " "$todo_file" 2>/dev/null | head -1 || true) | ||
| if [[ -z "$dep_line" ]]; then | ||
| # Dependency not complete — task is blocked | ||
| return 1 | ||
| fi | ||
| done | ||
|
|
||
| return 0 # All dependencies resolved | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find is_task_blocked definition and context
rg -n 'is_task_blocked' --type=sh -C3 | head -100Repository: marcusquinn/aidevops
Length of output: 559
🏁 Script executed:
# Find _check_and_skip_if_blocked definition
rg -n '_check_and_skip_if_blocked' --type=sh -C3 | head -100Repository: marcusquinn/aidevops
Length of output: 2072
🏁 Script executed:
# Check what scripts source what
fd '\.sh$' -x grep -l 'source\|^\.' {} \; | head -20Repository: marcusquinn/aidevops
Length of output: 663
🏁 Script executed:
# Get a summary of all script files and their sourcing patterns
fd '\.sh$' | sortRepository: marcusquinn/aidevops
Length of output: 11714
🏁 Script executed:
# Find where is_task_blocked is defined
rg -n '^is_task_blocked\s*\(' --type=sh -A 20Repository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
# Check what batch-cleanup-helper.sh sources
head -50 .agents/scripts/batch-cleanup-helper.shRepository: marcusquinn/aidevops
Length of output: 2006
🏁 Script executed:
# Check supervisor/_common.sh for task blocking logic
cat -n .agents/scripts/supervisor/_common.sh | head -100Repository: marcusquinn/aidevops
Length of output: 3560
🏁 Script executed:
# Broader search for is_task_blocked - check if it's defined anywhere
rg -n 'is_task_blocked' --type=shRepository: marcusquinn/aidevops
Length of output: 172
🏁 Script executed:
# Check supervisor/dispatch.sh since batch-cleanup-helper sources it
cat -n .agents/scripts/supervisor/dispatch.sh | head -80Repository: marcusquinn/aidevops
Length of output: 3116
🏁 Script executed:
# Check cron.sh around line 233 for is_task_blocked definition or context
sed -n '1,250p' .agents/scripts/supervisor/cron.sh | tail -100Repository: marcusquinn/aidevops
Length of output: 2580
🏁 Script executed:
# Search for any function definition starting with is_ or _check
rg -n '^\s*(is_|_check)' --type=sh .agents/scripts/supervisor/ | head -40Repository: marcusquinn/aidevops
Length of output: 1525
🏁 Script executed:
# Get the full cron.sh to understand its structure
wc -l .agents/scripts/supervisor/cron.shRepository: marcusquinn/aidevops
Length of output: 102
🏁 Script executed:
# Verify the exact context at cron.sh line 233
sed -n '220,240p' .agents/scripts/supervisor/cron.shRepository: marcusquinn/aidevops
Length of output: 670
🏁 Script executed:
# Check if is_task_blocked appears anywhere as an alias or indirect call
rg -n 'is_task_blocked|declare.*is_task_blocked' --type=shRepository: marcusquinn/aidevops
Length of output: 172
🏁 Script executed:
# Get the complete is_task_unblocked from batch-cleanup-helper for comparison
sed -n '91,120p' .agents/scripts/batch-cleanup-helper.shRepository: marcusquinn/aidevops
Length of output: 816
Extract and share blocked-by dependency check — cron.sh currently fails at runtime.
The function is_task_blocked() called at cron.sh:233 is undefined and will cause a runtime error. Meanwhile, batch-cleanup-helper.sh implements a working version: is_task_unblocked() (lines 91-120).
Extract this logic to supervisor/_common.sh as a shared is_task_blocked() function (inverting the return semantics to match cron.sh's expectation), then use it in both cron.sh and batch-cleanup-helper.sh to eliminate the duplication and fix the broken reference.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/batch-cleanup-helper.sh around lines 91 - 120, The cron job
fails because cron.sh calls an undefined is_task_blocked(); copy the
dependency-check logic from is_task_unblocked() in batch-cleanup-helper.sh into
supervisor/_common.sh as a single shared function named is_task_blocked()
(invert the boolean semantics so it returns 0 when the task IS blocked and 1
when unblocked), ensure it extracts the blocked-by: field, splits commas, trims
spaces, and checks for completed "[x]" entries in TODO.md the same way, then
update cron.sh and batch-cleanup-helper.sh to source supervisor/_common.sh and
replace their local is_task_unblocked()/calls with the new is_task_blocked()
implementation.
| # Check if already tracked in supervisor DB | ||
| if [[ -f "$SUPERVISOR_DB" ]]; then | ||
| local existing | ||
| existing=$(db "$SUPERVISOR_DB" "SELECT status FROM tasks WHERE id = '$(sql_escape "$task_id")';" 2>/dev/null || true) | ||
| if [[ -n "$existing" && "$existing" != "cancelled" && "$existing" != "complete" ]]; then | ||
| log_info " $task_id: already tracked in supervisor (status: $existing) — skipping" | ||
| continue | ||
| fi | ||
| fi |
There was a problem hiding this comment.
Completed tasks are not skipped — risks duplicate dispatch.
The condition on line 194 allows tasks in complete state to pass through as eligible. A complete task already has a worker that finished (and likely created a PR). Re-dispatching it in a batch creates duplicate work.
Compare with Strategy 1 in cron.sh (line 308) where complete tasks are explicitly skipped.
Proposed fix: also skip post-completion lifecycle states
- if [[ -n "$existing" && "$existing" != "cancelled" && "$existing" != "complete" ]]; then
+ if [[ -n "$existing" && "$existing" != "cancelled" ]]; then
log_info " $task_id: already tracked in supervisor (status: $existing) — skipping"
continue
fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/batch-cleanup-helper.sh around lines 190 - 198, The existing
check using the SUPERVISOR_DB/db/sql_escape lookup mistakenly allows tasks with
status "complete" to pass through; change the conditional around the variable
existing (result of db ... SELECT status ...) so that it treats "complete" as a
skipped/post-completion state (e.g. test for [[ -n "$existing" && ( "$existing"
== "cancelled" || "$existing" == "complete" ) ]] and then log_info " $task_id:
already tracked in supervisor (status: $existing) — skipping" and continue).
Update the condition in batch-cleanup-helper.sh where existing is set to ensure
complete tasks are skipped.
| # Dispatch headless worker | ||
| local dispatch_cmd=("$ai_cli" run --format json) | ||
|
|
||
| # Add model flag if supported | ||
| if [[ "$ai_cli" == "opencode" ]]; then | ||
| dispatch_cmd+=(--model "anthropic/claude-sonnet-4-6") | ||
| fi | ||
|
|
||
| nohup "${dispatch_cmd[@]}" "$worker_prompt" \ | ||
| >"$log_file" 2>&1 & | ||
| local worker_pid=$! | ||
| echo "$worker_pid" >"$BATCH_CLEANUP_PID_FILE" |
There was a problem hiding this comment.
Worker launched without cd to repo — will fail in cron context.
The nohup dispatch (line 363) inherits the caller's cwd. When triggered from cron or the supervisor pulse, this is likely $HOME, not the repository. The worker prompt references TODO.md and todo/ which won't be found.
Compare with dispatch_decomposition_worker in cron.sh which explicitly generates cd '${repo}' in its dispatch script (line 797).
Proposed fix: cd to repo before launching worker
# Dispatch headless worker
- local dispatch_cmd=("$ai_cli" run --format json)
+ # Ensure worker runs in the target repository
+ cd "$repo" || {
+ log_error "Failed to cd to repo: $repo"
+ return 1
+ }
+
+ local dispatch_cmd=("$ai_cli" run --format json)Alternatively, follow the pattern from dispatch_decomposition_worker and generate a wrapper script that includes an explicit cd.
📝 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.
| # Dispatch headless worker | |
| local dispatch_cmd=("$ai_cli" run --format json) | |
| # Add model flag if supported | |
| if [[ "$ai_cli" == "opencode" ]]; then | |
| dispatch_cmd+=(--model "anthropic/claude-sonnet-4-6") | |
| fi | |
| nohup "${dispatch_cmd[@]}" "$worker_prompt" \ | |
| >"$log_file" 2>&1 & | |
| local worker_pid=$! | |
| echo "$worker_pid" >"$BATCH_CLEANUP_PID_FILE" | |
| # Dispatch headless worker | |
| # Ensure worker runs in the target repository | |
| cd "$repo" || { | |
| log_error "Failed to cd to repo: $repo" | |
| return 1 | |
| } | |
| local dispatch_cmd=("$ai_cli" run --format json) | |
| # Add model flag if supported | |
| if [[ "$ai_cli" == "opencode" ]]; then | |
| dispatch_cmd+=(--model "anthropic/claude-sonnet-4-6") | |
| fi | |
| nohup "${dispatch_cmd[@]}" "$worker_prompt" \ | |
| >"$log_file" 2>&1 & | |
| local worker_pid=$! | |
| echo "$worker_pid" >"$BATCH_CLEANUP_PID_FILE" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/batch-cleanup-helper.sh around lines 355 - 366, The worker
is launched with nohup using dispatch_cmd and inherits the caller CWD so file
references in worker_prompt (e.g., TODO.md, todo/) will fail under cron; update
the dispatch to cd into the repository before launching the worker (either
prepend a "cd '${repo}' &&" to the dispatched command or generate a small
wrapper script like dispatch_decomposition_worker in cron.sh that does an
explicit cd then exec the ai_cli command), ensuring the code that builds
dispatch_cmd (and the nohup invocation) runs from the repo and still writes the
worker PID to BATCH_CLEANUP_PID_FILE.
| local batch_cleanup_helper="${SCRIPT_DIR}/../batch-cleanup-helper.sh" | ||
| if [[ -x "$batch_cleanup_helper" ]]; then |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the directory structure and where SCRIPT_DIR resolves
echo "=== Looking for batch-cleanup-helper.sh ==="
fd -t f 'batch-cleanup-helper.sh'
echo ""
echo "=== SCRIPT_DIR definition in supervisor-helper.sh ==="
rg -n 'SCRIPT_DIR=' .agents/scripts/supervisor-helper.sh | head -5
echo ""
echo "=== Path used in cron.sh (lines around 554) ==="
sed -n '550,560p' .agents/scripts/supervisor/cron.sh
echo ""
echo "=== All batch-cleanup-helper references in supervisor-helper.sh ==="
rg -n 'batch.*cleanup.*helper' .agents/scripts/supervisor-helper.sh
echo ""
echo "=== All batch-cleanup-helper references in cron.sh ==="
rg -n 'batch.*cleanup.*helper' .agents/scripts/supervisor/cron.shRepository: marcusquinn/aidevops
Length of output: 1657
Path resolution bug — Strategy 5 will never execute in cron context.
SCRIPT_DIR is set to .agents/scripts/ in supervisor-helper.sh line 167. The path ${SCRIPT_DIR}/../batch-cleanup-helper.sh resolves to .agents/batch-cleanup-helper.sh, but the helper actually lives at .agents/scripts/batch-cleanup-helper.sh.
Compare with the correct path in supervisor-helper.sh line 702: ${SCRIPT_DIR}/batch-cleanup-helper.sh (without ../). The -x check silently fails, making the entire Strategy 5 block dead code.
Proposed fix
- local batch_cleanup_helper="${SCRIPT_DIR}/../batch-cleanup-helper.sh"
+ local batch_cleanup_helper="${SCRIPT_DIR}/batch-cleanup-helper.sh"
if [[ -x "$batch_cleanup_helper" ]]; thenAdditionally, add error logging when the helper isn't found (consistent with supervisor-helper.sh line 704):
if [[ -x "$batch_cleanup_helper" ]]; then
+ else
+ log_warn "batch-cleanup-helper.sh not found or not executable at $batch_cleanup_helper"
fi📝 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.
| local batch_cleanup_helper="${SCRIPT_DIR}/../batch-cleanup-helper.sh" | |
| if [[ -x "$batch_cleanup_helper" ]]; then | |
| local batch_cleanup_helper="${SCRIPT_DIR}/batch-cleanup-helper.sh" | |
| if [[ -x "$batch_cleanup_helper" ]]; then |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/supervisor/cron.sh around lines 554 - 555, Fix the path
resolution for the batch-cleanup helper: change how batch_cleanup_helper is
built so it uses "${SCRIPT_DIR}/batch-cleanup-helper.sh" (not
"${SCRIPT_DIR}/../batch-cleanup-helper.sh") so the -x check for
batch_cleanup_helper succeeds in the cron context; also add an error log when
the helper is not found/ not executable (matching the behavior used in
supervisor-helper.sh) so the Strategy 5 block doesn't silently fail. Reference:
variable SCRIPT_DIR, variable batch_cleanup_helper and the Strategy 5 block in
cron.sh.



Adds batch-cleanup mode to the supervisor to reduce worktree/PR overhead for simple chore tasks.
Problem
Each TODO.md cleanup task (mark cancelled, fix duplicate, fix typo) currently creates its own worktree and PR. For N simple text edits, this costs N*(worktree setup + PR creation + CI run + merge) when a single commit would suffice.
Solution
batch-cleanup-helper.sh: New script that scans for eligible tasks and dispatches a single worker to handle all of them in one PRcmd_auto_pickup: Integrates batch-cleanup into the supervisor pulse cycle — triggers automatically when >=2 eligible tasks are foundsupervisor-helper.sh batch-cleanup: New command for manual invocationEligibility Criteria
#chorein TODO.md<=15m(~5m, ~10m, ~15m)blocked-by:dependenciesassignee:orstarted:fields (unclaimed)Expected Savings
Quality
Ref #1722
Summary by CodeRabbit