fix(spoolman): remove SPOOLMAN_CORS_ORIGIN=* wildcard #7322
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Squad Heartbeat (Ralph) | |
| # ⚠️ SYNC: This workflow is maintained in 4 locations. Changes must be applied to all: | |
| # - templates/workflows/squad-heartbeat.yml (source template) | |
| # - packages/squad-cli/templates/workflows/squad-heartbeat.yml (CLI package) | |
| # - .squad/templates/workflows/squad-heartbeat.yml (installed template) | |
| # - .github/workflows/squad-heartbeat.yml (active workflow) | |
| # Run 'squad upgrade' to sync installed copies from source templates. | |
| on: | |
| # React to completed work or new squad work | |
| issues: | |
| types: [closed, labeled] | |
| pull_request: | |
| types: [closed] | |
| # Manual trigger | |
| workflow_dispatch: | |
| permissions: | |
| issues: write | |
| contents: read | |
| pull-requests: read | |
| jobs: | |
| heartbeat: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| decisions: ${{ steps.triage-results.outputs.decisions }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Check triage script | |
| id: check-script | |
| run: | | |
| if [ -f ".squad/templates/ralph-triage.js" ]; then | |
| echo "has_script=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_script=false" >> $GITHUB_OUTPUT | |
| echo "⚠️ ralph-triage.js not found — run 'squad upgrade' to install" | |
| fi | |
| - name: Ralph — Smart triage | |
| if: steps.check-script.outputs.has_script == 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| node .squad/templates/ralph-triage.js \ | |
| --squad-dir .squad \ | |
| --output triage-results.json | |
| - name: Ralph — Publish triage decisions | |
| id: triage-results | |
| if: steps.check-script.outputs.has_script == 'true' | |
| shell: bash | |
| run: | | |
| if [ -f triage-results.json ]; then | |
| echo "decisions=$(jq -c . triage-results.json)" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "decisions=[]" >> "$GITHUB_OUTPUT" | |
| fi | |
| # Copilot auto-assign step (uses PAT if available) | |
| - name: Ralph — Assign @copilot issues | |
| if: success() | |
| uses: actions/github-script@v9 | |
| with: | |
| github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| const teamFile = '.squad/team.md'; | |
| if (!fs.existsSync(teamFile)) return; | |
| const content = fs.readFileSync(teamFile, 'utf8'); | |
| // Check if @copilot is on the team with auto-assign | |
| const hasCopilot = content.includes('🤖 Coding Agent') || content.includes('@copilot'); | |
| const autoAssign = content.includes('<!-- copilot-auto-assign: true -->'); | |
| if (!hasCopilot || !autoAssign) return; | |
| // Find issues labeled squad:copilot with no assignee | |
| try { | |
| const { data: copilotIssues } = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| labels: 'squad:copilot', | |
| state: 'open', | |
| per_page: 5 | |
| }); | |
| const unassigned = copilotIssues.filter(i => | |
| !i.assignees || i.assignees.length === 0 | |
| ); | |
| if (unassigned.length === 0) { | |
| core.info('No unassigned squad:copilot issues'); | |
| return; | |
| } | |
| // Get repo default branch | |
| const { data: repoData } = await github.rest.repos.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo | |
| }); | |
| for (const issue of unassigned) { | |
| try { | |
| await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| assignees: ['copilot-swe-agent[bot]'], | |
| agent_assignment: { | |
| target_repo: `${context.repo.owner}/${context.repo.repo}`, | |
| base_branch: repoData.default_branch, | |
| custom_instructions: `Read .squad/team.md for team context and .squad/routing.md for routing rules.` | |
| } | |
| }); | |
| core.info(`Assigned copilot-swe-agent[bot] to #${issue.number}`); | |
| } catch (e) { | |
| core.warning(`Failed to assign @copilot to #${issue.number}: ${e.message}`); | |
| } | |
| } | |
| } catch (e) { | |
| core.info(`No squad:copilot label found or error: ${e.message}`); | |
| } | |
| apply-triage: | |
| needs: heartbeat | |
| if: needs.heartbeat.outputs.decisions != '' && needs.heartbeat.outputs.decisions != '[]' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| decision: ${{ fromJSON(needs.heartbeat.outputs.decisions) }} | |
| concurrency: | |
| group: squad-triage-${{ matrix.decision.issueNumber }} | |
| cancel-in-progress: false | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Ralph — Apply triage decision | |
| uses: actions/github-script@v9 | |
| env: | |
| TRIAGE_DECISION: ${{ toJSON(matrix.decision) }} | |
| with: | |
| script: | | |
| const path = require('path'); | |
| const { canonicalOwnerLabels, ownerLabelsToRemove } = require( | |
| path.join(process.env.GITHUB_WORKSPACE, 'scripts/ci/squad-routing.cjs') | |
| ); | |
| const decision = JSON.parse(process.env.TRIAGE_DECISION); | |
| const { data: currentIssue } = await github.rest.issues.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: decision.issueNumber | |
| }); | |
| if (canonicalOwnerLabels(currentIssue.labels).length > 0) { | |
| core.info(`Issue #${decision.issueNumber} already has an owner — skipping`); | |
| return; | |
| } | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: decision.issueNumber, | |
| labels: [decision.label] | |
| }); | |
| for (const label of ownerLabelsToRemove(currentIssue.labels, decision.label)) { | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: decision.issueNumber, | |
| name: label | |
| }); | |
| } catch (error) { | |
| if (error.status !== 404) { | |
| throw error; | |
| } | |
| core.info(`Owner label already removed: ${label}`); | |
| } | |
| } | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: decision.issueNumber, | |
| body: [ | |
| '### 🔄 Ralph — Auto-Triage', | |
| '', | |
| `**Assigned to:** ${decision.assignTo}`, | |
| `**Reason:** ${decision.reason}`, | |
| `**Source:** ${decision.source}`, | |
| '', | |
| '> Ralph auto-triaged this issue using routing rules.', | |
| '> To reassign, swap the `squad:*` label.' | |
| ].join('\n') | |
| }); | |
| core.info(`Triaged #${decision.issueNumber} → ${decision.assignTo} (${decision.source})`); |