refactor: AI-driven task lifecycle engine replacing hardcoded bash heuristics#2107
refactor: AI-driven task lifecycle engine replacing hardcoded bash heuristics#2107marcusquinn merged 1 commit intomainfrom
Conversation
…tics Replace the 800-line cmd_pr_lifecycle case-statement approach with an intelligence-first design: gather real-world state, decide next action, execute, update TODO status tags. Key changes: - New ai-lifecycle.sh: fast-path deterministic decisions (CLEAN->merge, BEHIND->update_branch, MERGED->deploy) handle 90%+ of cases with zero AI cost. Ambiguous cases (conflicts, CI failures) get a focused sonnet call that picks the obvious next step. - status: tags on TODO.md lines communicate exact state at every decision point (status:ci-running, status:merging, status:behind-main, etc.) - Phase 3 in pulse.sh now calls process_ai_lifecycle() by default. SUPERVISOR_AI_LIFECYCLE=false falls back to legacy behavior. - Phase 3.5/3.6 (rebase retry, opus escalation) skipped when AI lifecycle active — those scenarios are handled by the AI engine directly. Fixes the awards app stuck-PR problem: 6 PRs with mergeStateStatus=BEHIND were trapped in rebase-fail->block->reset loops. The fast path now calls gh pr update-branch (GitHub API, no local git) instead of attempting complex local rebases.
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 significantly upgrades the supervisor's ability to manage the lifecycle of pull requests by replacing rigid, hardcoded bash logic with a flexible, AI-driven decision-making system. The primary goal is to resolve common PR blocking issues, such as those caused by failed local git rebases on non-conflicting 'BEHIND' branches, and to provide clearer, real-time status updates for tasks. By leveraging AI for complex scenarios and GitHub API for simpler ones, the system aims to streamline the PR merge and deployment process, reducing manual intervention and improving overall efficiency. Highlights
Changelog
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
|
WalkthroughIntroduces a comprehensive AI-driven lifecycle engine for task automation, replacing hardcoded heuristics with intelligent decision-making. The system gathers task state, queries AI for optimal actions, executes those actions, and tracks progress via status tags. Controlled via SUPERVISOR_AI_LIFECYCLE environment toggle. Changes
Sequence Diagram(s)sequenceDiagram
actor Supervisor
participant TaskDB as Task State DB
participant GitRepo as Git/Repo
participant AIEngine as AI Engine
participant GitHub as GitHub API
participant Deploy as Deploy System
Supervisor->>TaskDB: Fetch task metadata
TaskDB-->>Supervisor: Task data
Supervisor->>GitRepo: Check worktree status
GitRepo-->>Supervisor: Branch, commits, status
Supervisor->>GitHub: Fetch PR metadata
GitHub-->>Supervisor: PR state, reviews, CI status
Supervisor->>Supervisor: gather_task_state() → STATE block
Supervisor->>Supervisor: fast_path_decision(state) → action?
alt Fast Path Match
Supervisor->>Supervisor: Use deterministic action
else No Fast Path
Supervisor->>AIEngine: decide_next_action(STATE)
AIEngine-->>Supervisor: {action, reason, status_tag}
end
Supervisor->>TaskDB: update_task_status_tag(status_tag)
TaskDB-->>Supervisor: ✓
Supervisor->>Supervisor: execute_lifecycle_action(action)
alt Action: merge_pr
Supervisor->>GitHub: Merge PR
GitHub-->>GitRepo: Update main branch
else Action: update_branch
Supervisor->>GitRepo: Rebase/merge main
else Action: deploy
Supervisor->>Deploy: Execute deployment
Deploy-->>Supervisor: Deployment result
else Action: escalate
Supervisor->>Supervisor: Spawn additional AI worker
end
Supervisor->>GitRepo: Commit status tag updates
GitRepo-->>Supervisor: ✓
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes The review demands careful analysis of 980 lines of new AI-driven logic with multiple decision paths, state management, and 11 distinct action handlers. Integration into pulse.sh requires understanding conditional routing and fallback semantics. Logic density is high (AI prompting, JSON parsing, Git/GitHub interactions), and heterogeneous changes across three files necessitate separate reasoning for each function's correctness. 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 |
🔍 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: Sat Feb 21 14:41:54 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
|
There was a problem hiding this comment.
Code Review
This pull request introduces a significant and well-designed refactoring by replacing hardcoded bash heuristics with a new AI-driven task lifecycle engine. The implementation is clean and the use of a feature flag in pulse.sh is a great practice. My review focuses on the new ai-lifecycle.sh script, where I've identified a few areas for improvement, primarily concerning error handling and script robustness. There are several instances of error suppression using 2>/dev/null which can mask underlying issues and make debugging difficult. Additionally, a couple of commands could cause the script to exit unexpectedly under set -e, and the JSON parsing from the AI response is a bit brittle. Addressing these points will make the new engine even more resilient.
| if [[ -z "$trepo" ]]; then | ||
| local escaped_id | ||
| escaped_id=$(sql_escape "$task_id") | ||
| trepo=$(db "$SUPERVISOR_DB" "SELECT repo FROM tasks WHERE id = '$escaped_id';" 2>/dev/null || echo "") |
There was a problem hiding this comment.
The use of 2>/dev/null here, and in many other places throughout this script (e.g., with gh, jq, resolve_ai_cli, parse_pr_url), suppresses potentially critical error messages. If the database query fails due to a lock, corruption, or other issue, the error is hidden, and the script proceeds with an empty trepo variable, leading to silent failures that are difficult to debug. This is a violation of the general rule to avoid blanket error suppression.
To improve robustness, please remove 2>/dev/null and allow errors to be logged or handled. The || echo "" construct already prevents the script from exiting if the db command fails.
| trepo=$(db "$SUPERVISOR_DB" "SELECT repo FROM tasks WHERE id = '$escaped_id';" 2>/dev/null || echo "") | |
| trepo=$(db "$SUPERVISOR_DB" "SELECT repo FROM tasks WHERE id = '$escaped_id';" || echo "") |
References
- Avoid using '2>/dev/null' for blanket suppression of command errors in shell scripts to ensure that authentication, syntax, or system issues remain visible for debugging.
|
|
||
| # Extract JSON from response (may have preamble/postamble) | ||
| local json_block | ||
| json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1) |
There was a problem hiding this comment.
The regex \{[^}]+\} used to extract the JSON block is brittle and will fail if the AI's response contains a JSON object with nested objects (e.g., {"key": {"nested_key": "value"}}). While the current prompt asks for a flat object, future changes or slight deviations from the AI could break this parsing. For robust JSON parsing, jq should be used. A common pattern is to first extract the potential JSON string (e.g., with grep -oP) and then pipe it to jq for validation and parsing, handling potential failures gracefully.
| json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1) | |
| json_block=$(printf '%s' "$ai_result" | grep -oP '(\{.*\})' | head -1 | jq -c . 2>/dev/null || echo '{}') |
References
- To reliably wrap the entire content of a shell variable as a single JSON string, use
jq -Rn --arg v "$VAR" '$v'. This is more robust than piping the variable tojq -Rs '.'. - When a shell function is expected to return JSON, it should output a valid empty JSON array (
[]) or object ({}) on no results, rather than returning a non-zero exit code or empty string, to ensure consumers of the output (like pipes tojq) do not break.
|
|
||
| # Find the task line (open checkbox only — completed tasks don't get status updates) | ||
| local line_num | ||
| line_num=$(grep -nE "^[[:space:]]*- \[ \] ${task_id}( |$)" "$todo_file" | head -1 | cut -d: -f1) |
There was a problem hiding this comment.
With set -euo pipefail active, this command pipeline will cause the script to exit if grep doesn't find a match, as grep will return a non-zero exit code. The subsequent check on if [[ -z "$line_num" ]] would never be reached. This violates the style guide's recommendation to guard commands like grep that can fail on no-match scenarios.
To prevent the script from exiting unexpectedly, you should guard the pipeline.
| line_num=$(grep -nE "^[[:space:]]*- \[ \] ${task_id}( |$)" "$todo_file" | head -1 | cut -d: -f1) | |
| line_num=$(grep -nE "^[[:space:]]*- \[ \] ${task_id}( |$)" "$todo_file" | head -1 | cut -d: -f1) || true |
References
- In shell scripts with 'set -e' enabled, use '|| true' to prevent the script from exiting when a command like 'jq' fails on an optional lookup. Do not suppress stderr with '2>/dev/null' so that actual syntax or system errors remain visible for debugging.
| # CI still running → wait | ||
| if [[ "$pr_ci" == *"pending:"* ]]; then | ||
| local pending_count | ||
| pending_count=$(printf '%s' "$pr_ci" | grep -oE 'pending:[0-9]+' | cut -d: -f2) |
There was a problem hiding this comment.
Similar to a previous comment, this grep command is unguarded. If grep does not find a match for pending:[0-9]+, it will exit with a non-zero status, causing the entire script to terminate due to set -euo pipefail. The logic to handle a zero count in the following if statement will not be executed. The pipeline should be guarded to ensure the script continues gracefully when there are no pending checks.
| pending_count=$(printf '%s' "$pr_ci" | grep -oE 'pending:[0-9]+' | cut -d: -f2) | |
| pending_count=$(printf '%s' "$pr_ci" | grep -oE 'pending:[0-9]+' | cut -d: -f2) || true |
References
- In shell scripts with 'set -e' enabled, use '|| true' to prevent the script from exiting when a command like 'jq' fails on an optional lookup. Do not suppress stderr with '2>/dev/null' so that actual syntax or system errors remain visible for debugging.
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/reference/planning-detail.md:
- Line 65: Replace the unhyphenated compound phrase "decision making" with the
hyphenated "decision-making" in the descriptive sentence that explains the AI
lifecycle engine (the sentence mentioning SUPERVISOR_AI_LIFECYCLE and
cmd_pr_lifecycle) so it reads "intelligence-first decision-making" to correctly
form the compound adjective; update that occurrence in planning-detail.md
wherever the phrase appears.
In @.agents/scripts/supervisor/ai-lifecycle.sh:
- Around line 109-121: The sed replacement can break when task text contains |
or &; before calling sed_inplace replace the line, escape the updated_line so
sed special chars (|, /, &) are backslash-escaped. In other words, transform
updated_line into an escaped variable (used by the existing sed_inplace
invocation) and then call sed_inplace with that escaped value; reference the
existing symbols updated_line, sed_inplace, todo_file and line_num so you escape
updated_line (e.g., replace special chars with backslash-prefixed versions) and
then run sed_inplace "${line_num}s|.*|${escaped_updated_line}|" "$todo_file".
- Around line 431-443: Currently run_deploy_for_task is masked by "|| true", so
failures are ignored and the task is marked deployed; change the logic to check
the command exit status and only call update_task_status_tag "$task_id"
"deployed" and cmd_transition "$task_id" "deployed" when run_deploy_for_task
succeeds, otherwise log the error and mark the task blocked (e.g.,
update_task_status_tag "$task_id" "blocked" and cmd_transition "$task_id"
"blocked") and include the deploy failure details from run_deploy_for_task in
the SUPERVISOR_LOG; update the same pattern at both occurrences that currently
use "|| true" and follow the conditional pattern used by deploy.sh's if !
run_deploy_for_task ... to gate transitions.
- Around line 805-835: Extract pr_review from the task state (like the other
fields: pr_review=$(printf '%s' "$task_state" | grep '^PR_REVIEW:' | cut -d' '
-f2)) and add a guard to the CLEAN and UNSTABLE fast-paths so they only emit
merge actions when the review decision indicates approval (e.g. require
pr_review == "APPROVED"); if pr_review is "CHANGES_REQUESTED", "REVIEW_REQUIRED"
or any non-approved value, do not auto-merge and let the normal review/hold
logic handle the PR.
- Around line 460-480: In the update_branch case, remove the forced empty
parameter "-f expected_head_sha=\"\"" from the gh api PUT to
"repos/${pr_repo_slug}/pulls/${pr_number}/update-branch" so the optional
expected_head_sha is omitted (allowing the API to use the PR's current HEAD);
keep the same gh invocation, logging (log_info/log_success/log_warn), and DB
reset (db ... rebase_attempts = 0) behavior unchanged—only stop sending the
empty expected_head_sha field.
| | `status:changes-requested` | Human reviewer requested changes | | ||
| | `status:blocked:<reason>` | Cannot proceed, reason given | | ||
|
|
||
| The AI lifecycle engine (`SUPERVISOR_AI_LIFECYCLE=true`, default) replaces hardcoded bash heuristics with intelligence-first decision making. For each active task, it gathers real-world state (DB, GitHub PR, CI, git), decides the next action, executes it, and updates the status tag. Set `SUPERVISOR_AI_LIFECYCLE=false` to fall back to the legacy `cmd_pr_lifecycle` bash heuristics. |
There was a problem hiding this comment.
Hyphenate the compound adjective.
“Decision making” should be “decision‑making” in this compound phrase.
✍️ Proposed fix
-The AI lifecycle engine (`SUPERVISOR_AI_LIFECYCLE=true`, default) replaces hardcoded bash heuristics with intelligence-first decision making.
+The AI lifecycle engine (`SUPERVISOR_AI_LIFECYCLE=true`, default) replaces hardcoded bash heuristics with intelligence-first decision-making.📝 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.
| The AI lifecycle engine (`SUPERVISOR_AI_LIFECYCLE=true`, default) replaces hardcoded bash heuristics with intelligence-first decision making. For each active task, it gathers real-world state (DB, GitHub PR, CI, git), decides the next action, executes it, and updates the status tag. Set `SUPERVISOR_AI_LIFECYCLE=false` to fall back to the legacy `cmd_pr_lifecycle` bash heuristics. | |
| The AI lifecycle engine (`SUPERVISOR_AI_LIFECYCLE=true`, default) replaces hardcoded bash heuristics with intelligence-first decision-making. For each active task, it gathers real-world state (DB, GitHub PR, CI, git), decides the next action, executes it, and updates the status tag. Set `SUPERVISOR_AI_LIFECYCLE=false` to fall back to the legacy `cmd_pr_lifecycle` bash heuristics. |
🧰 Tools
🪛 LanguageTool
[grammar] ~65-~65: Use a hyphen to join words.
Context: ...ristics with intelligence-first decision making. For each active task, it gathers...
(QB_NEW_EN_HYPHEN)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/reference/planning-detail.md at line 65, Replace the unhyphenated
compound phrase "decision making" with the hyphenated "decision-making" in the
descriptive sentence that explains the AI lifecycle engine (the sentence
mentioning SUPERVISOR_AI_LIFECYCLE and cmd_pr_lifecycle) so it reads
"intelligence-first decision-making" to correctly form the compound adjective;
update that occurrence in planning-detail.md wherever the phrase appears.
| local task_line | ||
| task_line=$(sed -n "${line_num}p" "$todo_file") | ||
|
|
||
| # Remove existing status: tag if present | ||
| local updated_line | ||
| updated_line=$(printf '%s' "$task_line" | sed -E 's/ status:[^ ]*//') | ||
|
|
||
| # Append new status tag | ||
| updated_line="${updated_line} status:${new_status}" | ||
|
|
||
| # Replace the line in-place | ||
| sed_inplace "${line_num}s|.*|${updated_line}|" "$todo_file" | ||
|
|
There was a problem hiding this comment.
Escape TODO line content before sed replacement.
Task descriptions can contain | or &, which will break the replacement and corrupt TODO.md.
🛠️ Proposed fix
# Append new status tag
updated_line="${updated_line} status:${new_status}"
# Replace the line in-place
- sed_inplace "${line_num}s|.*|${updated_line}|" "$todo_file"
+ local escaped_line
+ escaped_line=$(printf '%s' "$updated_line" | sed -e 's/[\\/&|]/\\&/g')
+ sed_inplace "${line_num}s|.*|${escaped_line}|" "$todo_file"📝 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 task_line | |
| task_line=$(sed -n "${line_num}p" "$todo_file") | |
| # Remove existing status: tag if present | |
| local updated_line | |
| updated_line=$(printf '%s' "$task_line" | sed -E 's/ status:[^ ]*//') | |
| # Append new status tag | |
| updated_line="${updated_line} status:${new_status}" | |
| # Replace the line in-place | |
| sed_inplace "${line_num}s|.*|${updated_line}|" "$todo_file" | |
| # Append new status tag | |
| updated_line="${updated_line} status:${new_status}" | |
| # Replace the line in-place | |
| local escaped_line | |
| escaped_line=$(printf '%s' "$updated_line" | sed -e 's/[\\/&|]/\\&/g') | |
| sed_inplace "${line_num}s|.*|${escaped_line}|" "$todo_file" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/supervisor/ai-lifecycle.sh around lines 109 - 121, The sed
replacement can break when task text contains | or &; before calling sed_inplace
replace the line, escape the updated_line so sed special chars (|, /, &) are
backslash-escaped. In other words, transform updated_line into an escaped
variable (used by the existing sed_inplace invocation) and then call sed_inplace
with that escaped value; reference the existing symbols updated_line,
sed_inplace, todo_file and line_num so you escape updated_line (e.g., replace
special chars with backslash-prefixed versions) and then run sed_inplace
"${line_num}s|.*|${escaped_updated_line}|" "$todo_file".
| # Post-merge: pull main, rebase siblings, deploy | ||
| git -C "$repo_path" pull --rebase origin main 2>>"$SUPERVISOR_LOG" || true | ||
| rebase_sibling_prs_after_merge "$task_id" 2>>"$SUPERVISOR_LOG" || true | ||
|
|
||
| # Run postflight + deploy | ||
| run_postflight_for_task "$task_id" "$repo_path" 2>>"$SUPERVISOR_LOG" || true | ||
| update_task_status_tag "$task_id" "deploying" "$repo_path" | ||
| cmd_transition "$task_id" "deploying" 2>>"$SUPERVISOR_LOG" || true | ||
|
|
||
| run_deploy_for_task "$task_id" "$repo_path" 2>>"$SUPERVISOR_LOG" || true | ||
| cmd_transition "$task_id" "deployed" 2>>"$SUPERVISOR_LOG" || true | ||
| update_task_status_tag "$task_id" "deployed" "$repo_path" | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, let's confirm the file exists and inspect the relevant lines
wc -l .agents/scripts/supervisor/ai-lifecycle.shRepository: marcusquinn/aidevops
Length of output: 110
🏁 Script executed:
#!/bin/bash
# Read the specific lines mentioned in the review (431-443)
sed -n '431,443p' .agents/scripts/supervisor/ai-lifecycle.shRepository: marcusquinn/aidevops
Length of output: 717
🏁 Script executed:
#!/bin/bash
# Also read lines 581-586
sed -n '581,586p' .agents/scripts/supervisor/ai-lifecycle.shRepository: marcusquinn/aidevops
Length of output: 395
🏁 Script executed:
#!/bin/bash
# Find run_deploy_for_task definition and its behavior
rg -n "run_deploy_for_task" .agents/scripts/supervisor/ -g '*.sh' -A 5Repository: marcusquinn/aidevops
Length of output: 2833
🏁 Script executed:
#!/bin/bash
# Check for ShellCheck availability and run it on the file per coding guidelines
if command -v shellcheck &> /dev/null; then
shellcheck -x .agents/scripts/supervisor/ai-lifecycle.sh || true
else
echo "ShellCheck not available"
fiRepository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Get context around line 440 to see the full function
sed -n '420,450p' .agents/scripts/supervisor/ai-lifecycle.shRepository: marcusquinn/aidevops
Length of output: 1378
🏁 Script executed:
#!/bin/bash
# Get context around line 581 to see the full function
sed -n '570,595p' .agents/scripts/supervisor/ai-lifecycle.shRepository: marcusquinn/aidevops
Length of output: 1048
🏁 Script executed:
#!/bin/bash
# Inspect the run_deploy_for_task function definition to confirm it can return non-zero
sed -n '40,80p' .agents/scripts/supervisor/deploy.shRepository: marcusquinn/aidevops
Length of output: 1518
Gate task deployment status on deploy success.
run_deploy_for_task failures are masked by || true at two locations (lines 440–442 and 581–583), allowing tasks to transition to deployed with update_task_status_tag and update_todo_on_complete executed despite failed releases. Compare with deploy.sh:698 which correctly uses if ! run_deploy_for_task to handle failures. Update both occurrences to verify deploy success before marking deployed, or transition to blocked with error details.
Suggested fix for both locations
- run_deploy_for_task "$task_id" "$repo_path" 2>>"$SUPERVISOR_LOG" || true
- cmd_transition "$task_id" "deployed" 2>>"$SUPERVISOR_LOG" || true
- update_task_status_tag "$task_id" "deployed" "$repo_path"
+ if run_deploy_for_task "$task_id" "$repo_path" 2>>"$SUPERVISOR_LOG"; then
+ cmd_transition "$task_id" "deployed" 2>>"$SUPERVISOR_LOG" || true
+ update_task_status_tag "$task_id" "deployed" "$repo_path"
+ else
+ log_warn "ai-lifecycle: deploy failed for $task_id"
+ update_task_status_tag "$task_id" "blocked:deploy-failed" "$repo_path"
+ cmd_transition "$task_id" "blocked" --error "Deploy failed" 2>>"$SUPERVISOR_LOG" || true
+ return 1
+ fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/supervisor/ai-lifecycle.sh around lines 431 - 443, Currently
run_deploy_for_task is masked by "|| true", so failures are ignored and the task
is marked deployed; change the logic to check the command exit status and only
call update_task_status_tag "$task_id" "deployed" and cmd_transition "$task_id"
"deployed" when run_deploy_for_task succeeds, otherwise log the error and mark
the task blocked (e.g., update_task_status_tag "$task_id" "blocked" and
cmd_transition "$task_id" "blocked") and include the deploy failure details from
run_deploy_for_task in the SUPERVISOR_LOG; update the same pattern at both
occurrences that currently use "|| true" and follow the conditional pattern used
by deploy.sh's if ! run_deploy_for_task ... to gate transitions.
| update_branch) | ||
| log_info "ai-lifecycle: updating PR branch for $task_id via GitHub API" | ||
| update_task_status_tag "$task_id" "updating-branch" "$repo_path" | ||
|
|
||
| if [[ -z "$pr_number" || -z "$pr_repo_slug" ]]; then | ||
| log_warn "ai-lifecycle: no PR number/repo for update_branch on $task_id" | ||
| return 1 | ||
| fi | ||
|
|
||
| # Use GitHub API to update the branch (no local git needed) | ||
| if gh api "repos/${pr_repo_slug}/pulls/${pr_number}/update-branch" \ | ||
| -X PUT -f expected_head_sha="" 2>>"$SUPERVISOR_LOG"; then | ||
| log_success "ai-lifecycle: branch updated for $task_id — CI will re-run" | ||
| update_task_status_tag "$task_id" "ci-running" "$repo_path" | ||
| # Reset rebase counter since we used a different strategy | ||
| db "$SUPERVISOR_DB" "UPDATE tasks SET rebase_attempts = 0 WHERE id = '$escaped_id';" 2>/dev/null || true | ||
| return 0 | ||
| else | ||
| log_warn "ai-lifecycle: GitHub API update-branch failed for $task_id — will try rebase next cycle" | ||
| update_task_status_tag "$task_id" "behind-main" "$repo_path" | ||
| return 1 |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
GitHub REST API pull request update-branch expected_head_sha parameter documentation
💡 Result:
In the GitHub REST API, expected_head_sha is documented as an optional JSON body parameter for the “Update a pull request branch” endpoint:
- Endpoint:
PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch expected_head_shameaning: “The expected SHA of the pull request’s HEAD ref” (i.e., the most recent commit on the PR’s head branch). If you provide it, GitHub will only perform the update if the PR’s current head SHA still matches what you sent—otherwise you get422 Unprocessable Entity.- Default behavior: If omitted, it defaults to the SHA of the pull request’s current HEAD ref.
This is all described in the REST “pulls” docs under Parameters for “Update a pull request branch”. [1]
Source (official docs): GitHub REST API → Pull requests → “Update a pull request branch” (expected_head_sha). [1]
[1] (docs.github.com)
Citations:
🏁 Script executed:
#!/bin/bash
# Check if ShellCheck is available and run it on the script
if command -v shellcheck &> /dev/null; then
shellcheck -f gcc .agents/scripts/supervisor/ai-lifecycle.sh 2>&1 | head -100
else
echo "ShellCheck not found, will attempt to install and run"
# Try to find the script first
fd "ai-lifecycle.sh" .agents/scripts/
fiRepository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Find the script file
fd "ai-lifecycle.sh" --full-path ".agents/scripts"Repository: marcusquinn/aidevops
Length of output: 106
🏁 Script executed:
#!/bin/bash
# If found, run ShellCheck on it
script_file=".agents/scripts/supervisor/ai-lifecycle.sh"
if [[ -f "$script_file" ]]; then
wc -l "$script_file"
echo "---"
sed -n '460,480p' "$script_file"
else
echo "Script not found at expected path"
# List what's in the .agents directory
find .agents -name "*.sh" -type f 2>/dev/null | head -20
fiRepository: marcusquinn/aidevops
Length of output: 1120
🏁 Script executed:
#!/bin/bash
# Run ShellCheck on the script (install if needed)
if ! command -v shellcheck &> /dev/null; then
apt-get update -qq && apt-get install -qq shellcheck > /dev/null 2>&1
fi
shellcheck -f gcc .agents/scripts/supervisor/ai-lifecycle.shRepository: marcusquinn/aidevops
Length of output: 46
Omit expected_head_sha parameter to avoid API rejection.
Passing an empty string causes GitHub to return 422 Unprocessable Entity since the parameter is optional and expects either a valid SHA or to be omitted entirely. When omitted, the API correctly defaults to the PR's current HEAD ref, enabling the fast-path update as intended.
Fix
- if gh api "repos/${pr_repo_slug}/pulls/${pr_number}/update-branch" \
- -X PUT -f expected_head_sha="" 2>>"$SUPERVISOR_LOG"; then
+ if gh api "repos/${pr_repo_slug}/pulls/${pr_number}/update-branch" \
+ -X PUT 2>>"$SUPERVISOR_LOG"; then🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/supervisor/ai-lifecycle.sh around lines 460 - 480, In the
update_branch case, remove the forced empty parameter "-f
expected_head_sha=\"\"" from the gh api PUT to
"repos/${pr_repo_slug}/pulls/${pr_number}/update-branch" so the optional
expected_head_sha is omitted (allowing the API to use the PR's current HEAD);
keep the same gh invocation, logging (log_info/log_success/log_warn), and DB
reset (db ... rebase_attempts = 0) behavior unchanged—only stop sending the
empty expected_head_sha field.
| # Extract key fields from state snapshot | ||
| local db_status pr_state pr_merge_state pr_ci pr_url | ||
| db_status=$(printf '%s' "$task_state" | grep '^DB_STATUS:' | cut -d' ' -f2) | ||
| pr_state=$(printf '%s' "$task_state" | grep '^PR_STATE:' | cut -d' ' -f2) | ||
| pr_merge_state=$(printf '%s' "$task_state" | grep '^PR_MERGE_STATE:' | cut -d' ' -f2) | ||
| pr_ci=$(printf '%s' "$task_state" | grep '^PR_CI:' | cut -d' ' -f2-) | ||
| pr_url=$(printf '%s' "$task_state" | grep '^PR_URL:' | cut -d' ' -f2) | ||
|
|
||
| # PR already merged → deploy | ||
| if [[ "$pr_state" == "MERGED" ]]; then | ||
| echo '{"action":"deploy","reason":"PR already merged","status_tag":"deploying"}' | ||
| return 0 | ||
| fi | ||
|
|
||
| # No PR and task is complete → mark deployed | ||
| if [[ "$db_status" == "complete" && ("$pr_url" == "none" || "$pr_url" == "no_pr" || "$pr_url" == "task_only" || "$pr_url" == "verified_complete") ]]; then | ||
| echo '{"action":"mark_deployed","reason":"No PR, task complete","status_tag":"deployed"}' | ||
| return 0 | ||
| fi | ||
|
|
||
| # PR is CLEAN (all checks passed, no conflicts) → merge | ||
| if [[ "$pr_merge_state" == "CLEAN" ]]; then | ||
| echo '{"action":"merge_pr","reason":"CI passed, PR clean","status_tag":"merging"}' | ||
| return 0 | ||
| fi | ||
|
|
||
| # PR is UNSTABLE (non-required checks failed) → merge (safe) | ||
| if [[ "$pr_merge_state" == "UNSTABLE" ]]; then | ||
| echo '{"action":"merge_pr","reason":"Required checks passed (non-required failed)","status_tag":"merging"}' | ||
| return 0 | ||
| fi |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's see the full context of the function containing these lines
head -n 850 .agents/scripts/supervisor/ai-lifecycle.sh | tail -n 100Repository: marcusquinn/aidevops
Length of output: 3601
🏁 Script executed:
# Check if PR_REVIEW field is used elsewhere in the file
rg 'PR_REVIEW' .agents/scripts/supervisor/ai-lifecycle.sh -B 2 -A 2Repository: marcusquinn/aidevops
Length of output: 228
🏁 Script executed:
# Let's see the function signature and understand its purpose
sed -n '700,810p' .agents/scripts/supervisor/ai-lifecycle.shRepository: marcusquinn/aidevops
Length of output: 3951
🏁 Script executed:
# Check for any ShellCheck issues in this file as per coding guidelines
command -v shellcheck >/dev/null 2>&1 && shellcheck .agents/scripts/supervisor/ai-lifecycle.sh -x || echo "ShellCheck not available, searching for syntax issues..."Repository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
# Search for where task_state is populated to understand what fields it contains
rg 'task_state=' .agents/scripts/supervisor/ai-lifecycle.sh -B 1 -A 1 | head -n 30Repository: marcusquinn/aidevops
Length of output: 312
🏁 Script executed:
# Find the gather_task_state function to confirm PR_REVIEW is populated
rg 'gather_task_state' .agents/scripts/supervisor/ai-lifecycle.sh -A 50 | head -n 80Repository: marcusquinn/aidevops
Length of output: 2845
🏁 Script executed:
# Check if there are any guards elsewhere that catch CHANGES_REQUESTED or REVIEW_REQUIRED
rg 'CHANGES_REQUESTED|REVIEW_REQUIRED' .agents/scripts/supervisor/ai-lifecycle.sh -B 2 -A 2Repository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
# Validate the suggested shell syntax for the guard conditions
# Let's check if there are similar guard patterns in the file
rg '\[[ ]*\$' .agents/scripts/supervisor/ai-lifecycle.sh | head -n 10Repository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
# Get the complete gather_task_state function to see how pr_review_decision is set
rg 'gather_task_state' .agents/scripts/supervisor/ai-lifecycle.sh -A 100 | grep -A 100 'pr_review_decision'Repository: marcusquinn/aidevops
Length of output: 5496
🏁 Script executed:
# Check the complete state output formatting
rg 'PR_REVIEW:' .agents/scripts/supervisor/ai-lifecycle.sh -B 5 -A 2Repository: marcusquinn/aidevops
Length of output: 317
🏁 Script executed:
# Let's verify the shell syntax for the guard by examining similar conditional patterns
sed -n '820,835p' .agents/scripts/supervisor/ai-lifecycle.shRepository: marcusquinn/aidevops
Length of output: 765
Fast-path merge must check review decisions before merging.
The CLEAN and UNSTABLE merge paths bypass PR_REVIEW checks entirely. Since mergeStateStatus reflects only CI and conflict state (not reviews), a PR can be CLEAN while having CHANGES_REQUESTED or REVIEW_REQUIRED status. This allows automated merges to override explicit human review feedback.
Extract pr_review from the task state snapshot and guard both merge paths:
Add review guard before fast-path merge
- local db_status pr_state pr_merge_state pr_ci pr_url
+ local db_status pr_state pr_merge_state pr_ci pr_url pr_review
db_status=$(printf '%s' "$task_state" | grep '^DB_STATUS:' | cut -d' ' -f2)
pr_state=$(printf '%s' "$task_state" | grep '^PR_STATE:' | cut -d' ' -f2)
pr_merge_state=$(printf '%s' "$task_state" | grep '^PR_MERGE_STATE:' | cut -d' ' -f2)
pr_ci=$(printf '%s' "$task_state" | grep '^PR_CI:' | cut -d' ' -f2-)
pr_url=$(printf '%s' "$task_state" | grep '^PR_URL:' | cut -d' ' -f2)
+ pr_review=$(printf '%s' "$task_state" | grep '^PR_REVIEW:' | cut -d' ' -f2)
# PR is CLEAN (all checks passed, no conflicts) → merge
if [[ "$pr_merge_state" == "CLEAN" ]]; then
+ if [[ "$pr_review" == "CHANGES_REQUESTED" || "$pr_review" == "REVIEW_REQUIRED" ]]; then
+ return 1
+ fi
echo '{"action":"merge_pr","reason":"CI passed, PR clean","status_tag":"merging"}'
return 0
fi
# PR is UNSTABLE (non-required checks failed) → merge (safe)
if [[ "$pr_merge_state" == "UNSTABLE" ]]; then
+ if [[ "$pr_review" == "CHANGES_REQUESTED" || "$pr_review" == "REVIEW_REQUIRED" ]]; then
+ return 1
+ fi
echo '{"action":"merge_pr","reason":"Required checks passed (non-required failed)","status_tag":"merging"}'
return 0
fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/supervisor/ai-lifecycle.sh around lines 805 - 835, Extract
pr_review from the task state (like the other fields: pr_review=$(printf '%s'
"$task_state" | grep '^PR_REVIEW:' | cut -d' ' -f2)) and add a guard to the
CLEAN and UNSTABLE fast-paths so they only emit merge actions when the review
decision indicates approval (e.g. require pr_review == "APPROVED"); if pr_review
is "CHANGES_REQUESTED", "REVIEW_REQUIRED" or any non-approved value, do not
auto-merge and let the normal review/hold logic handle the PR.



Summary
.agents/scripts/supervisor/ai-lifecycle.sh— AI-driven lifecycle engine with fast-path deterministic decisions (CLEAN→merge, BEHIND→update_branch, MERGED→deploy) and sonnet AI fallback for ambiguous casespulse.shPhase 3 now callsprocess_ai_lifecycle()by default; legacy path viaSUPERVISOR_AI_LIFECYCLE=falsesupervisor-helper.shsources the new moduleplanning-detail.mdadds status tag vocabulary referenceProblem
6 awards app PRs stuck in blocked state because the supervisor's
rebase_sibling_pr()(local git rebase) fails on BEHIND branches that have NO actual conflicts. This blocks 8+ downstream tasks from dispatching.Solution
Replace hardcoded bash case statements with an AI-decides/bash-executes architecture:
gh pr update-branch(GitHub API) instead of local git rebase for BEHIND branchesFeature Flag
SUPERVISOR_AI_LIFECYCLE=true(default). Setfalseto instantly revert to legacy behavior.Testing
Summary by CodeRabbit
Release Notes
New Features
Documentation