t1324: AI-based issue duplicate detection and auto-dispatch assessment#2241
t1324: AI-based issue duplicate detection and auto-dispatch assessment#2241marcusquinn merged 1 commit intomainfrom
Conversation
… (t1324)
Replace fragile deterministic checks with AI intelligence for two
issue management capabilities:
1. Semantic duplicate detection (ai_detect_duplicate_issue):
- Before creating a GitHub issue, AI compares the new title against
all open issues to find semantic duplicates
- Catches cases deterministic prefix matching misses: different task
IDs for the same work (e.g., t10 vs t023), rephrased descriptions
- Integrated into: issue-sync.sh create_github_issue(),
issue-sync-helper.sh cmd_push(), claim-task-id.sh
- When duplicate found: links to existing issue instead of creating
2. Auto-dispatch eligibility assessment (ai_assess_auto_dispatch):
- AI evaluates task description, brief, acceptance criteria, and scope
to determine if a task is ready for autonomous dispatch
- Assesses: clear deliverable, testable criteria, bounded scope,
actionability, dependency status, task type
- Returns recommended labels including auto-dispatch when appropriate
3. claim-task-id.sh delegation to issue-sync-helper.sh:
- Previously created bare issues (no labels, minimal body)
- Now delegates to issue-sync-helper.sh push for rich bodies and
proper labels, falling back to bare creation if helper unavailable
All AI calls use sonnet tier with 15s timeout, audit-logged to
AI_LIFECYCLE_LOG_DIR. Graceful degradation: if AI unavailable, falls
back to existing deterministic behavior.
WalkthroughThese changes introduce AI-driven semantic duplicate detection across the GitHub issue creation pipeline. The claim-task-id.sh script delegates enriched issue creation to issue-sync-helper.sh when possible, while new AI functions detect semantic duplicates and assess auto-dispatch eligibility. Fallback mechanisms preserve backward compatibility. Changes
Sequence Diagram(s)sequenceDiagram
participant Task as Task Submission
participant Helper as issue-sync-helper
participant AI as AI Model
participant GitHub as GitHub API
participant Supervisor as supervisor/issue-sync
Task->>Supervisor: Submit new issue
Supervisor->>Supervisor: Fast deterministic check
alt Deterministic match found
Supervisor->>GitHub: Link to existing issue
else No deterministic match
Supervisor->>Supervisor: ai_detect_duplicate_issue()
Supervisor->>AI: Compare title vs open issues
AI-->>Supervisor: Semantic duplicate verdict (JSON)
alt Semantic duplicate detected
Supervisor->>GitHub: Link to existing issue
else No duplicate
Supervisor->>Helper: Delegate rich creation
Helper->>Helper: _ai_check_duplicate()
Helper->>AI: Verify no duplicate
AI-->>Helper: Confirmation
Helper->>GitHub: Create issue
Helper-->>Supervisor: Issue number
end
end
Supervisor->>Supervisor: ai_assess_auto_dispatch()
Supervisor->>AI: Evaluate dispatch eligibility
AI-->>Supervisor: Dispatch decision (JSON)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 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 significantly enhances the issue management workflow by integrating AI capabilities for smarter issue creation and task dispatch. It introduces semantic duplicate detection to prevent redundant issues and an AI-driven assessment for determining if a task is suitable for autonomous processing. These changes aim to improve the efficiency and accuracy of issue tracking and automation, ensuring that new tasks are unique and properly prepared for further action. 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
|
🔍 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: Tue Feb 24 23:50:42 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
|
There was a problem hiding this comment.
Code Review
This pull request introduces valuable AI-driven features for duplicate issue detection and auto-dispatch assessment. The implementation is well-structured, delegating to helper scripts and providing fallbacks. However, there's a recurring issue of suppressing stderr with 2>/dev/null across all new shell scripts, which violates repository conventions and can hide critical errors. Additionally, there's a bug in how JSON is extracted from AI responses, which will fail for complex objects. Addressing these will improve the robustness and debuggability of the new features.
| local issue_sync_helper="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}/issue-sync-helper.sh" | ||
| if [[ -n "$task_id" && -x "$issue_sync_helper" && -f "$repo_path/TODO.md" ]]; then | ||
| local push_output | ||
| push_output=$("$issue_sync_helper" push "$task_id" 2>/dev/null || echo "") |
There was a problem hiding this comment.
Suppressing stderr with 2>/dev/null can hide important errors from the issue-sync-helper.sh script, such as authentication failures, syntax errors, or missing dependencies. This makes debugging difficult. The || echo "" already prevents the script from exiting on failure. Please remove 2>/dev/null to allow errors to be logged.
| push_output=$("$issue_sync_helper" push "$task_id" 2>/dev/null || echo "") | |
| push_output=$("$issue_sync_helper" push "$task_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.
| _ai_check_duplicate() { | ||
| local new_title="$1" | ||
| local repo_slug="$2" | ||
|
|
||
| # Resolve AI CLI (claude preferred, opencode fallback) | ||
| local ai_cli="" | ||
| if command -v claude &>/dev/null; then | ||
| ai_cli="claude" | ||
| elif command -v opencode &>/dev/null; then | ||
| ai_cli="opencode" | ||
| else | ||
| return 1 # No AI available | ||
| fi | ||
|
|
||
| # Fetch recent open issues | ||
| local existing_issues | ||
| existing_issues=$(gh issue list --repo "$repo_slug" --state open --limit 50 \ | ||
| --json number,title,labels \ | ||
| --jq '.[] | "#\(.number): \(.title) [\(.labels | map(.name) | join(","))]"' \ | ||
| 2>/dev/null || echo "") | ||
|
|
||
| if [[ -z "$existing_issues" ]]; then | ||
| return 1 | ||
| fi | ||
|
|
||
| local prompt | ||
| prompt="You are a duplicate issue detector. Determine if a new issue is a semantic duplicate of any existing open issue. | ||
|
|
||
| NEW ISSUE TITLE: ${new_title} | ||
|
|
||
| EXISTING OPEN ISSUES: | ||
| ${existing_issues} | ||
|
|
||
| RULES: | ||
| - A duplicate means the same work described differently (different task IDs, rephrased titles). | ||
| - Different task IDs (e.g., t10 vs t023) for the same work ARE duplicates. | ||
| - Related but different-scope issues are NOT duplicates. | ||
| - Auto-generated dashboard/status issues are never duplicates of task issues. | ||
| - When uncertain, prefer false (not duplicate). | ||
|
|
||
| Respond with ONLY a JSON object: | ||
| {\"duplicate\": true|false, \"duplicate_of\": \"#NNN or empty\", \"reason\": \"one sentence\"}" | ||
|
|
||
| local ai_result="" | ||
| if [[ "$ai_cli" == "opencode" ]]; then | ||
| ai_result=$(timeout 15 opencode run \ | ||
| -m "anthropic/claude-sonnet-4-20250514" \ | ||
| --format default \ | ||
| --title "dedup-$$" \ | ||
| "$prompt" 2>/dev/null || echo "") | ||
| ai_result=$(printf '%s' "$ai_result" | sed 's/\x1b\[[0-9;]*[mGKHF]//g; s/\x1b\[[0-9;]*[A-Za-z]//g; s/\x1b\]//g; s/\x07//g') | ||
| else | ||
| ai_result=$(timeout 15 claude \ | ||
| -p "$prompt" \ | ||
| --model "claude-sonnet-4-20250514" \ | ||
| --output-format text 2>/dev/null || echo "") | ||
| fi | ||
|
|
||
| if [[ -z "$ai_result" ]]; then | ||
| return 1 | ||
| fi | ||
|
|
||
| local json_block | ||
| json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1) | ||
| if [[ -z "$json_block" ]]; then | ||
| return 1 | ||
| fi | ||
|
|
||
| local is_duplicate | ||
| is_duplicate=$(printf '%s' "$json_block" | jq -r '.duplicate // false' 2>/dev/null || echo "false") | ||
| local duplicate_of | ||
| duplicate_of=$(printf '%s' "$json_block" | jq -r '.duplicate_of // ""' 2>/dev/null | tr -d '#') | ||
|
|
||
| if [[ "$is_duplicate" == "true" && -n "$duplicate_of" && "$duplicate_of" =~ ^[0-9]+$ ]]; then | ||
| local reason | ||
| reason=$(printf '%s' "$json_block" | jq -r '.reason // ""' 2>/dev/null || echo "") | ||
| print_warning "Semantic duplicate: $new_title duplicates #${duplicate_of} — $reason" | ||
| echo "$duplicate_of" | ||
| return 0 | ||
| fi | ||
|
|
||
| return 1 | ||
| } |
There was a problem hiding this comment.
The _ai_check_duplicate function consistently suppresses stderr using 2>/dev/null for gh, opencode, claude, and jq commands. This is against repository guidelines and makes debugging extremely difficult by hiding critical errors like authentication failures, API issues, or malformed JSON responses. Please remove all instances of 2>/dev/null in this function. The existing || echo ... fallbacks are sufficient to prevent script termination.
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.
| # Only runs on GitHub (gh CLI required for issue listing) | ||
| if [[ "$_DETECTED_PLATFORM" == "github" ]]; then | ||
| local dup_issue_num | ||
| if dup_issue_num=$(_ai_check_duplicate "$title" "$repo_slug" 2>/dev/null); then |
There was a problem hiding this comment.
Suppressing stderr here prevents the print_warning message from _ai_check_duplicate (line 152) from being displayed, which contains useful context about why a duplicate was detected. It also hides any other potential errors from that function. Please remove 2>/dev/null.
| if dup_issue_num=$(_ai_check_duplicate "$title" "$repo_slug" 2>/dev/null); then | |
| if dup_issue_num=$(_ai_check_duplicate "$title" "$repo_slug"); then |
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.
| if dedup_result=$(ai_detect_duplicate_issue "$new_title" "" "$repo_slug" 2>/dev/null); then | ||
| local dup_number | ||
| dup_number=$(printf '%s' "$dedup_result" | jq -r '.duplicate_of // ""' 2>/dev/null | tr -d '#') | ||
| if [[ -n "$dup_number" && "$dup_number" =~ ^[0-9]+$ ]]; then | ||
| local dup_reason | ||
| dup_reason=$(printf '%s' "$dedup_result" | jq -r '.reason // ""' 2>/dev/null) |
There was a problem hiding this comment.
Suppressing stderr for ai_detect_duplicate_issue and subsequent jq calls hides potential errors from the AI call and JSON parsing. This makes debugging failures in the duplicate detection logic very difficult. Please remove 2>/dev/null from these lines.
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.
| ai_detect_duplicate_issue() { | ||
| local new_title="$1" | ||
| local new_body="${2:-}" | ||
| local repo_slug="$3" | ||
|
|
||
| # Fetch recent open issues for comparison | ||
| local existing_issues | ||
| existing_issues=$(gh issue list --repo "$repo_slug" --state open --limit 50 \ | ||
| --json number,title,labels \ | ||
| --jq '.[] | "#\(.number): \(.title) [\(.labels | map(.name) | join(","))]"' \ | ||
| 2>/dev/null || echo "") | ||
|
|
||
| if [[ -z "$existing_issues" ]]; then | ||
| log_verbose "ai_detect_duplicate_issue: no open issues to compare against" | ||
| return 1 | ||
| fi | ||
|
|
||
| local ai_cli | ||
| ai_cli=$(resolve_ai_cli 2>/dev/null) || { | ||
| log_verbose "ai_detect_duplicate_issue: AI unavailable, skipping duplicate check" | ||
| return 1 | ||
| } | ||
|
|
||
| local ai_model | ||
| ai_model=$(resolve_model "sonnet" "$ai_cli" 2>/dev/null) || { | ||
| log_verbose "ai_detect_duplicate_issue: model resolution failed, skipping" | ||
| return 1 | ||
| } | ||
|
|
||
| local body_context="" | ||
| if [[ -n "$new_body" ]]; then | ||
| # Truncate body to avoid token overflow | ||
| body_context=" | ||
|
|
||
| BODY (first 500 chars): | ||
| $(printf '%s' "$new_body" | head -c 500)" | ||
| fi | ||
|
|
||
| local prompt | ||
| prompt="You are a duplicate issue detector for a DevOps task management system. Determine if a new issue is a semantic duplicate of any existing open issue. | ||
|
|
||
| NEW ISSUE: | ||
| Title: ${new_title}${body_context} | ||
|
|
||
| EXISTING OPEN ISSUES: | ||
| ${existing_issues} | ||
|
|
||
| RULES: | ||
| - A duplicate means the same work described differently (different task IDs, rephrased titles, same underlying fix/feature). | ||
| - Different task IDs (e.g., t10 vs t023) for the same work ARE duplicates. | ||
| - Issues that are related but address different aspects are NOT duplicates. | ||
| - Auto-generated dashboard/status issues (e.g., [Supervisor:*]) are never duplicates of task issues. | ||
| - If the new issue title starts with a task ID (tNNN:) and an existing issue has a DIFFERENT task ID but describes the SAME work, that is a duplicate. | ||
| - When uncertain, prefer false (not duplicate) — closing a non-duplicate is worse than having a duplicate. | ||
|
|
||
| Respond with ONLY a JSON object: | ||
| {\"duplicate\": true|false, \"duplicate_of\": \"#NNN or empty\", \"reason\": \"one sentence\"}" | ||
|
|
||
| local ai_result="" | ||
| if [[ "$ai_cli" == "opencode" ]]; then | ||
| ai_result=$(portable_timeout 15 opencode run \ | ||
| -m "$ai_model" \ | ||
| --format default \ | ||
| --title "dedup-issue-$$" \ | ||
| "$prompt" 2>/dev/null || echo "") | ||
| ai_result=$(printf '%s' "$ai_result" | sed 's/\x1b\[[0-9;]*[mGKHF]//g; s/\x1b\[[0-9;]*[A-Za-z]//g; s/\x1b\]//g; s/\x07//g') | ||
| else | ||
| local claude_model="${ai_model#*/}" | ||
| ai_result=$(portable_timeout 15 claude \ | ||
| -p "$prompt" \ | ||
| --model "$claude_model" \ | ||
| --output-format text 2>/dev/null || echo "") | ||
| fi | ||
|
|
||
| if [[ -z "$ai_result" ]]; then | ||
| log_verbose "ai_detect_duplicate_issue: empty AI response" | ||
| return 2 | ||
| fi | ||
|
|
||
| local json_block | ||
| json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1) | ||
| if [[ -z "$json_block" ]]; then | ||
| log_verbose "ai_detect_duplicate_issue: no JSON in response" | ||
| return 2 | ||
| fi | ||
|
|
||
| local is_duplicate | ||
| is_duplicate=$(printf '%s' "$json_block" | jq -r '.duplicate // false' 2>/dev/null || echo "false") | ||
| local duplicate_of | ||
| duplicate_of=$(printf '%s' "$json_block" | jq -r '.duplicate_of // ""' 2>/dev/null || echo "") | ||
| local reason | ||
| reason=$(printf '%s' "$json_block" | jq -r '.reason // ""' 2>/dev/null || echo "") | ||
|
|
||
| # Log for audit trail | ||
| mkdir -p "${AI_LIFECYCLE_LOG_DIR:-/tmp}" 2>/dev/null || true | ||
| local timestamp | ||
| timestamp=$(date -u '+%Y%m%d-%H%M%S') | ||
| { | ||
| echo "# Duplicate Check @ $timestamp" | ||
| echo "New: $new_title" | ||
| echo "Duplicate: $is_duplicate" | ||
| echo "Of: $duplicate_of" | ||
| echo "Reason: $reason" | ||
| } >"${AI_LIFECYCLE_LOG_DIR:-/tmp}/dedup-issue-${timestamp}.md" 2>/dev/null || true | ||
|
|
||
| if [[ "$is_duplicate" == "true" && -n "$duplicate_of" ]]; then | ||
| log_info "ai_detect_duplicate_issue: DUPLICATE of $duplicate_of — $reason" | ||
| printf '%s' "$json_block" | ||
| return 0 | ||
| fi | ||
|
|
||
| log_verbose "ai_detect_duplicate_issue: not a duplicate — $reason" | ||
| return 1 | ||
| } |
There was a problem hiding this comment.
This function consistently suppresses stderr using 2>/dev/null for gh, resolve_ai_cli, resolve_model, opencode, claude, jq, mkdir, and file redirection. This is against repository guidelines and makes debugging extremely difficult by hiding critical errors. Please remove all instances of 2>/dev/null in this function. The existing || ... fallbacks are sufficient to prevent script termination where used. For file operations, || true can be used if necessary.
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.
| ai_assess_auto_dispatch() { | ||
| local task_id="$1" | ||
| local task_line="$2" | ||
| local project_root="$3" | ||
|
|
||
| # Gather context: task description, tags, brief if available | ||
| local description | ||
| description=$(printf '%s' "$task_line" | sed -E 's/^[[:space:]]*- \[.\] [^ ]+ //' || echo "") | ||
|
|
||
| local tags | ||
| tags=$(printf '%s' "$task_line" | grep -oE '#[a-zA-Z0-9_-]+' | tr '\n' ' ' || echo "") | ||
|
|
||
| local estimate | ||
| estimate=$(printf '%s' "$task_line" | grep -oE '~[0-9]+[hm]' | head -1 || echo "") | ||
|
|
||
| local brief_content="" | ||
| local brief_file="$project_root/todo/tasks/${task_id}-brief.md" | ||
| if [[ -f "$brief_file" ]]; then | ||
| # Read first 1000 chars of brief | ||
| brief_content=$(head -c 1000 "$brief_file" 2>/dev/null || echo "") | ||
| fi | ||
|
|
||
| local ai_cli | ||
| ai_cli=$(resolve_ai_cli 2>/dev/null) || { | ||
| log_verbose "ai_assess_auto_dispatch: AI unavailable, skipping assessment" | ||
| return 2 | ||
| } | ||
|
|
||
| local ai_model | ||
| ai_model=$(resolve_model "sonnet" "$ai_cli" 2>/dev/null) || { | ||
| log_verbose "ai_assess_auto_dispatch: model resolution failed, skipping" | ||
| return 2 | ||
| } | ||
|
|
||
| local brief_section="" | ||
| if [[ -n "$brief_content" ]]; then | ||
| brief_section=" | ||
|
|
||
| TASK BRIEF (first 1000 chars): | ||
| ${brief_content}" | ||
| else | ||
| brief_section=" | ||
|
|
||
| TASK BRIEF: Not found (no brief file at todo/tasks/${task_id}-brief.md)" | ||
| fi | ||
|
|
||
| local prompt | ||
| prompt="You are a task dispatch eligibility assessor for a DevOps automation system. Determine whether a task is ready for autonomous AI dispatch (no human supervision). | ||
|
|
||
| TASK: ${task_id} | ||
| DESCRIPTION: ${description} | ||
| TAGS: ${tags} | ||
| ESTIMATE: ${estimate}${brief_section} | ||
|
|
||
| ASSESSMENT CRITERIA: | ||
| 1. CLEAR DELIVERABLE: Is the task's output well-defined? (e.g., 'fix X in file Y' vs 'investigate something') | ||
| 2. ACCEPTANCE CRITERIA: Does the brief have testable acceptance criteria? (2+ criteria = strong signal) | ||
| 3. SCOPE: Is the task bounded enough for a single AI worker session? (>4h estimate = risky) | ||
| 4. ACTIONABILITY: Are there enough specifics (file paths, function names, error messages) for an AI to act on? | ||
| 5. DEPENDENCIES: Are all prerequisites met? (no unresolved blocked-by, no -needed tags) | ||
| 6. TYPE: Investigation, research, and design tasks are NOT auto-dispatchable (need human judgment). | ||
|
|
||
| LABEL ASSESSMENT: | ||
| Also determine which GitHub labels should be applied based on the task content: | ||
| - Map #tags to labels (e.g., #bugfix -> fix, #feature -> enhancement, #docs -> documentation) | ||
| - Add 'auto-dispatch' label ONLY if the task is dispatchable | ||
| - Add appropriate status label (status:available if no assignee, status:claimed if assignee present) | ||
|
|
||
| Respond with ONLY a JSON object: | ||
| {\"dispatchable\": true|false, \"labels\": [\"label1\", \"label2\"], \"reason\": \"one sentence\"}" | ||
|
|
||
| local ai_result="" | ||
| if [[ "$ai_cli" == "opencode" ]]; then | ||
| ai_result=$(portable_timeout 15 opencode run \ | ||
| -m "$ai_model" \ | ||
| --format default \ | ||
| --title "dispatch-assess-$$" \ | ||
| "$prompt" 2>/dev/null || echo "") | ||
| ai_result=$(printf '%s' "$ai_result" | sed 's/\x1b\[[0-9;]*[mGKHF]//g; s/\x1b\[[0-9;]*[A-Za-z]//g; s/\x1b\]//g; s/\x07//g') | ||
| else | ||
| local claude_model="${ai_model#*/}" | ||
| ai_result=$(portable_timeout 15 claude \ | ||
| -p "$prompt" \ | ||
| --model "$claude_model" \ | ||
| --output-format text 2>/dev/null || echo "") | ||
| fi | ||
|
|
||
| if [[ -z "$ai_result" ]]; then | ||
| log_verbose "ai_assess_auto_dispatch: empty AI response" | ||
| return 2 | ||
| fi | ||
|
|
||
| local json_block | ||
| json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]*\}' | head -1) | ||
| if [[ -z "$json_block" ]]; then | ||
| log_verbose "ai_assess_auto_dispatch: no JSON in response" | ||
| return 2 | ||
| fi | ||
|
|
||
| local is_dispatchable | ||
| is_dispatchable=$(printf '%s' "$json_block" | jq -r '.dispatchable // false' 2>/dev/null || echo "false") | ||
| local reason | ||
| reason=$(printf '%s' "$json_block" | jq -r '.reason // ""' 2>/dev/null || echo "") | ||
|
|
||
| # Log for audit trail | ||
| mkdir -p "${AI_LIFECYCLE_LOG_DIR:-/tmp}" 2>/dev/null || true | ||
| local timestamp | ||
| timestamp=$(date -u '+%Y%m%d-%H%M%S') | ||
| { | ||
| echo "# Dispatch Assessment: $task_id @ $timestamp" | ||
| echo "Dispatchable: $is_dispatchable" | ||
| echo "Reason: $reason" | ||
| echo "Response: $json_block" | ||
| } >"${AI_LIFECYCLE_LOG_DIR:-/tmp}/dispatch-assess-${task_id}-${timestamp}.md" 2>/dev/null || true | ||
|
|
||
| if [[ "$is_dispatchable" == "true" ]]; then | ||
| log_info "ai_assess_auto_dispatch: $task_id IS dispatchable — $reason" | ||
| printf '%s' "$json_block" | ||
| return 0 | ||
| fi | ||
|
|
||
| log_info "ai_assess_auto_dispatch: $task_id NOT dispatchable — $reason" | ||
| printf '%s' "$json_block" | ||
| return 1 | ||
| } |
There was a problem hiding this comment.
This function consistently suppresses stderr using 2>/dev/null for head, resolve_ai_cli, resolve_model, opencode, claude, jq, mkdir, and file redirection. This is against repository guidelines and makes debugging extremely difficult by hiding critical errors. Please remove all instances of 2>/dev/null in this function. The existing || ... fallbacks are sufficient to prevent script termination where used. For file operations, || true can be used if necessary.
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.
| fi | ||
|
|
||
| local json_block | ||
| json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]*\}' | head -1) |
There was a problem hiding this comment.
The regex \{[^}]*\} is not robust enough to parse the JSON response expected from the AI. The prompt asks for a JSON object that includes a labels array, e.g., {"labels": ["label1", "label2"]}. This regex will incorrectly stop at the first closing brace } it encounters inside the array definition, resulting in a truncated and invalid JSON block. This will cause subsequent jq parsing to fail.
| json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]*\}' | head -1) | |
| json_block=$(printf '%s' "$ai_result" | sed -n 's/.*\({\(.*\)}\).*/\1/p' | head -1) |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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/claim-task-id.sh:
- Around line 440-451: The script fails to detect the helper's "semantic
duplicate of `#NNN`" message so fallback creates a duplicate issue; update the
parsing logic that sets issue_num from push_output (the block using issue_num
and push_output) to search for either "already has issue #[0-9]+" OR "semantic
duplicate of #[0-9]+" (or more simply extract the first occurrence of "#[0-9]+"
from push_output) and then strip the "#" to populate issue_num before falling
back to bare creation.
- Around line 415-418: Run .agents/scripts/linters-local.sh before committing to
validate shell scripts (ShellCheck with proper source, secrets/syntax/pattern
checks) and fix any issues it reports; in particular update
supervisor/issue-sync.sh where the expression using sed 's/`[^`]*`//g' (around
the code handling issue body formatting) should be converted to bash parameter
expansion (e.g., use ${variable//pattern/replace}) and correct the sed quoting
to avoid SC2001/SC2016 warnings—locate the transformation around the function
handling issue body creation (referenced near create_github_issue and the
issue-sync helper invocation) and replace the sed invocation with the equivalent
safe parameter expansion and re-run linters until clean.
In @.agents/scripts/issue-sync-helper.sh:
- Around line 138-142: The grep pipeline that sets json_block from ai_result can
fail under set -euo pipefail and prematurely exit; modify the pipeline that
assigns json_block (the command using printf '%s' "$ai_result" | grep -oE
'\{[^}]+\}' | head -1) to tolerate no-match failures by appending a protective
fallback (e.g., add "|| echo \"\"" or "|| true" to the pipeline) so the
subsequent if [[ -z "$json_block" ]] check runs; update the assignment to ensure
json_block is set to an empty string on grep failure while keeping the grep/head
logic intact.
- Around line 121-132: Add the portable_timeout() function to
shared-constants.sh (copy the implementation from agent-test-helper.sh lines
166–203) so GNU timeout, gtimeout, and background+kill fallback are available,
then replace the raw timeout invocations in this script (the two calls that set
ai_result around the opencode/claude invocations) with portable_timeout to
ensure the dedup AI check uses the portable timeout wrapper.
In @.agents/scripts/supervisor/issue-sync.sh:
- Around line 2249-2259: The semantic-duplicate branch currently echoes
dup_number and returns early, skipping the task→issue persistence and DB
issue_url update; instead, after detecting a duplicate (inside the
ai_detect_duplicate_issue handling where dup_number and dup_reason are extracted
and task_id is available), call or reuse the same link-persistence routine used
for newly-created issues (the code path that persists the task→issue link and
updates the DB issue_url) or set the necessary variables so execution falls
through to that routine—do not return immediately; also log the linkage and
ensure the DB update (issue_url) is performed for task_id linking to
#${dup_number}.
- Around line 2396-2401: The json extraction pipeline can cause the shell to
exit under errexit/pipefail when sourced; update the command substitution that
sets json_block (in ai_detect_duplicate_issue and the other json-extraction
block) to append "|| true" to the pipeline so the substitution never fails,
allowing the existing [[ -z "$json_block" ]] check and return 2 handling to run
normally; apply the same change to the second occurrence referenced in the
comment so both json_block assignments are guarded.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.agents/scripts/claim-task-id.sh.agents/scripts/issue-sync-helper.sh.agents/scripts/supervisor/issue-sync.sh
| # t1324: Delegates to issue-sync-helper.sh push when available for rich | ||
| # issue bodies, proper labels (including auto-dispatch), and duplicate | ||
| # detection. Falls back to bare gh issue create if helper not found. | ||
| create_github_issue() { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if linters-local.sh exists and what it contains
fd -t f "linters-local.sh" .agents/scripts/Repository: marcusquinn/aidevops
Length of output: 96
🏁 Script executed:
# Run ShellCheck on the modified files
shellcheck .agents/scripts/claim-task-id.sh \
.agents/scripts/issue-sync-helper.sh \
.agents/scripts/supervisor/issue-sync.sh 2>&1 | head -100Repository: marcusquinn/aidevops
Length of output: 1298
🏁 Script executed:
# Examine the create_github_issue function and output parsing logic
sed -n '415,470p' .agents/scripts/claim-task-id.shRepository: marcusquinn/aidevops
Length of output: 2023
🏁 Script executed:
# Check the issue-sync-helper.sh duplicate detection message at line 1350
sed -n '1340,1360p' .agents/scripts/issue-sync-helper.shRepository: marcusquinn/aidevops
Length of output: 759
🏁 Script executed:
# Check what linters-local.sh validates and requires
cat .agents/scripts/linters-local.shRepository: marcusquinn/aidevops
Length of output: 21422
Run linters-local.sh before committing to validate shell scripts and catch quality regressions.
Per coding guidelines, execute .agents/scripts/linters-local.sh for comprehensive pre-commit validation. This includes ShellCheck with proper source following and additional quality checks (secrets, syntax, patterns). The script ensures zero technical debt standards.
Additionally, supervisor/issue-sync.sh line 1377 has style improvements: replace sed 's/[^]*//g'with bash parameter expansion${variable//search/replace}` and fix sed quoting (SC2001/SC2016).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/claim-task-id.sh around lines 415 - 418, Run
.agents/scripts/linters-local.sh before committing to validate shell scripts
(ShellCheck with proper source, secrets/syntax/pattern checks) and fix any
issues it reports; in particular update supervisor/issue-sync.sh where the
expression using sed 's/`[^`]*`//g' (around the code handling issue body
formatting) should be converted to bash parameter expansion (e.g., use
${variable//pattern/replace}) and correct the sed quoting to avoid SC2001/SC2016
warnings—locate the transformation around the function handling issue body
creation (referenced near create_github_issue and the issue-sync helper
invocation) and replace the sed invocation with the equivalent safe parameter
expansion and re-run linters until clean.
| # Also check if it found an existing issue (already has ref) | ||
| if [[ -z "$issue_num" ]]; then | ||
| issue_num=$(printf '%s' "$push_output" | grep -oE 'already has issue #[0-9]+' | grep -oE '[0-9]+' | head -1 || echo "") | ||
| fi | ||
|
|
||
| if [[ -n "$issue_num" ]]; then | ||
| log_info "Issue created via issue-sync-helper.sh: #$issue_num" | ||
| echo "$issue_num" | ||
| return 0 | ||
| fi | ||
| log_warn "issue-sync-helper.sh push returned no issue number, falling back to bare creation" | ||
| fi |
There was a problem hiding this comment.
Helper duplicate output isn’t parsed, so fallback can create a second issue.
Line 442 only matches already has issue #NNN, but helper duplicate flow emits `semantic duplicate of `#NNN. That leaves issue_num empty and triggers bare creation at Line 450.
Suggested fix
- # Also check if it found an existing issue (already has ref)
- if [[ -z "$issue_num" ]]; then
- issue_num=$(printf '%s' "$push_output" | grep -oE 'already has issue #[0-9]+' | grep -oE '[0-9]+' | head -1 || echo "")
- fi
+ # Also check existing-issue flows (already linked / semantic duplicate)
+ if [[ -z "$issue_num" ]]; then
+ issue_num=$(printf '%s' "$push_output" |
+ grep -oE '(already has issue|semantic duplicate of) #[0-9]+' |
+ grep -oE '[0-9]+' | head -1 || echo "")
+ 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.
| # Also check if it found an existing issue (already has ref) | |
| if [[ -z "$issue_num" ]]; then | |
| issue_num=$(printf '%s' "$push_output" | grep -oE 'already has issue #[0-9]+' | grep -oE '[0-9]+' | head -1 || echo "") | |
| fi | |
| if [[ -n "$issue_num" ]]; then | |
| log_info "Issue created via issue-sync-helper.sh: #$issue_num" | |
| echo "$issue_num" | |
| return 0 | |
| fi | |
| log_warn "issue-sync-helper.sh push returned no issue number, falling back to bare creation" | |
| fi | |
| # Also check existing-issue flows (already linked / semantic duplicate) | |
| if [[ -z "$issue_num" ]]; then | |
| issue_num=$(printf '%s' "$push_output" | | |
| grep -oE '(already has issue|semantic duplicate of) #[0-9]+' | | |
| grep -oE '[0-9]+' | head -1 || echo "") | |
| fi | |
| if [[ -n "$issue_num" ]]; then | |
| log_info "Issue created via issue-sync-helper.sh: #$issue_num" | |
| echo "$issue_num" | |
| return 0 | |
| fi | |
| log_warn "issue-sync-helper.sh push returned no issue number, falling back to bare creation" | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/claim-task-id.sh around lines 440 - 451, The script fails to
detect the helper's "semantic duplicate of `#NNN`" message so fallback creates a
duplicate issue; update the parsing logic that sets issue_num from push_output
(the block using issue_num and push_output) to search for either "already has
issue #[0-9]+" OR "semantic duplicate of #[0-9]+" (or more simply extract the
first occurrence of "#[0-9]+" from push_output) and then strip the "#" to
populate issue_num before falling back to bare creation.
| ai_result=$(timeout 15 opencode run \ | ||
| -m "anthropic/claude-sonnet-4-20250514" \ | ||
| --format default \ | ||
| --title "dedup-$$" \ | ||
| "$prompt" 2>/dev/null || echo "") | ||
| ai_result=$(printf '%s' "$ai_result" | sed 's/\x1b\[[0-9;]*[mGKHF]//g; s/\x1b\[[0-9;]*[A-Za-z]//g; s/\x1b\]//g; s/\x07//g') | ||
| else | ||
| ai_result=$(timeout 15 claude \ | ||
| -p "$prompt" \ | ||
| --model "claude-sonnet-4-20250514" \ | ||
| --output-format text 2>/dev/null || echo "") | ||
| fi |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if portable_timeout is defined anywhere in the codebase
rg "portable_timeout" --type shRepository: marcusquinn/aidevops
Length of output: 4267
🏁 Script executed:
# Check what's in the .agents/scripts directory
ls -la .agents/scripts/ 2>/dev/null || echo "Directory not found"Repository: marcusquinn/aidevops
Length of output: 20320
🏁 Script executed:
# View the actual issue-sync-helper.sh file to understand context
wc -l .agents/scripts/issue-sync-helper.sh 2>/dev/null || echo "File not found"Repository: marcusquinn/aidevops
Length of output: 105
🏁 Script executed:
# Check for timeout usage and any utility function definitions
rg "timeout\s" .agents/scripts/issue-sync-helper.sh -A 2 -B 2Repository: marcusquinn/aidevops
Length of output: 704
🏁 Script executed:
# Look for any helper functions or utilities defined in the script
head -50 .agents/scripts/issue-sync-helper.shRepository: marcusquinn/aidevops
Length of output: 2296
🏁 Script executed:
# Check if portable_timeout is sourced or defined in issue-sync-helper.sh
cat -n .agents/scripts/issue-sync-helper.sh | head -100 | grep -E "(source|portable_timeout|\. )"Repository: marcusquinn/aidevops
Length of output: 161
🏁 Script executed:
# Check what's in issue-sync-lib.sh to see if portable_timeout is there
grep -n "portable_timeout" .agents/scripts/issue-sync-lib.shRepository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
# Check what shared-constants.sh contains
head -50 .agents/scripts/shared-constants.sh | grep -E "(source|portable_timeout|\. )"Repository: marcusquinn/aidevops
Length of output: 184
🏁 Script executed:
# Look at the actual lines 121-132 in context
sed -n '115,140p' .agents/scripts/issue-sync-helper.sh | cat -nRepository: marcusquinn/aidevops
Length of output: 1053
🏁 Script executed:
# Check if portable_timeout is defined anywhere in the main scripts directory
grep -n "^portable_timeout()" .agents/scripts/*.shRepository: marcusquinn/aidevops
Length of output: 125
🏁 Script executed:
# Check if portable_timeout is defined in shared-constants.sh
grep -n "portable_timeout" .agents/scripts/shared-constants.shRepository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
# Check the full issue-sync-lib.sh to see if portable_timeout is defined
wc -l .agents/scripts/issue-sync-lib.shRepository: marcusquinn/aidevops
Length of output: 102
🏁 Script executed:
# Search for portable_timeout definition in issue-sync-lib.sh
grep -A 10 "^portable_timeout" .agents/scripts/issue-sync-lib.shRepository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
# Get the portable_timeout function from agent-test-helper.sh to understand its signature
sed -n '166,200p' .agents/scripts/agent-test-helper.shRepository: marcusquinn/aidevops
Length of output: 773
🏁 Script executed:
# Check if supervisor scripts source a common file with portable_timeout
head -40 .agents/scripts/supervisor/_common.sh | grep -A 10 "^portable_timeout"Repository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
# Verify the exact lines 121-132 in issue-sync-helper.sh with more context
sed -n '110,145p' .agents/scripts/issue-sync-helper.sh | cat -nRepository: marcusquinn/aidevops
Length of output: 1614
🏁 Script executed:
# Check if issue-sync-lib.sh sources _common.sh
grep -E "(source|\..*_common)" .agents/scripts/issue-sync-lib.sh | head -20Repository: marcusquinn/aidevops
Length of output: 449
🏁 Script executed:
# Confirm this is the _ai_check_duplicate function mentioned in the review
grep -n "_ai_check_duplicate" .agents/scripts/issue-sync-helper.shRepository: marcusquinn/aidevops
Length of output: 178
🏁 Script executed:
# Look at the beginning of issue-sync-lib.sh to see what it sources
head -80 .agents/scripts/issue-sync-lib.sh | cat -nRepository: marcusquinn/aidevops
Length of output: 3362
🏁 Script executed:
# Check if issue-sync-lib.sh or shared-constants.sh should be sourcing _common.sh
grep -E "(supervisor|_common)" .agents/scripts/issue-sync-lib.sh .agents/scripts/shared-constants.shRepository: marcusquinn/aidevops
Length of output: 639
🏁 Script executed:
# Verify that portable_timeout is not available in issue-sync-helper.sh context
# by checking if it can be called
bash -c 'source .agents/scripts/shared-constants.sh; source .agents/scripts/issue-sync-lib.sh; declare -f portable_timeout' 2>&1 | head -5Repository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
# Check how other issue-sync scripts in supervisor/ handle this
grep -B 5 "portable_timeout" .agents/scripts/supervisor/issue-sync.sh | head -30Repository: marcusquinn/aidevops
Length of output: 1235
🏁 Script executed:
# Check if shared-constants.sh or issue-sync-lib.sh should be providing portable_timeout
grep -n "portable_timeout" .agents/scripts/shared-constants.shRepository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
# Verify the _ai_check_duplicate function location and see if it has access to portable_timeout
sed -n '76,90p' .agents/scripts/issue-sync-helper.sh | cat -nRepository: marcusquinn/aidevops
Length of output: 500
🏁 Script executed:
# Check how supervisor/issue-sync.sh makes portable_timeout available
head -50 .agents/scripts/supervisor/issue-sync.sh | grep -E "(source|portable)"Repository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
# Determine if issue-sync-lib.sh should be sourcing supervisor/_common.sh or if portable_timeout should be in shared-constants.sh
grep -A 5 "^source" .agents/scripts/issue-sync-lib.shRepository: marcusquinn/aidevops
Length of output: 296
🏁 Script executed:
# Confirm: does the current issue-sync-helper.sh have timeout but not portable_timeout?
grep -n "timeout" .agents/scripts/issue-sync-helper.sh | grep -v "#"Repository: marcusquinn/aidevops
Length of output: 213
🏁 Script executed:
# Look at the overall structure of supervisor/issue-sync.sh to understand how it sources portable_timeout
head -60 .agents/scripts/supervisor/issue-sync.sh | cat -nRepository: marcusquinn/aidevops
Length of output: 3385
🏁 Script executed:
# Check if there's a way issue-sync-helper.sh should be getting portable_timeout
# Look at what shared-constants.sh actually contains
grep -n "^[a-z_]*() {" .agents/scripts/shared-constants.sh | head -20Repository: marcusquinn/aidevops
Length of output: 558
🏁 Script executed:
# Check if supervisor/_common.sh is the right source for portable_timeout and see if issue-sync should use it
head -30 .agents/scripts/supervisor/_common.sh | cat -nRepository: marcusquinn/aidevops
Length of output: 1159
🏁 Script executed:
# Verify: does issue-sync-helper.sh work at all currently, or is this a new addition?
git log -1 --oneline .agents/scripts/issue-sync-helper.sh 2>/dev/null || echo "Git not available"Repository: marcusquinn/aidevops
Length of output: 149
🏁 Script executed:
# Verify the new feature commit changed timeout to something
git show 642a0af --stat 2>/dev/null | head -20Repository: marcusquinn/aidevops
Length of output: 1110
🏁 Script executed:
# Check if there are other scripts in .agents/scripts (not supervisor/) that use portable_timeout
grep -l "portable_timeout" .agents/scripts/*.shRepository: marcusquinn/aidevops
Length of output: 100
🏁 Script executed:
# See if portable_timeout should be added to shared-constants.sh
grep -A 15 "^portable_timeout" .agents/scripts/supervisor/_common.sh | head -40Repository: marcusquinn/aidevops
Length of output: 374
🏁 Script executed:
# Check if portable_timeout should be in shared-constants.sh by looking at agent-test-helper.sh
sed -n '160,180p' .agents/scripts/agent-test-helper.sh | cat -nRepository: marcusquinn/aidevops
Length of output: 688
🏁 Script executed:
# Verify if there's a way issue-sync-helper.sh is supposed to work currently
# by checking if it has its own timeout handling or workaround
grep -B 5 -A 15 "^_ai_check_duplicate" .agents/scripts/issue-sync-helper.sh | head -40Repository: marcusquinn/aidevops
Length of output: 572
🏁 Script executed:
# Check what portable_timeout implementation looks like in agent-test-helper.sh completely
sed -n '166,220p' .agents/scripts/agent-test-helper.sh | cat -nRepository: marcusquinn/aidevops
Length of output: 1616
🏁 Script executed:
# Final check: confirm that issue-sync-helper.sh cannot call portable_timeout
bash -c '
source .agents/scripts/shared-constants.sh
source .agents/scripts/issue-sync-lib.sh
declare -f portable_timeout >/dev/null 2>&1 && echo "portable_timeout is available" || echo "portable_timeout is NOT available"
'Repository: marcusquinn/aidevops
Length of output: 97
Add portable_timeout to shared-constants.sh, then replace raw timeout calls on lines 121 and 128.
The timeout command is unavailable on macOS runners by default, causing the dedup AI check to silently fail. A portable_timeout function already exists in agent-test-helper.sh that provides GNU timeout, gtimeout, and a background+kill fallback — it should be added to shared-constants.sh (which this script already sources) and used here instead.
Suggested fix
- Add
portable_timeout()toshared-constants.sh(copy fromagent-test-helper.shlines 166–203) - Update lines 121 and 128:
- ai_result=$(timeout 15 opencode run \
+ ai_result=$(portable_timeout 15 opencode run \
-m "anthropic/claude-sonnet-4-20250514" \
--format default \
--title "dedup-$$" \
"$prompt" 2>/dev/null || echo "")
@@
- ai_result=$(timeout 15 claude \
+ ai_result=$(portable_timeout 15 claude \
-p "$prompt" \
--model "claude-sonnet-4-20250514" \
--output-format text 2>/dev/null || echo "")Per coding guidelines .agents/scripts/*.sh: Automation scripts must ensure reliability and robustness across platforms.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/issue-sync-helper.sh around lines 121 - 132, Add the
portable_timeout() function to shared-constants.sh (copy the implementation from
agent-test-helper.sh lines 166–203) so GNU timeout, gtimeout, and
background+kill fallback are available, then replace the raw timeout invocations
in this script (the two calls that set ai_result around the opencode/claude
invocations) with portable_timeout to ensure the dedup AI check uses the
portable timeout wrapper.
| local json_block | ||
| json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1) | ||
| if [[ -z "$json_block" ]]; then | ||
| return 1 | ||
| fi |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the top of the file for set -euo pipefail or equivalent
head -30 .agents/scripts/issue-sync-helper.sh
# Also check lines around 138-142 for context
sed -n '130,150p' .agents/scripts/issue-sync-helper.sh
# Check if there are other instances of similar patterns in the file
rg -n 'grep.*\|.*head' .agents/scripts/issue-sync-helper.shRepository: marcusquinn/aidevops
Length of output: 3963
Add error handling to grep pipeline on line 139 to prevent hard exit under set -euo pipefail.
The grep command can fail when no JSON block is found, causing the script to terminate before the fallback check if [[ -z "$json_block" ]] executes. This pattern is inconsistent with similar grep operations throughout the script (lines 1268, 1317, 1465, etc.), which all include || echo "" or || true protection.
Suggested fix
- json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1)
+ json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1 || true)
if [[ -z "$json_block" ]]; then
return 1
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 json_block | |
| json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1) | |
| if [[ -z "$json_block" ]]; then | |
| return 1 | |
| fi | |
| local json_block | |
| json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1 || true) | |
| if [[ -z "$json_block" ]]; then | |
| return 1 | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/issue-sync-helper.sh around lines 138 - 142, The grep
pipeline that sets json_block from ai_result can fail under set -euo pipefail
and prematurely exit; modify the pipeline that assigns json_block (the command
using printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1) to tolerate
no-match failures by appending a protective fallback (e.g., add "|| echo \"\""
or "|| true" to the pipeline) so the subsequent if [[ -z "$json_block" ]] check
runs; update the assignment to ensure json_block is set to an empty string on
grep failure while keeping the grep/head logic intact.
| if dedup_result=$(ai_detect_duplicate_issue "$new_title" "" "$repo_slug" 2>/dev/null); then | ||
| local dup_number | ||
| dup_number=$(printf '%s' "$dedup_result" | jq -r '.duplicate_of // ""' 2>/dev/null | tr -d '#') | ||
| if [[ -n "$dup_number" && "$dup_number" =~ ^[0-9]+$ ]]; then | ||
| local dup_reason | ||
| dup_reason=$(printf '%s' "$dedup_result" | jq -r '.reason // ""' 2>/dev/null) | ||
| log_warn "Semantic duplicate detected: $task_id duplicates #${dup_number} — $dup_reason" | ||
| log_info "Linking $task_id to existing issue #${dup_number} instead of creating new" | ||
| echo "$dup_number" | ||
| return 0 | ||
| fi |
There was a problem hiding this comment.
Semantic-duplicate branch returns before persisting the task→issue link.
This early return skips the normal link persistence path (TODO ref + DB issue_url update), so the task can remain unlinked even though you found a valid existing issue.
Suggested fix
if dedup_result=$(ai_detect_duplicate_issue "$new_title" "" "$repo_slug" 2>/dev/null); then
local dup_number
dup_number=$(printf '%s' "$dedup_result" | jq -r '.duplicate_of // ""' 2>/dev/null | tr -d '#')
if [[ -n "$dup_number" && "$dup_number" =~ ^[0-9]+$ ]]; then
local dup_reason
dup_reason=$(printf '%s' "$dedup_result" | jq -r '.reason // ""' 2>/dev/null)
log_warn "Semantic duplicate detected: $task_id duplicates #${dup_number} — $dup_reason"
log_info "Linking $task_id to existing issue #${dup_number} instead of creating new"
+ local todo_file="$repo_path/TODO.md"
+ if [[ -f "$todo_file" ]]; then
+ add_gh_ref_to_todo "$task_id" "$dup_number" "$todo_file"
+ commit_and_push_todo "$repo_path" "chore: link $task_id to existing GH#${dup_number}"
+ fi
+ local escaped_id escaped_url
+ escaped_id=$(sql_escape "$task_id")
+ escaped_url=$(sql_escape "https://github.com/${repo_slug}/issues/${dup_number}")
+ db "$SUPERVISOR_DB" "UPDATE tasks SET issue_url = '$escaped_url' WHERE id = '$escaped_id';"
echo "$dup_number"
return 0
fi
fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/supervisor/issue-sync.sh around lines 2249 - 2259, The
semantic-duplicate branch currently echoes dup_number and returns early,
skipping the task→issue persistence and DB issue_url update; instead, after
detecting a duplicate (inside the ai_detect_duplicate_issue handling where
dup_number and dup_reason are extracted and task_id is available), call or reuse
the same link-persistence routine used for newly-created issues (the code path
that persists the task→issue link and updates the DB issue_url) or set the
necessary variables so execution falls through to that routine—do not return
immediately; also log the linkage and ensure the DB update (issue_url) is
performed for task_id linking to #${dup_number}.
| local json_block | ||
| json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1) | ||
| if [[ -z "$json_block" ]]; then | ||
| log_verbose "ai_detect_duplicate_issue: no JSON in response" | ||
| return 2 | ||
| fi |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check file size and read critical sections
wc -l .agents/scripts/supervisor/issue-sync.sh
# Read the shebang and initial shell options
head -20 .agents/scripts/supervisor/issue-sync.sh
# Search for set -e, set -o pipefail, or set -euo pipefail at the beginning
head -50 .agents/scripts/supervisor/issue-sync.sh | grep -E '(set -|set \+)'Repository: marcusquinn/aidevops
Length of output: 860
🏁 Script executed:
#!/bin/bash
# Read the specific lines mentioned: 2396-2401
sed -n '2390,2410p' .agents/scripts/supervisor/issue-sync.sh
# Read the secondary location: 2539-2544
sed -n '2535,2550p' .agents/scripts/supervisor/issue-sync.shRepository: marcusquinn/aidevops
Length of output: 1240
🏁 Script executed:
#!/bin/bash
# Check entire script for all set options
grep -n "^set " .agents/scripts/supervisor/issue-sync.sh | head -20Repository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Check if ShellCheck is available, if not install it
if ! command -v shellcheck &> /dev/null; then
apt-get update -qq && apt-get install -y shellcheck > /dev/null 2>&1
fi
# Run ShellCheck on the file focusing on the specific areas
shellcheck -S warning .agents/scripts/supervisor/issue-sync.sh 2>&1 | grep -A 3 "2396\|2539\|SC2143\|pipefail" || echo "No specific warnings found"Repository: marcusquinn/aidevops
Length of output: 90
🏁 Script executed:
#!/bin/bash
# Run full ShellCheck to see all issues
shellcheck .agents/scripts/supervisor/issue-sync.sh 2>&1 | head -40Repository: marcusquinn/aidevops
Length of output: 610
🏁 Script executed:
#!/bin/bash
# Check if the script is sourced by other scripts
grep -r "source.*issue-sync.sh\|\..*issue-sync.sh" .agents --include="*.sh" 2>/dev/null || echo "No sourcing found"Repository: marcusquinn/aidevops
Length of output: 355
Guard JSON extraction to avoid unexpected exits when sourced into errexit/pipefail contexts.
This script is sourced by supervisor-helper.sh and backfill-blocked-comments.sh. If those parent contexts enable set -o pipefail, a failed grep in the pipeline will cause the command substitution to fail and exit hard, bypassing your intended return 2 on empty JSON. Add || true to ensure the pipeline always succeeds and lets the empty-check logic handle the error case.
Applies to lines 2396–2401 and 2539–2544.
Suggested fix
- json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1)
+ json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1 || true)
if [[ -z "$json_block" ]]; then
log_verbose "ai_detect_duplicate_issue: no JSON in response"
return 2
fi
@@
- json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]*\}' | head -1)
+ json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]*\}' | head -1 || true)
if [[ -z "$json_block" ]]; then
log_verbose "ai_assess_auto_dispatch: no JSON in response"
return 2
fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/supervisor/issue-sync.sh around lines 2396 - 2401, The json
extraction pipeline can cause the shell to exit under errexit/pipefail when
sourced; update the command substitution that sets json_block (in
ai_detect_duplicate_issue and the other json-extraction block) to append "||
true" to the pipeline so the substitution never fails, allowing the existing [[
-z "$json_block" ]] check and return 2 handling to run normally; apply the same
change to the second occurrence referenced in the comment so both json_block
assignments are guarded.



Summary
Changes
.agents/scripts/supervisor/issue-sync.shai_detect_duplicate_issue(): Fetches open issues, sends to AI with new title for semantic comparison. Returns duplicate issue number if found.ai_assess_auto_dispatch(): Gathers task description, tags, brief content. AI assesses dispatchability and recommends labels.create_github_issue(): Added AI duplicate check after deterministic title-prefix check..agents/scripts/issue-sync-helper.sh_ai_check_duplicate(): Standalone duplicate detection (no supervisor module dependencies). Uses claude/opencode with 15s timeout.cmd_push(): Calls_ai_check_duplicate()before creating issues. Links to existing issue if duplicate found..agents/scripts/claim-task-id.shcreate_github_issue(): Delegates toissue-sync-helper.sh pushwhen available for rich bodies + labels. Falls back to bare creation.Design
AI_LIFECYCLE_LOG_DIRcheck_task_staleness()(gather facts → AI decides → act)Summary by CodeRabbit
New Features
Improvements