From bdfaa978ab7354d2e6ecf19c5d9921a1d77f31ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isaac=20Rold=C3=A1n?= Date: Tue, 27 Oct 2026 12:04:44 +0100 Subject: [PATCH 1/3] Remove unused gardener investigate workflow The workflow has never run, and the devtools-investigate-for-gardener label has never been used here. Co-authored-by: Claude Code --- .../workflows/gardener-investigate-issue.yml | 226 ------------------ 1 file changed, 226 deletions(-) delete mode 100644 .github/workflows/gardener-investigate-issue.yml diff --git a/.github/workflows/gardener-investigate-issue.yml b/.github/workflows/gardener-investigate-issue.yml deleted file mode 100644 index 65f242a3..00000000 --- a/.github/workflows/gardener-investigate-issue.yml +++ /dev/null @@ -1,226 +0,0 @@ -name: Gardener - Investigate Issue -# Automatically investigates GitHub issues using Claude Code when the -# 'devtools-investigate-for-gardener' label is applied. Can also be triggered manually -# via workflow_dispatch for a specific issue number. -on: - issues: - types: [labeled] - workflow_dispatch: - inputs: - issue_number: - description: 'Issue number to investigate' - required: true - type: number - -permissions: - contents: read - issues: read - pull-requests: read - checks: read - -jobs: - investigate: - if: >- - github.event_name == 'workflow_dispatch' || - github.event.label.name == 'devtools-investigate-for-gardener' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Resolve issue number - id: issue - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - NUMBER="${{ github.event.inputs.issue_number }}" - else - NUMBER="${{ github.event.issue.number }}" - fi - echo "number=$NUMBER" >> "$GITHUB_OUTPUT" - echo "url=https://github.com/${{ github.repository }}/issues/$NUMBER" >> "$GITHUB_OUTPUT" - - # Post a starter message so reviewers can follow along while Claude works. - # `continue-on-error: true` keeps a Slack outage from blocking the run. - # The response `ts` is stashed for the completion step to thread onto. - - name: Post investigation start to Slack - id: start_slack - continue-on-error: true - uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 - with: - method: chat.postMessage - token: ${{ secrets.SLACK_GARDENER_BOT_TOKEN }} - payload: |- - { - "channel": "${{ vars.GARDENER_SLACK_CHANNEL_ID }}", - "text": "Investigation started for issue #${{ steps.issue.outputs.number }}", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": ":mag: *<${{ steps.issue.outputs.url }}|Issue #${{ steps.issue.outputs.number }}>* — investigation starting…\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>" - } - } - ] - } - - - name: Investigate issue - id: investigate - timeout-minutes: 30 - uses: anthropics/claude-code-action@b47fd721da662d48c5680e154ad16a73ed74d2e0 # v1 - env: - ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }} - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - github_token: ${{ secrets.GITHUB_TOKEN }} - allowed_tools: "Bash(gh issue view *),Bash(gh issue list *),Bash(gh pr list *),Bash(gh pr view *),Bash(gh pr checks *),Bash(gh pr diff *),Bash(gh release list *),Bash(git log *),Bash(git tag -l*),Bash(git show *),Read,Glob,Grep" - prompt: | - This is a GitHub Actions report-only investigation run. - - /investigating-github-issues ${{ steps.issue.outputs.url }} - - If the skill above did not load, read and follow `.claude/skills/investigating-github-issues/SKILL.md`. - - Do not modify files, create branches, commit, push, or open pull requests. - Always return an investigation report as the `report` field in your structured output. - If you identify a straightforward fix, describe the proposed fix in the report instead of implementing it. - claude_args: | - --json-schema '{"type":"object","properties":{"report":{"type":"string","description":"The full investigation report markdown"}},"required":["report"]}' - - - name: Write report to job summary - if: always() && steps.investigate.outputs.structured_output - env: - STRUCTURED_OUTPUT: ${{ steps.investigate.outputs.structured_output }} - run: | - echo "$STRUCTURED_OUTPUT" | jq -r '.report' >> "$GITHUB_STEP_SUMMARY" - - # Build a single Slack payload — success shape when Claude returned - # structured output, failure shape otherwise (crash, timeout, cancel, - # or no structured_output). Running in github-script so we can parse - # the starter response to thread onto it, and use JSON.stringify to - # dodge shell-escaping hazards. The report is Claude's trusted - # structured output, so no HTML-escape pass. - - name: Prepare Slack payload - id: slack_payload - if: always() - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - env: - STRUCTURED_OUTPUT: ${{ steps.investigate.outputs.structured_output }} - START_RESPONSE: ${{ steps.start_slack.outputs.response }} - CHANNEL_ID: ${{ vars.GARDENER_SLACK_CHANNEL_ID }} - ISSUE_NUMBER: ${{ steps.issue.outputs.number }} - ISSUE_URL: ${{ steps.issue.outputs.url }} - RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - INVESTIGATE_OUTCOME: ${{ steps.investigate.outcome }} - with: - result-encoding: string - script: | - const num = process.env.ISSUE_NUMBER; - const url = process.env.ISSUE_URL; - const runUrl = process.env.RUN_URL; - - // Thread onto the starter post when it succeeded; otherwise - // the result still posts standalone to the channel. - let threadTs = null; - try { - const r = JSON.parse(process.env.START_RESPONSE || '{}'); - if (r.ok && r.ts) threadTs = r.ts; - } catch (_) {} - - let text, blocks; - - // Gate on STRUCTURED_OUTPUT (not report content) so the - // empty-report edge case still goes through the success path, - // matching the previous two-step behavior. Wrapped in try/catch - // so a malformed payload falls through to the failure notice - // instead of leaving the starter message orphaned. - let builtSuccess = false; - if (process.env.STRUCTURED_OUTPUT) { - try { - const structured = JSON.parse(process.env.STRUCTURED_OUTPUT); - const report = structured.report || ''; - - // Top-sections slice: keep everything up to and including - // the Summary section, drop sections that follow it. Falls - // back to the full report if the template no longer contains - // "## Summary". - const lines = report.split('\n'); - const slice = []; - let sawSummary = false; - for (const line of lines) { - if (sawSummary && /^## /.test(line)) break; - slice.push(line); - if (/^## Summary/.test(line)) sawSummary = true; - } - // Stash fenced code blocks so their contents don't get - // rewritten by the header/bullet passes below. - const codeBlocks = []; - let slackReport = (slice.join('\n').trim() || report) - .replace(/^```[^\n]*\n([\s\S]*?)\n```$/gm, (_m, c) => { - codeBlocks.push(c); - return `\x04${codeBlocks.length - 1}\x05`; - }) - .replace(/^#{1,6}\s+(.+)$/gm, '*$1*') - .replace(/\*\*(.+?)\*\*/g, '*$1*') - .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<$2|$1>') - .replace(/^(\s*)- \[x\]\s+/gm, '$1✓ ') - .replace(/^(\s*)[-*]\s+/gm, '$1• ') - .replace(/\x04(\d+)\x05/g, (_m, i) => '```\n' + codeBlocks[+i] + '\n```'); - - // Slack section blocks cap at 3000 chars; leave headroom for the footer. - const footer = `\n\n<${runUrl}|View full report>`; - const MAX = 2900; - if (slackReport.length + footer.length > MAX) { - slackReport = slackReport.slice(0, MAX - footer.length - 1) + '…'; - } - slackReport += footer; - - text = `Investigation report for issue #${num}`; - blocks = [ - { type: 'section', - text: { type: 'mrkdwn', - text: `*<${url}|Issue #${num}>* — Investigation Report` } }, - { type: 'divider' }, - { type: 'section', - text: { type: 'mrkdwn', text: slackReport } } - ]; - builtSuccess = true; - } catch (e) { - core.warning(`Failed to build success payload: ${e}; posting failure notice`); - } - } - - if (!builtSuccess) { - const outcome = process.env.INVESTIGATE_OUTCOME; - // Outcome = 'success' + no structured output means Claude - // returned without a structured report — distinct from an - // outright failure. - const reason = outcome === 'success' - ? 'completed without a report' - : `${outcome || 'did not complete'}`; - text = `Investigation failed for issue #${num}`; - blocks = [ - { type: 'section', - text: { type: 'mrkdwn', - text: `:x: *<${url}|Issue #${num}>* — investigation ${reason}. <${runUrl}|View run>` } } - ]; - } - - const payload = { channel: process.env.CHANNEL_ID, text, blocks }; - if (threadTs) { - payload.thread_ts = threadTs; - // Broadcasts the threaded reply back to the channel so the - // summary shows up inline, not only for thread subscribers. - payload.reply_broadcast = true; - } - return JSON.stringify(payload); - - - name: Post to Slack - if: always() && steps.slack_payload.outputs.result - uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 - with: - method: chat.postMessage - token: ${{ secrets.SLACK_GARDENER_BOT_TOKEN }} - payload: '${{ steps.slack_payload.outputs.result }}' From 24b4f340c3ce8b44395ec9fadc85a185876f1e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isaac=20Rold=C3=A1n?= Date: Tue, 27 Oct 2026 12:04:44 +0100 Subject: [PATCH 2/3] Replace blocked issues-helper action with actions/stale in the waiting-for-response closer GitHub blocked actions-cool/issues-helper on 2026-09-24, so the workflow has failed every day since 2026-09-25. Use the actions/stale setup the remix template has run since 2026-07-13, keeping the 14-day window and the closing message. Co-authored-by: Claude Code --- .../close-waiting-for-response-issues.yml | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/close-waiting-for-response-issues.yml b/.github/workflows/close-waiting-for-response-issues.yml index aab71708..87a66adb 100644 --- a/.github/workflows/close-waiting-for-response-issues.yml +++ b/.github/workflows/close-waiting-for-response-issues.yml @@ -3,18 +3,24 @@ on: schedule: - cron: "30 1 * * *" workflow_dispatch: + +permissions: + actions: write + issues: write + jobs: check-need-info: runs-on: ubuntu-latest steps: - - name: close-issues - uses: actions-cool/issues-helper@45d75b6cf72bf4f254be6230cb887ad002702491 # v3.6.3 + - name: Close inactive issues + uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0 with: - actions: 'close-issues' - token: ${{ secrets.GITHUB_TOKEN }} - labels: 'Waiting for Response' - inactive-day: 14 - body: | + days-before-stale: -1 + days-before-close: -1 + days-before-issue-close: 14 + stale-issue-label: "Waiting for Response" + remove-issue-stale-when-updated: false + close-issue-message: | We are closing this issue because we did not hear back regarding additional details we needed to resolve this issue. If the issue persists and you are able to provide the missing clarification we need, feel free to create a new issue with the additional information. We appreciate your understanding as we try to manage our number of open issues. From e1d746416449e9f1ff23883a313762afb167a4d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isaac=20Rold=C3=A1n?= Date: Fri, 30 Oct 2026 11:53:03 +0100 Subject: [PATCH 3/3] Remove gardener Slack notification and auto-labelling workflows Deletes gardener-notify-event.yml and gardener-notify-slack.yml, which labelled every new issue and PR as devtools-gardener and posted it to #devtools-gardener-backlog. Co-authored-by: Claude Code --- .github/workflows/gardener-notify-event.yml | 35 ---------- .github/workflows/gardener-notify-slack.yml | 75 --------------------- 2 files changed, 110 deletions(-) delete mode 100644 .github/workflows/gardener-notify-event.yml delete mode 100644 .github/workflows/gardener-notify-slack.yml diff --git a/.github/workflows/gardener-notify-event.yml b/.github/workflows/gardener-notify-event.yml deleted file mode 100644 index f03b719f..00000000 --- a/.github/workflows/gardener-notify-event.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Gardener - Notify Event -# Tiny event capturer: stashes the triggering issue/PR payload as an artifact -# for `gardener-notify-slack.yml` to pick up via workflow_run. -# -# Why two workflows? When Dependabot triggers a workflow, GitHub forces -# GITHUB_TOKEN to read-only and hides Actions secrets — so labeling and -# Slack posting from this workflow would fail on every Dependabot PR. A -# workflow_run-triggered follow-up runs in the default-branch context with -# full permissions and secret access, regardless of the upstream actor. -# -# Uses pull_request_target so fork-opened PRs still produce an artifact. -# No code is checked out here; this workflow only reads the pre-parsed -# event payload, so there is no pwn-request surface. -on: - issues: - types: [opened, labeled] - pull_request_target: - types: [opened, labeled] - -permissions: - contents: read - -jobs: - capture: - if: github.event.action == 'opened' || github.event.label.name == 'devtools-gardener' - runs-on: ubuntu-latest - steps: - - name: Stash event payload - run: cp "$GITHUB_EVENT_PATH" event.json - - - uses: actions/upload-artifact@v4 - with: - name: gardener-event - path: event.json - retention-days: 1 diff --git a/.github/workflows/gardener-notify-slack.yml b/.github/workflows/gardener-notify-slack.yml deleted file mode 100644 index d4241fa6..00000000 --- a/.github/workflows/gardener-notify-slack.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: Gardener - Notify Slack -# Runs after `Gardener - Notify Event` completes and does the real work: -# applies the devtools-gardener label and posts a repo-prefixed linked title to Slack. -# -# The workflow_run trigger runs this job in the default-branch context with -# full GITHUB_TOKEN permissions and Actions secret access — this is what -# lets it succeed for Dependabot-opened PRs, where the upstream event -# workflow can't label or reach secrets directly. -on: - workflow_run: - workflows: ['Gardener - Notify Event'] - types: [completed] - -permissions: - contents: read - issues: write - pull-requests: write - actions: read - -jobs: - notify: - # `conclusion == success` also covers runs where the capture job was - # skipped by its `if` gate (no matching label, etc.) — in that case - # no artifact was uploaded, so the download step below no-ops. - if: github.event.workflow_run.conclusion == 'success' - runs-on: ubuntu-latest - steps: - - name: Download event payload - id: download - continue-on-error: true - uses: actions/download-artifact@v4 - with: - name: gardener-event - run-id: ${{ github.event.workflow_run.id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Add devtools-gardener label - if: steps.download.outcome == 'success' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - run: | - ACTION=$(jq -r '.action' event.json) - # On `labeled` events the label is already there — skip. - if [ "$ACTION" != "opened" ]; then - exit 0 - fi - NUMBER=$(jq -r '(.issue // .pull_request).number' event.json) - if jq -e 'has("pull_request")' event.json > /dev/null; then - gh pr edit "$NUMBER" --add-label devtools-gardener - else - gh issue edit "$NUMBER" --add-label devtools-gardener - fi - - - name: Post to Slack - if: steps.download.outcome == 'success' - continue-on-error: true - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_GARDENER_BOT_TOKEN }} - SLACK_CHANNEL_ID: ${{ vars.GARDENER_SLACK_CHANNEL_ID }} - run: | - jq \ - --arg channel "$SLACK_CHANNEL_ID" \ - ' - def escape: gsub("&";"&") | gsub("<";"<") | gsub(">";">"); - - (.issue // .pull_request) as $i - | { channel: $channel, - text: "\(.repository.name) · <\($i.html_url)|\($i.title | escape)>", - unfurl_links: false, - unfurl_media: false } - ' event.json | curl -sf -X POST \ - -H "Authorization: Bearer $SLACK_BOT_TOKEN" \ - -H 'Content-type: application/json; charset=utf-8' \ - -d @- https://slack.com/api/chat.postMessage