Skip to content

Comments

t1009: Supervisor auto-updates GitHub issue status labels on every state transition#1299

Merged
marcusquinn merged 3 commits intomainfrom
feature/t1009
Feb 12, 2026
Merged

t1009: Supervisor auto-updates GitHub issue status labels on every state transition#1299
marcusquinn merged 3 commits intomainfrom
feature/t1009

Conversation

@marcusquinn
Copy link
Owner

@marcusquinn marcusquinn commented Feb 12, 2026

Summary

Implements automatic GitHub issue status label synchronization on every supervisor state transition (t1009).

State-to-label mapping:

  • queuedstatus:queued
  • dispatched/running/evaluating/retryingstatus:claimed
  • complete/pr_review/review_triage/merging/merged/deployingstatus:in-review
  • blockedstatus:blocked
  • verify_failedstatus:verify-failed
  • verified/deployed → close issue + status:done
  • cancelled → close as not-planned
  • failed → close issue

On 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., failedqueued).

Changes:

supervisor-helper.sh

  1. ensure_status_labels() — expanded with 3 new labels: status:queued, status:blocked, status:verify-failed
  2. state_to_status_label() — NEW: maps supervisor state to the correct status:* label
  3. sync_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-entry
  4. cmd_transition() — wired to call sync_issue_status_label() after every state change
  5. cmd_add() — sets status:queued label when a task is first added to the supervisor
  6. sync_claim_to_github() — updated to remove all status labels (not just available/claimed) when claiming/unclaiming
  7. Phase 8b in pulse — NEW reconciliation sweep that checks all active tasks and fixes drifted labels (runs on same ~50min interval as Phase 8)

issue-sync-helper.sh

  1. cmd_close() — updated to remove new labels (status:queued, status:blocked, status:verify-failed) when closing issues
  2. _close_single_task() — same label cleanup as cmd_close()

supervisor/issue-sync.sh

  1. Added stub documentation for state_to_status_label() and sync_issue_status_label()

Quality:

  • Both files pass bash -n syntax check
  • New functions pass ShellCheck (-x -S warning)
  • Follows existing patterns: local var="$1", explicit returns, best-effort with || true

Ref #1298

Summary by CodeRabbit

  • Improvements
    • Enhanced GitHub issue synchronization with task lifecycle states. Issue status labels are now automatically maintained and updated to reflect current task status (queued, claimed, in-review, done, etc.).
    • Issues are automatically closed when tasks reach terminal states.
    • Added periodic reconciliation to correct label drift and ensure consistency between task states and issue labels.

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).
@gemini-code-assist
Copy link

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 12, 2026

Walkthrough

The 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

Cohort / File(s) Summary
Label Management & Issue Closure
.agents/scripts/issue-sync-helper.sh
Expanded status label removal on issue close from {available, claimed, in-review} to {available, queued, claimed, in-review, blocked, verify-failed} with automatic addition of status:done label.
GitHub Status-Label Sync Infrastructure
.agents/scripts/supervisor-helper.sh
Added state-to-label mapping (state_to_status_label), issue-sync function (sync_issue_status_label), task-to-issue resolution (find_task_issue_number), label enumeration (ALL_STATUS_LABELS), integration into task lifecycle (cmd_add, cmd_transition, cmd_claim/unclaim), and phase 8b reconciliation sweep for label-drift correction.
Issue-Sync Wrapper Functions
.agents/scripts/supervisor/issue-sync.sh
Added delegating wrapper functions (state_to_status_label, sync_issue_status_label) that invoke supervisor-helper implementations; placeholder pattern for modular architecture.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

Possibly related PRs

Poem

🏷️ A task floats through the states it flows,
From queued to claimed, as wisdom knows,
Each transition whispers to the label queue,
GitHub issues dance in sync anew.
Labels drift? Phase eight sweeps clean—
Harmony between code and machine. 🌀

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Merge Conflict Detection ⚠️ Warning ⚠️ Unable to check for merge conflicts: Failed to fetch base branch: From https://github.com/marcusquinn/aidevops
! [rejected] main -> main (non-fast-forward)
+ 890d8a0...2c6a0a0 main -> origin/main (forced update)
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: automatic synchronization of GitHub issue status labels triggered by supervisor state transitions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/t1009
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch feature/t1009
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 0 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Thu Feb 12 21:35:15 UTC 2026: Code review monitoring started
Thu Feb 12 21:35:15 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 0

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 0
  • VULNERABILITIES: 0

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
@marcusquinn marcusquinn marked this pull request as ready for review February 12, 2026 21:42
@gemini-code-assist
Copy link

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@github-actions
Copy link

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 0 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Thu Feb 12 21:42:51 UTC 2026: Code review monitoring started
Thu Feb 12 21:42:51 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 0

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 0
  • VULNERABILITIES: 0

Generated on: Thu Feb 12 21:42:54 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@sonarqubecloud
Copy link

@marcusquinn marcusquinn merged commit de0a78a into main Feb 12, 2026
10 of 11 checks passed
@marcusquinn marcusquinn deleted the feature/t1009 branch February 12, 2026 21:49
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 explicit return 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 explicit return 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-label flags is repeated in _close_single_task (lines 1625–1629). If a new status:* label is introduced (and this PR's own objectives hint at future reconciliation sweeps), both call sites must be updated in lockstep. The ALL_STATUS_LABELS array already exists in supervisor-helper.sh per 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

Comment on lines +3055 to +3080
# 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
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file structure and locate the relevant functions
wc -l .agents/scripts/supervisor-helper.sh

Repository: 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 10

Repository: 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 2

Repository: 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.sh

Repository: 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.

Suggested change
# 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.

Comment on lines +3854 to +3869
# 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
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant