t1009: Supervisor auto-updates GitHub issue status labels on every state transition#1299
t1009: Supervisor auto-updates GitHub issue status labels on every state transition#1299marcusquinn merged 3 commits intomainfrom
Conversation
State-to-label mapping: queued->status:queued, dispatched/running->status:claimed, pr_review/merging->status:in-review, blocked->status:blocked, verify_failed->status:verify-failed, verified/deployed->close+status:done, cancelled->close as not-planned. Removes old status label before adding new one. Expanded ensure_status_labels() with new labels (queued, blocked, verify-failed).
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
WalkthroughThe changes implement GitHub issue status-label synchronization infrastructure that maps supervisor task states to GitHub status labels, integrates label updates across the task lifecycle (add, transition, claim/unclaim), expands label cleanup on issue closure, and adds periodic reconciliation to correct label drift. Changes
Sequence DiagramsequenceDiagram
participant Supervisor as Supervisor<br/>(Task State)
participant Helper as supervisor-<br/>helper.sh
participant IssueSync as GitHub<br/>Issue Sync
participant GitHub as GitHub API<br/>(gh CLI)
rect rgba(100, 150, 255, 0.5)
note over Supervisor,GitHub: Task State Transition Flow
Supervisor->>Helper: cmd_transition(task_id, new_state)
Helper->>Helper: Perform state transition<br/>& proof-logging
Helper->>IssueSync: sync_issue_status_label<br/>(task_id, new_state)
IssueSync->>Helper: state_to_status_label(new_state)
Helper-->>IssueSync: Map state → label
IssueSync->>IssueSync: find_task_issue_number<br/>(task_id)
IssueSync->>GitHub: gh label create status:*
GitHub-->>IssueSync: Labels ensured
IssueSync->>GitHub: gh issue edit --remove-label<br/>OLD_STATUS_LABELS
GitHub-->>IssueSync: Stale labels removed
IssueSync->>GitHub: gh issue edit --add-label<br/>NEW_STATUS_LABEL
GitHub-->>IssueSync: New label applied
end
rect rgba(150, 200, 100, 0.5)
note over Supervisor,GitHub: Phase 8b Reconciliation Sweep
Supervisor->>Helper: phase_8b_pulse()
Helper->>Helper: For each task:<br/>get current state
Helper->>IssueSync: sync_issue_status_label<br/>(task_id, state)
IssueSync->>GitHub: Reconcile labels<br/>to match state
GitHub-->>IssueSync: Drift corrected
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (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: Thu Feb 12 21:35:18 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
…onciliation, and cmd_add (t1009) - sync_claim_to_github: removes all status labels before adding claimed/available - issue-sync-helper.sh cmd_close/_close_single_task: removes new labels (queued, blocked, verify-failed) - Phase 8b in pulse: reconciliation sweep checks all active tasks and fixes drifted labels - cmd_add: sets status:queued on initial task addition
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
🔍 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: Thu Feb 12 21:42:54 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @.agents/scripts/supervisor-helper.sh:
- Around line 3055-3080: The removal list logic uses new_label from
state_to_status_label("$new_state"), but that returns an empty string for
terminal states (verified/deployed) so the loop ends up scheduling removal of
"status:done" and then trying to add it in gh issue edit; update the loop that
builds remove_args to skip empty labels and explicitly skip "status:done" when
new_state is a terminal state (verified|deployed) so you never add
"--remove-label status:done" — reference the variables and functions new_label,
state_to_status_label, ALL_STATUS_LABELS, remove_args and the gh issue edit call
to find where to change the condition.
- Around line 3854-3869: The claim/unclaim branches currently remove a hardcoded
subset of status labels which can leave other status:* labels (e.g.,
status:in-review) behind; update both the "claim" and "unclaim" paths to remove
every label listed in ALL_STATUS_LABELS except the one you're adding: iterate
ALL_STATUS_LABELS (or build a filtered list) to pass --remove-label for each
label not equal to the target ("status:claimed" for claim, "status:available"
for unclaim) while preserving the existing --add-label and
--add-assignee/--remove-assignee behavior; reference the variables/action names
in the snippet (action, issue_number, repo_slug, my_login, ALL_STATUS_LABELS) so
the change replaces the explicit --remove-label ... sequence with the filtered
removal logic.
🧹 Nitpick comments (4)
.agents/scripts/supervisor/issue-sync.sh (2)
93-95: Stub missing explicitreturn 0.The coding guidelines require explicit returns in shell scripts. The existing stubs in this file also omit them, but since these are new additions, let's start the pattern right.
Proposed fix
state_to_status_label() { : + return 0 }As per coding guidelines, "Include explicit returns in shell scripts".
108-110: Stub missing explicitreturn 0.Same as above — add an explicit return for consistency with the guidelines.
Proposed fix
sync_issue_status_label() { : + return 0 }As per coding guidelines, "Include explicit returns in shell scripts".
.agents/scripts/issue-sync-helper.sh (1)
1527-1533: Duplicated label-removal list — extract to a shared variable or helper.The exact same set of
--remove-labelflags is repeated in_close_single_task(lines 1625–1629). If a newstatus:*label is introduced (and this PR's own objectives hint at future reconciliation sweeps), both call sites must be updated in lockstep. TheALL_STATUS_LABELSarray already exists insupervisor-helper.shper the PR summary — consider sourcing it or extracting a small_remove_all_status_labels()helper in this file to keep a single source of truth..agents/scripts/supervisor-helper.sh (1)
13409-13427: Consider avoiding GH_TOKEN in crontab for secret hygiene.
The script already resolves/caches GH_TOKEN at runtime, so embedding it in the cron line increases exposure without clear benefit.🔒 Proposed refactor (rely on runtime token resolution)
- # Detect GH_TOKEN from gh CLI if available (t1006) - local gh_token="" - if command -v gh &>/dev/null; then - gh_token=$(gh auth token 2>/dev/null || true) - fi - # Build cron command with environment variables local env_vars="" if [[ -n "$user_path" ]]; then env_vars="PATH=${user_path}" fi - if [[ -n "$gh_token" ]]; then - env_vars="${env_vars:+${env_vars} }GH_TOKEN=${gh_token}" - fi
| # Determine the new label | ||
| local new_label | ||
| new_label=$(state_to_status_label "$new_state") | ||
|
|
||
| # Build remove args for all status labels except the new one | ||
| local -a remove_args=() | ||
| local label | ||
| while IFS=',' read -ra labels; do | ||
| for label in "${labels[@]}"; do | ||
| if [[ "$label" != "$new_label" ]]; then | ||
| remove_args+=("--remove-label" "$label") | ||
| fi | ||
| done | ||
| done <<<"$ALL_STATUS_LABELS" | ||
|
|
||
| # Handle terminal states that close the issue | ||
| case "$new_state" in | ||
| verified | deployed) | ||
| # Close the issue with a completion comment | ||
| gh issue close "$issue_number" --repo "$repo_slug" \ | ||
| --comment "Task $task_id reached state: $new_state (from $old_state)" 2>/dev/null || true | ||
| # Add status:done and remove all other status labels | ||
| gh issue edit "$issue_number" --repo "$repo_slug" \ | ||
| --add-label "status:done" "${remove_args[@]}" 2>/dev/null || true | ||
| log_verbose "sync_issue_status_label: closed #$issue_number ($task_id -> $new_state)" | ||
| return 0 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the file structure and locate the relevant functions
wc -l .agents/scripts/supervisor-helper.shRepository: marcusquinn/aidevops
Length of output: 106
🏁 Script executed:
# Find the state_to_status_label function definition
rg -n "state_to_status_label" .agents/scripts/supervisor-helper.sh -A 10Repository: marcusquinn/aidevops
Length of output: 1657
🏁 Script executed:
# Find ALL_STATUS_LABELS definition
rg -n "ALL_STATUS_LABELS" .agents/scripts/supervisor-helper.sh -B 2 -A 2Repository: marcusquinn/aidevops
Length of output: 709
🏁 Script executed:
# Get the exact lines in question for full context
sed -n '3050,3090p' .agents/scripts/supervisor-helper.shRepository: marcusquinn/aidevops
Length of output: 1530
Status label logic conflict in terminal state transitions removes status:done.
When new_state is verified or deployed, state_to_status_label() returns an empty string (line 2997-2998). This causes the removal filter condition [[ "$label" != "$new_label" ]] to be true for all labels in ALL_STATUS_LABELS, including status:done. The subsequent gh issue edit command then attempts to both add and remove status:done in the same operation.
Exclude the terminal target label from the removal list:
Fix: Exclude terminal state target label from removal
# Determine the new label
local new_label
new_label=$(state_to_status_label "$new_state")
+ local target_label="$new_label"
+ case "$new_state" in
+ verified | deployed) target_label="status:done" ;;
+ esac
# Build remove args for all status labels except the new one
local -a remove_args=()
local label
while IFS=',' read -ra labels; do
for label in "${labels[@]}"; do
- if [[ "$label" != "$new_label" ]]; then
+ if [[ "$label" != "$target_label" ]]; then
remove_args+=("--remove-label" "$label")
fi
done
done <<<"$ALL_STATUS_LABELS"
verified | deployed)
# Close the issue with a completion comment
gh issue close "$issue_number" --repo "$repo_slug" \
--comment "Task $task_id reached state: $new_state (from $old_state)" 2>/dev/null || true
# Add status:done and remove all other status labels
gh issue edit "$issue_number" --repo "$repo_slug" \
- --add-label "status:done" "${remove_args[@]}" 2>/dev/null || true
+ --add-label "$target_label" "${remove_args[@]}" 2>/dev/null || true📝 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.
| # Determine the new label | |
| local new_label | |
| new_label=$(state_to_status_label "$new_state") | |
| # Build remove args for all status labels except the new one | |
| local -a remove_args=() | |
| local label | |
| while IFS=',' read -ra labels; do | |
| for label in "${labels[@]}"; do | |
| if [[ "$label" != "$new_label" ]]; then | |
| remove_args+=("--remove-label" "$label") | |
| fi | |
| done | |
| done <<<"$ALL_STATUS_LABELS" | |
| # Handle terminal states that close the issue | |
| case "$new_state" in | |
| verified | deployed) | |
| # Close the issue with a completion comment | |
| gh issue close "$issue_number" --repo "$repo_slug" \ | |
| --comment "Task $task_id reached state: $new_state (from $old_state)" 2>/dev/null || true | |
| # Add status:done and remove all other status labels | |
| gh issue edit "$issue_number" --repo "$repo_slug" \ | |
| --add-label "status:done" "${remove_args[@]}" 2>/dev/null || true | |
| log_verbose "sync_issue_status_label: closed #$issue_number ($task_id -> $new_state)" | |
| return 0 | |
| # Determine the new label | |
| local new_label | |
| new_label=$(state_to_status_label "$new_state") | |
| local target_label="$new_label" | |
| case "$new_state" in | |
| verified | deployed) target_label="status:done" ;; | |
| esac | |
| # Build remove args for all status labels except the new one | |
| local -a remove_args=() | |
| local label | |
| while IFS=',' read -ra labels; do | |
| for label in "${labels[@]}"; do | |
| if [[ "$label" != "$target_label" ]]; then | |
| remove_args+=("--remove-label" "$label") | |
| fi | |
| done | |
| done <<<"$ALL_STATUS_LABELS" | |
| # Handle terminal states that close the issue | |
| case "$new_state" in | |
| verified | deployed) | |
| # Close the issue with a completion comment | |
| gh issue close "$issue_number" --repo "$repo_slug" \ | |
| --comment "Task $task_id reached state: $new_state (from $old_state)" 2>/dev/null || true | |
| # Add status:done and remove all other status labels | |
| gh issue edit "$issue_number" --repo "$repo_slug" \ | |
| --add-label "$target_label" "${remove_args[@]}" 2>/dev/null || true | |
| log_verbose "sync_issue_status_label: closed #$issue_number ($task_id -> $new_state)" | |
| return 0 |
🤖 Prompt for AI Agents
In @.agents/scripts/supervisor-helper.sh around lines 3055 - 3080, The removal
list logic uses new_label from state_to_status_label("$new_state"), but that
returns an empty string for terminal states (verified/deployed) so the loop ends
up scheduling removal of "status:done" and then trying to add it in gh issue
edit; update the loop that builds remove_args to skip empty labels and
explicitly skip "status:done" when new_state is a terminal state
(verified|deployed) so you never add "--remove-label status:done" — reference
the variables and functions new_label, state_to_status_label, ALL_STATUS_LABELS,
remove_args and the gh issue edit call to find where to change the condition.
| # t1009: Remove all status labels, add status:claimed | ||
| gh issue edit "$issue_number" --repo "$repo_slug" \ | ||
| --add-assignee "@me" \ | ||
| --add-label "status:claimed" --remove-label "status:available" 2>/dev/null || true | ||
| --add-label "status:claimed" \ | ||
| --remove-label "status:available" --remove-label "status:queued" \ | ||
| --remove-label "status:blocked" --remove-label "status:verify-failed" 2>/dev/null || true | ||
| elif [[ "$action" == "unclaim" ]]; then | ||
| local my_login | ||
| my_login=$(gh api user --jq '.login' 2>/dev/null || echo "") | ||
| if [[ -n "$my_login" ]]; then | ||
| # t1009: Remove all status labels, add status:available | ||
| gh issue edit "$issue_number" --repo "$repo_slug" \ | ||
| --remove-assignee "$my_login" \ | ||
| --add-label "status:available" --remove-label "status:claimed" 2>/dev/null || true | ||
| --add-label "status:available" \ | ||
| --remove-label "status:claimed" --remove-label "status:queued" \ | ||
| --remove-label "status:blocked" --remove-label "status:verify-failed" 2>/dev/null || true |
There was a problem hiding this comment.
Claim/unclaim paths can leave stale status: labels.*
The removal list is partial, so issues can accumulate multiple status labels (e.g., status:in-review or status:done) and violate the single‑status invariant. Prefer removing all labels from ALL_STATUS_LABELS except the target.
🛠️ Proposed fix (remove all status labels except the target)
if [[ "$action" == "claim" ]]; then
+ local -a remove_args=()
+ local label
+ while IFS=',' read -ra labels; do
+ for label in "${labels[@]}"; do
+ [[ "$label" != "status:claimed" ]] && remove_args+=("--remove-label" "$label")
+ done
+ done <<<"$ALL_STATUS_LABELS"
# t1009: Remove all status labels, add status:claimed
gh issue edit "$issue_number" --repo "$repo_slug" \
--add-assignee "@me" \
--add-label "status:claimed" \
- --remove-label "status:available" --remove-label "status:queued" \
- --remove-label "status:blocked" --remove-label "status:verify-failed" 2>/dev/null || true
+ "${remove_args[@]}" 2>/dev/null || true
elif [[ "$action" == "unclaim" ]]; then
local my_login
my_login=$(gh api user --jq '.login' 2>/dev/null || echo "")
if [[ -n "$my_login" ]]; then
+ local -a remove_args=()
+ local label
+ while IFS=',' read -ra labels; do
+ for label in "${labels[@]}"; do
+ [[ "$label" != "status:available" ]] && remove_args+=("--remove-label" "$label")
+ done
+ done <<<"$ALL_STATUS_LABELS"
# t1009: Remove all status labels, add status:available
gh issue edit "$issue_number" --repo "$repo_slug" \
--remove-assignee "$my_login" \
--add-label "status:available" \
- --remove-label "status:claimed" --remove-label "status:queued" \
- --remove-label "status:blocked" --remove-label "status:verify-failed" 2>/dev/null || true
+ "${remove_args[@]}" 2>/dev/null || true
fi
fi🤖 Prompt for AI Agents
In @.agents/scripts/supervisor-helper.sh around lines 3854 - 3869, The
claim/unclaim branches currently remove a hardcoded subset of status labels
which can leave other status:* labels (e.g., status:in-review) behind; update
both the "claim" and "unclaim" paths to remove every label listed in
ALL_STATUS_LABELS except the one you're adding: iterate ALL_STATUS_LABELS (or
build a filtered list) to pass --remove-label for each label not equal to the
target ("status:claimed" for claim, "status:available" for unclaim) while
preserving the existing --add-label and --add-assignee/--remove-assignee
behavior; reference the variables/action names in the snippet (action,
issue_number, repo_slug, my_login, ALL_STATUS_LABELS) so the change replaces the
explicit --remove-label ... sequence with the filtered removal logic.



Summary
Implements automatic GitHub issue status label synchronization on every supervisor state transition (t1009).
State-to-label mapping:
queued→status:queueddispatched/running/evaluating/retrying→status:claimedcomplete/pr_review/review_triage/merging/merged/deploying→status:in-reviewblocked→status:blockedverify_failed→status:verify-failedverified/deployed→ close issue +status:donecancelled→ close as not-plannedfailed→ close issueOn every transition, removes all old
status:*labels and adds the new one (only one at a time). Reopens closed issues when tasks re-enter the pipeline (e.g.,failed→queued).Changes:
supervisor-helper.shensure_status_labels()— expanded with 3 new labels:status:queued,status:blocked,status:verify-failedstate_to_status_label()— NEW: maps supervisor state to the correctstatus:*labelsync_issue_status_label()— NEW: core function that updates GitHub issue labels on state transitions. Handles terminal states (close issue), non-terminal states (swap labels), and reopening closed issues on re-entrycmd_transition()— wired to callsync_issue_status_label()after every state changecmd_add()— setsstatus:queuedlabel when a task is first added to the supervisorsync_claim_to_github()— updated to remove all status labels (not just available/claimed) when claiming/unclaimingissue-sync-helper.shcmd_close()— updated to remove new labels (status:queued,status:blocked,status:verify-failed) when closing issues_close_single_task()— same label cleanup ascmd_close()supervisor/issue-sync.shstate_to_status_label()andsync_issue_status_label()Quality:
bash -nsyntax check-x -S warning)local var="$1", explicit returns, best-effort with|| trueRef #1298
Summary by CodeRabbit