From f3557aca97f80356e07d7710ee6bc79951884593 Mon Sep 17 00:00:00 2001 From: Hardik Date: Fri, 19 Jun 2026 03:13:09 +0530 Subject: [PATCH] fix(automation): feed issue comments to Claude; fix PS 5.1 array-unroll bug - Get-IssueCommentsBlock includes human issue comments in Claude's prompt so scope/repro added as comments is acted on (requested for delivery-dropdown #19) - Critical: capture Api responses to a variable before filtering. Piping the Api function's array output straight into Where-Object collapses all issues into one object in PS 5.1, so the watcher tried to process #12/#8/#7 at once - Bot status comments now carry an ASCII marker and are filtered out (incl. legacy emoji comments via stable ASCII phrase match) so they are never fed back as human input; script kept ASCII-only for ANSI load - Harden numeric sort/select on issue numbers Co-Authored-By: Claude Opus 4.8 --- automation/claude-issue-watcher.ps1 | 52 +++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/automation/claude-issue-watcher.ps1 b/automation/claude-issue-watcher.ps1 index b95293b..3800d13 100644 --- a/automation/claude-issue-watcher.ps1 +++ b/automation/claude-issue-watcher.ps1 @@ -73,6 +73,34 @@ function Add-IssueComment([int]$IssueNumber, [string]$Text) { Api POST "/repos/$($cfg.repo)/issues/$IssueNumber/comments" @{ body = $Text } | Out-Null } +# Hidden ASCII marker on every comment the watcher posts, so human comments can +# be told apart from bot status comments regardless of who the API token posts as. +# Kept ASCII-only: PS 5.1 reads BOM-less scripts as ANSI, so non-ASCII here is unsafe. +$BotMarker = '' + +# Identifies the watcher's own status comments so they are not fed back to Claude +# as if they were human input. New comments carry the ppms-bot marker; legacy ones +# (posted before the marker existed) are matched by their stable ASCII phrases — +# the emoji they used got mojibake-mangled in storage, so it is unmatchable. +# Bot posts under the same account as humans, so we match on content, not author. +$BotCommentPattern = 'ppms-bot|has started working on this issue|Claude opened PR \[#|Automated fix attempt did not produce' + +# Fetch human comments on an issue as a markdown block for Claude's prompt. +function Get-IssueCommentsBlock([int]$IssueNumber) { + # Capture before wrapping: @(Api ...) alone collapses a multi-comment array + # into a single object in PS 5.1 (same quirk as the queued-issues fetch). + $resp = Api GET "/repos/$($cfg.repo)/issues/$IssueNumber/comments?limit=50" + $human = @(@($resp) | Where-Object { + $_.body -and ($_.body -notmatch $BotCommentPattern) + }) + if ($human.Count -eq 0) { return "" } + $lines = foreach ($c in $human) { + $who = $c.user.login + "**$who commented:**`n$($c.body)`n" + } + return "## Comments on the issue (read these -- they refine the scope/repro)`n`n" + ($lines -join "`n") +} + # Run git without tripping ErrorActionPreference=Stop on stderr output # (native stderr lines become ErrorRecords in PS 5.1). Returns exit code. function Run-Git([string[]]$GitArgs) { @@ -87,12 +115,16 @@ function Run-Git([string[]]$GitArgs) { } # ── Find queued issues ────────────────────────────────────────────── -$queued = @(Api GET "/repos/$($cfg.repo)/issues?state=open&labels=claude-queue&type=issues&limit=20" | Where-Object { $_ -and $_.number }) +# NB: capture the API result into a variable before filtering. Piping the Api +# function's output straight into Where-Object does NOT unroll the array in +# PS 5.1 — it collapses all issues into one object whose props are arrays. +$queuedResp = Api GET "/repos/$($cfg.repo)/issues?state=open&labels=claude-queue&type=issues&limit=20" +$queued = @(@($queuedResp) | Where-Object { $_ -and $_.number }) if ($queued.Count -eq 0) { Log "No queued issues." exit 0 } -$queued = @($queued | Sort-Object number | Select-Object -First $cfg.maxIssuesPerRun) +$queued = @($queued | Sort-Object { [int]$_.number } | Select-Object -First ([int]$cfg.maxIssuesPerRun)) Log "Found $($queued.Count) queued issue(s): $(($queued | ForEach-Object { '#' + $_.number }) -join ', ')" # ── Prepare the dedicated work clone ──────────────────────────────── @@ -113,22 +145,30 @@ foreach ($issue in $queued) { Log "-- Working issue #${n}: $($issue.title)" Set-IssueLabels $n -Remove @('claude-queue', 'claude-failed') -Add @('claude-working') - Add-IssueComment $n "🤖 Claude has started working on this issue on branch ``$branch``." + Add-IssueComment $n "$BotMarker`n[Claude] Started working on this issue on branch ``$branch``." Run-Git @('-C', $cfg.workDir, 'fetch', 'origin') | Out-Null if ((Run-Git @('-C', $cfg.workDir, 'checkout', '-B', $branch, "origin/$($cfg.baseBranch)")) -ne 0) { Log "checkout failed for #$n"; continue } + $commentsBlock = Get-IssueCommentsBlock $n + if ($commentsBlock) { + $cCount = ([regex]::Matches($commentsBlock, 'commented:\*\*')).Count + Log "Including $cCount human comment(s) for #$n" + } + $prompt = @" You are working autonomously on issue #$n of the Pelagia Portal (PPMS), a Next.js 15 purchase-order -management system for a maritime company. The web app lives in the App/ directory — read App/CLAUDE.md +management system for a maritime company. The web app lives in the App/ directory -- read App/CLAUDE.md first for architecture, conventions, and commands. ## Issue #${n}: $($issue.title) $($issue.body) +$commentsBlock + ## Your job 1. Investigate the issue and implement a focused, minimal fix in this repository. @@ -182,13 +222,13 @@ explanation to a file named CLAUDE_RESULT.md in the repository root (it will be body = "Automated fix by Claude Code for #$n.`n`nCloses #$n`n`nReview, merge, then create a release tag (vX.Y.Z) to deploy." } Set-IssueLabels $n -Remove @('claude-working') -Add @('claude-pr') - Add-IssueComment $n "🤖 Claude opened PR [#$($pr.number)]($($pr.html_url)) with a proposed fix. Review and merge it, then create a release tag to deploy." + Add-IssueComment $n "$BotMarker`n[Claude] Opened PR [#$($pr.number)]($($pr.html_url)) with a proposed fix. Review and merge it, then create a release tag to deploy." Log "PR #$($pr.number) opened for issue #$n" } else { Log "No commits produced for #$n; marking claude-failed" Set-IssueLabels $n -Remove @('claude-working') -Add @('claude-failed') $reason = if ($abortNote) { $abortNote } else { "Claude did not produce a verified fix. See watcher logs on the dev machine: $claudeLog" } - Add-IssueComment $n "🤖 Automated fix attempt did not produce a change.`n`n$reason" + Add-IssueComment $n "$BotMarker`n[Claude] Automated fix attempt did not produce a change.`n`n$reason" } }