fix: prevent auto-closing failed issues and preserve em-dash content#2112
fix: prevent auto-closing failed issues and preserve em-dash content#2112marcusquinn wants to merge 1 commit intomainfrom
Conversation
…in task descriptions - Supervisor no longer closes issues when tasks fail after max retries; instead posts a failure comment and adds 'needs-review' label, keeping the issue open for human triage (fixes #2106, #1763, #1762) - parse_task_line() now strips metadata fields individually instead of truncating at the first tag, preserving em-dash content in descriptions
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 addresses two key issues: preventing the automatic closure of failed tasks in the supervisor system and improving the parsing of issue descriptions to correctly handle em-dash content. These changes enhance the robustness of task management by ensuring human review for failed tasks and improving data integrity in issue descriptions. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
WalkthroughModified task description parsing in Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Sat Feb 21 16:42:42 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
|
There was a problem hiding this comment.
Code Review
This pull request makes two valuable fixes. First, it prevents failed tasks from being automatically closed on GitHub, instead adding a needs-review label and a comment, which improves the triage process for failed tasks. Second, it refactors the task description parser to correctly handle em-dashes and other special content by individually removing metadata fields instead of truncating the description. My review includes suggestions to improve script efficiency by consolidating multiple sed commands and to adhere to the repository's style guide and rules by removing blanket error suppression on gh commands, which will improve debuggability.
| description=$(echo "$full_text" | | ||
| sed -E 's/ #[a-z][a-z0-9-]*//g' | # Remove tags | ||
| sed -E 's/ ~[0-9]+[hmd]( \(ai:[^)]+\))?//g' | # Remove estimate | ||
| sed -E 's/ (logged|started|completed|actual|verified):[^ ]+//g' | # Remove date fields | ||
| sed -E 's/ (assignee|blocked-by|blocks|model|category):[^ ]+//g' | # Remove other metadata | ||
| sed -E 's/ ref:GH#[0-9]+//g' | # Remove GH ref | ||
| sed -E 's/ pr:#[0-9]+//g' | # Remove PR ref | ||
| sed -E 's/ → \[todo\/PLANS\.md#[^]]+\]//g' | # Remove plan link | ||
| sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' || # Trim whitespace | ||
| echo "") |
There was a problem hiding this comment.
Chaining multiple sed commands via pipes can be inefficient as each one starts a new process. You can consolidate these into a single sed call using the -e option for each expression. This improves both performance and readability.
| description=$(echo "$full_text" | | |
| sed -E 's/ #[a-z][a-z0-9-]*//g' | # Remove tags | |
| sed -E 's/ ~[0-9]+[hmd]( \(ai:[^)]+\))?//g' | # Remove estimate | |
| sed -E 's/ (logged|started|completed|actual|verified):[^ ]+//g' | # Remove date fields | |
| sed -E 's/ (assignee|blocked-by|blocks|model|category):[^ ]+//g' | # Remove other metadata | |
| sed -E 's/ ref:GH#[0-9]+//g' | # Remove GH ref | |
| sed -E 's/ pr:#[0-9]+//g' | # Remove PR ref | |
| sed -E 's/ → \[todo\/PLANS\.md#[^]]+\]//g' | # Remove plan link | |
| sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' || # Trim whitespace | |
| echo "") | |
| description=$(echo "$full_text" | sed -E \ | |
| -e 's/ #[a-z][a-z0-9-]*//g' \ | |
| -e 's/ ~[0-9]+[hmd]( \(ai:[^)]+\))?//g' \ | |
| -e 's/ (logged|started|completed|actual|verified):[^ ]+//g' \ | |
| -e 's/ (assignee|blocked-by|blocks|model|category):[^ ]+//g' \ | |
| -e 's/ ref:GH#[0-9]+//g' \ | |
| -e 's/ pr:#[0-9]+//g' \ | |
| -e 's/ → \[todo\/PLANS\.md#[^]]+\]//g' \ | |
| -e 's/^[[:space:]]+//; s/[[:space:]]+$//' || | |
| echo "") |
References
- Optimize shell script pipelines by replacing 'grep | sed' combinations with a single, more efficient 'sed' command where possible to improve performance. This principle extends to consolidating multiple 'sed' commands for efficiency.
| gh issue comment "$issue_number" --repo "$repo_slug" \ | ||
| --body "$fail_comment" 2>/dev/null || true |
There was a problem hiding this comment.
Using 2>/dev/null suppresses all error messages from the gh command, which can hide important issues like authentication failures, network problems, or invalid issue numbers. The || true already prevents the script from exiting on failure. According to the repository style guide (line 50) and repository rules, 2>/dev/null should not be used for blanket error suppression. Please remove it to allow errors to be visible for debugging.
| gh issue comment "$issue_number" --repo "$repo_slug" \ | |
| --body "$fail_comment" 2>/dev/null || true | |
| gh issue comment "$issue_number" --repo "$repo_slug" \ | |
| --body "$fail_comment" || true |
References
- Line 50:
2>/dev/nullis acceptable ONLY when redirecting to log files, not blanket suppression. (link) - 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.
- In shell scripts with 'set -e' enabled, use '|| true' to prevent the script from exiting when a command like 'jq' fails on an optional lookup. Do not suppress stderr with '2>/dev/null' so that actual syntax or system errors remain visible for debugging.
| gh issue edit "$issue_number" --repo "$repo_slug" \ | ||
| "${remove_args[@]}" 2>/dev/null || true | ||
| log_verbose "sync_issue_status_label: closed #$issue_number as failed ($task_id)" | ||
| return 0 | ||
| --add-label "needs-review" "${remove_args[@]}" 2>/dev/null || true |
There was a problem hiding this comment.
Similar to the previous comment, using 2>/dev/null here suppresses potentially important error messages from the gh command. This violates the repository style guide (line 50) and repository rules which advise against blanket error suppression. Please remove 2>/dev/null to aid in debugging potential issues.
| gh issue edit "$issue_number" --repo "$repo_slug" \ | |
| "${remove_args[@]}" 2>/dev/null || true | |
| log_verbose "sync_issue_status_label: closed #$issue_number as failed ($task_id)" | |
| return 0 | |
| --add-label "needs-review" "${remove_args[@]}" 2>/dev/null || true | |
| gh issue edit "$issue_number" --repo "$repo_slug" \ | |
| --add-label "needs-review" "${remove_args[@]}" || true |
References
- Line 50:
2>/dev/nullis acceptable ONLY when redirecting to log files, not blanket suppression. (link) - 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.
- In shell scripts with 'set -e' enabled, use '|| true' to prevent the script from exiting when a command like 'jq' fails on an optional lookup. Do not suppress stderr with '2>/dev/null' so that actual syntax or system errors remain visible for debugging.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
.agents/scripts/issue-sync-lib.sh (1)
91-100: Consider consolidating 8 pipedsedcalls into one.8 separate process forks per task line is acceptable for single invocations but becomes a bottleneck when
parse_task_lineis called in a loop over hundreds of tasks (e.g., during the ~400-issue backfill mentioned in the PR). All these substitutions can be expressed as a singlesedwith multiple-eexpressions.♻️ Proposed refactor — single `sed` invocation
- description=$(echo "$full_text" | - sed -E 's/ #[a-z][a-z0-9-]*//g' | # Remove tags - sed -E 's/ ~[0-9]+[hmd]( \(ai:[^)]+\))?//g' | # Remove estimate - sed -E 's/ (logged|started|completed|actual|verified):[^ ]+//g' | # Remove date fields - sed -E 's/ (assignee|blocked-by|blocks|model|category):[^ ]+//g' | # Remove other metadata - sed -E 's/ ref:GH#[0-9]+//g' | # Remove GH ref - sed -E 's/ pr:#[0-9]+//g' | # Remove PR ref - sed -E 's/ → \[todo\/PLANS\.md#[^]]+\]//g' | # Remove plan link - sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' || # Trim whitespace - echo "") + description=$(echo "$full_text" | + sed -E \ + -e 's/ #[a-z][a-z0-9-]*//g' \ + -e 's/ ~[0-9]+[hmd]( \(ai:[^)]+\))?//g' \ + -e 's/ (logged|started|completed|actual|verified):[^ ]+//g' \ + -e 's/ (assignee|blocked-by|blocks|model|category):[^ ]+//g' \ + -e 's/ ref:GH#[0-9]+//g' \ + -e 's/ pr:#[0-9]+//g' \ + -e 's/ ref:todo\/tasks\/[^ ]+//g' \ + -e 's/ → \[todo\/PLANS\.md#[^]]+\]//g' \ + -e 's/^[[:space:]]+//; s/[[:space:]]+$//' || + echo "")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.agents/scripts/issue-sync-lib.sh around lines 91 - 100, The pipeline that builds description from full_text currently spawns eight sed processes; inside parse_task_line replace the chained sed calls that pipe from echo "$full_text" with a single sed invocation using multiple -e expressions (or a single -E script with semicolon-separated commands) that preserves each existing regex (tag removal, estimate, date fields, metadata keys, GH/PR refs, plan link, and trimming) and keep the existing "|| echo """ fallback; update the command that sets description so it still reads from full_text and yields the same final output but with one sed process instead of eight.
🤖 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/issue-sync-lib.sh:
- Around line 83-100: The description sanitization pipeline fails to strip
"ref:todo/tasks/..." references; update the pipeline that assigns description
(the sed chain that currently removes "ref:GH#...", "pr:#", and plan links) to
also remove patterns like "ref:todo/tasks/..." by adding a sed rule that strips
" ref:todo/tasks/<non-space>" (e.g., a sed -E replacement for "
ref:todo/tasks/[^ ]+") into the same chain that produces the description
variable so find_related_files and generated issue descriptions no longer leak
those refs.
In @.agents/scripts/supervisor/issue-sync.sh:
- Around line 467-468: The comment on the fall-through after the "failed" case
is misleading because state_to_status_label("failed") returns an empty string so
the subsequent non-terminal label logic is a no-op; update the comment near the
fall-through to state that the fall-through is intentional to let the "blocked"
branch (which yields a non-empty label) reuse the label-update logic and that
"failed" produces no label, or alternatively change the comment to say
"fall-through to shared label handling; no-op for failed (empty label)";
reference state_to_status_label, the "failed" and "blocked" cases, and the
guarded blocks that check if [[ -n "$new_label" ]] to guide where to change the
text.
- Around line 460-468: The script currently attempts to add the "needs-review"
label with gh issue edit which will silently fail if the label doesn't exist;
update the label creation flow by ensuring the "needs-review" label is created
(use the existing pattern that calls gh label create --force before gh issue
edit, e.g. in ensure_status_labels or the same place add_model_label is invoked)
so the --add-label call cannot fail, and add "needs-review" to the
ALL_STATUS_LABELS array so it is cleaned up during subsequent state transitions
(reference ensure_status_labels and ALL_STATUS_LABELS to locate where to add the
create call and the new label entry).
---
Nitpick comments:
In @.agents/scripts/issue-sync-lib.sh:
- Around line 91-100: The pipeline that builds description from full_text
currently spawns eight sed processes; inside parse_task_line replace the chained
sed calls that pipe from echo "$full_text" with a single sed invocation using
multiple -e expressions (or a single -E script with semicolon-separated
commands) that preserves each existing regex (tag removal, estimate, date
fields, metadata keys, GH/PR refs, plan link, and trimming) and keep the
existing "|| echo """ fallback; update the command that sets description so it
still reads from full_text and yields the same final output but with one sed
process instead of eight.
| # Extract FULL description (everything after task ID, including em-dash content) | ||
| # We'll clean out metadata fields separately, but preserve the full text | ||
| local full_text | ||
| full_text=$(echo "$line" | sed -E 's/^[[:space:]]*- \[.\] t[0-9]+(\.[0-9]+)* //' || echo "") | ||
|
|
||
| # Extract description by removing all metadata fields but keeping em-dash content | ||
| # Strategy: remove tags, estimate, and key:value pairs, but preserve text after em dash | ||
| local description | ||
| description=$(echo "$line" | sed -E 's/^[[:space:]]*- \[.\] t[0-9]+(\.[0-9]+)* //' | | ||
| sed -E 's/ (#[a-z]|~[0-9]|→ |logged:|started:|completed:|ref:|actual:|blocked-by:|blocks:|assignee:|verified:).*//' || | ||
| description=$(echo "$full_text" | | ||
| sed -E 's/ #[a-z][a-z0-9-]*//g' | # Remove tags | ||
| sed -E 's/ ~[0-9]+[hmd]( \(ai:[^)]+\))?//g' | # Remove estimate | ||
| sed -E 's/ (logged|started|completed|actual|verified):[^ ]+//g' | # Remove date fields | ||
| sed -E 's/ (assignee|blocked-by|blocks|model|category):[^ ]+//g' | # Remove other metadata | ||
| sed -E 's/ ref:GH#[0-9]+//g' | # Remove GH ref | ||
| sed -E 's/ pr:#[0-9]+//g' | # Remove PR ref | ||
| sed -E 's/ → \[todo\/PLANS\.md#[^]]+\]//g' | # Remove plan link | ||
| sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' || # Trim whitespace | ||
| echo "") |
There was a problem hiding this comment.
ref:todo/tasks/ metadata not stripped from description.
The sanitization pipeline removes ref:GH#, pr:#, and plan links, but ref:todo/tasks/ references (used in find_related_files at line 546) are not stripped. Any task line containing ref:todo/tasks/prd.md will have that text leak into the generated issue description.
🛠️ Proposed fix — add `ref:todo/tasks/` pattern to the pipeline
sed -E 's/ ref:GH#[0-9]+//g' | # Remove GH ref
sed -E 's/ pr:#[0-9]+//g' | # Remove PR ref
+ sed -E 's/ ref:todo\/tasks\/[^ ]+//g' | # Remove task file ref
sed -E 's/ → \[todo\/PLANS\.md#[^]]+\]//g' | # Remove plan link🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/scripts/issue-sync-lib.sh around lines 83 - 100, The description
sanitization pipeline fails to strip "ref:todo/tasks/..." references; update the
pipeline that assigns description (the sed chain that currently removes
"ref:GH#...", "pr:#", and plan links) to also remove patterns like
"ref:todo/tasks/..." by adding a sed rule that strips "
ref:todo/tasks/<non-space>" (e.g., a sed -E replacement for " ref:todo/tasks/[^
]+") into the same chain that produces the description variable so
find_related_files and generated issue descriptions no longer leak those refs.
| # DO NOT auto-close failed tasks - they need human review | ||
| # Add needs-review label and post failure comment, but keep issue OPEN | ||
| gh issue comment "$issue_number" --repo "$repo_slug" \ | ||
| --body "$fail_comment" 2>/dev/null || true | ||
| gh issue edit "$issue_number" --repo "$repo_slug" \ | ||
| "${remove_args[@]}" 2>/dev/null || true | ||
| log_verbose "sync_issue_status_label: closed #$issue_number as failed ($task_id)" | ||
| return 0 | ||
| --add-label "needs-review" "${remove_args[@]}" 2>/dev/null || true | ||
| log_verbose "sync_issue_status_label: flagged #$issue_number for review ($task_id failed)" | ||
| # Don't return here - let the non-terminal state logic handle label updates | ||
| ;; |
There was a problem hiding this comment.
needs-review label will silently fail to apply if it doesn't exist on the repo.
GitHub CLI's --add-label requires the label to already exist in the repository — it will error (silently swallowed by 2>/dev/null || true) if needs-review hasn't been created. Since this label is the core deliverable of the PR fix, a silent failure means failed tasks appear to stay open but carry no needs-review signal.
The established in-file pattern (add_model_label, lines 178–184) always calls gh label create --force before gh issue edit --add-label. needs-review also needs to be added to ALL_STATUS_LABELS (line 344) so it is cleaned up in subsequent state transitions — right now it will persist as a zombie label when a failed task is re-queued, retried, and eventually succeeds/closes.
🛠️ Proposed fix
In ensure_status_labels (line 21+), add:
gh label create "status:done" --repo "$repo_slug" --color "6F42C1" --description "Task is complete" --force 2>/dev/null || true
+ gh label create "needs-review" --repo "$repo_slug" --color "E4E669" --description "Failed task needs human review" --force 2>/dev/null || true
return 0In ALL_STATUS_LABELS (line 344):
-ALL_STATUS_LABELS="status:available,status:queued,status:claimed,status:in-review,status:blocked,status:verify-failed,status:needs-testing,status:done"
+ALL_STATUS_LABELS="status:available,status:queued,status:claimed,status:in-review,status:blocked,status:verify-failed,status:needs-testing,status:done,needs-review"🤖 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 460 - 468, The script
currently attempts to add the "needs-review" label with gh issue edit which will
silently fail if the label doesn't exist; update the label creation flow by
ensuring the "needs-review" label is created (use the existing pattern that
calls gh label create --force before gh issue edit, e.g. in ensure_status_labels
or the same place add_model_label is invoked) so the --add-label call cannot
fail, and add "needs-review" to the ALL_STATUS_LABELS array so it is cleaned up
during subsequent state transitions (reference ensure_status_labels and
ALL_STATUS_LABELS to locate where to add the create call and the new label
entry).
| # Don't return here - let the non-terminal state logic handle label updates | ||
| ;; |
There was a problem hiding this comment.
Misleading fall-through comment — non-terminal label logic is a no-op for failed.
The comment says "let the non-terminal state logic handle label updates", but state_to_status_label("failed") returns "" (line 333), so new_label is empty and both guarded blocks at lines 484–488 and 492–500 (if [[ -n "$new_label" ]]) are skipped entirely. The fall-through is harmless but the comment misrepresents the intent; it matches the blocked case only because blocked gets a non-empty label. Update the comment to reflect the actual behaviour.
✏️ Proposed fix
- # Don't return here - let the non-terminal state logic handle label updates
+ # Return early — failed is a terminal state; label and comment already applied above.
+ return 0Or, if intentional fall-through is desired for future flexibility, correct the comment:
- # Don't return here - let the non-terminal state logic handle label updates
+ # Fall through intentionally (new_label is empty for failed; no further label is applied)📝 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.
| # Don't return here - let the non-terminal state logic handle label updates | |
| ;; | |
| # Return early — failed is a terminal state; label and comment already applied above. | |
| return 0 | |
| ;; |
🤖 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 467 - 468, The comment
on the fall-through after the "failed" case is misleading because
state_to_status_label("failed") returns an empty string so the subsequent
non-terminal label logic is a no-op; update the comment near the fall-through to
state that the fall-through is intentional to let the "blocked" branch (which
yields a non-empty label) reuse the label-update logic and that "failed"
produces no label, or alternatively change the comment to say "fall-through to
shared label handling; no-op for failed (empty label)"; reference
state_to_status_label, the "failed" and "blocked" cases, and the guarded blocks
that check if [[ -n "$new_label" ]] to guide where to change the text.
Three bugs fixed in sync_issue_status_label(): 1. Failed tasks were auto-closed — now they post a failure comment and add needs-review label, keeping the issue OPEN for human triage. 2. Verified/deployed tasks were auto-closed even without a merged PR — now they verify the PR state is MERGED before closing. If no merged PR exists, the issue is flagged for human review instead. 3. needs-review label was not in ensure_status_labels() or ALL_STATUS_LABELS — now it is created on-demand and cleaned up on subsequent state transitions. Reopened 5 incorrectly auto-closed issues: #2106, #1998, #1764, #1763, #1762. Supersedes PR #2112 (which only fixed the failed-state bug).
|
Superseded by PR #2115 which includes this fix plus two additional fixes:
|
* fix: never auto-close issues without merged PR evidence Three bugs fixed in sync_issue_status_label(): 1. Failed tasks were auto-closed — now they post a failure comment and add needs-review label, keeping the issue OPEN for human triage. 2. Verified/deployed tasks were auto-closed even without a merged PR — now they verify the PR state is MERGED before closing. If no merged PR exists, the issue is flagged for human review instead. 3. needs-review label was not in ensure_status_labels() or ALL_STATUS_LABELS — now it is created on-demand and cleaned up on subsequent state transitions. Reopened 5 incorrectly auto-closed issues: #2106, #1998, #1764, #1763, #1762. Supersedes PR #2112 (which only fixed the failed-state bug). * fix: address review feedback — grep false positive and failed-state reopen 1. Replace grep -qi 'MERGED' with dedicated gh pr view --json state query. The old pattern matched the field label 'merged:' in the jq output, causing ALL PRs to be treated as merged (false positive). 2. Add explicit reopen logic for failed state. The fall-through was a no-op because state_to_status_label('failed') returns empty, so the reopen block gated on new_label never fired. Now explicitly checks if issue is closed and reopens it for human review. * fix: remove 2>/dev/null from gh pr view calls to surface auth/API errors Addresses Gemini review feedback: blanket stderr suppression hides authentication failures, API rate limits, and network issues. The || echo "" fallback still handles expected errors (non-existent PR). * fix: needs-review label was simultaneously added and removed CodeRabbit found that needs-review in ALL_STATUS_LABELS caused the remove_args loop to include --remove-label needs-review, negating the --add-label needs-review in the same gh issue edit call. Fix: remove needs-review from ALL_STATUS_LABELS (it's a triage flag, not a status label). Also merge redundant dual gh pr view calls into one API request, and use distinct label color (E99695) to avoid duplicating status:claimed color.



Summary
needs-reviewlabel is added, keeping the issue open for human triage. Fixes t1300: Investigate t1165.1 repeated permission_denied_write_operations failures #2106, t1165.1: Design container image and OAuth token provisioning #1763, t1165: Containerized Claude Code CLI instances for multi-subscription scaling #1762.parse_task_line()now strips metadata fields individually (tags, estimates, dates, assignees, refs) instead of truncating at the first#tag, preserving em-dash (—) content in issue descriptions.Testing
parse_task_line()tested against 8 edge cases (no em-dash, with em-dash, multiple em-dashes, subtasks, completed tasks, etc.) — all pass.needs-reviewlabel.Files Changed
.agents/scripts/issue-sync-lib.sh.agents/scripts/supervisor/issue-sync.shgh issue closewith comment +needs-reviewlabel for failed tasksSummary by CodeRabbit