anomalyco/opencode

32 workflows · maturity 67% · 11 patterns · GitHub ↗

Security 10.16/100

Security dimensions

permissions
10.2
security scan
0
supply chain
0
secret handling
0
harden runner
0

Workflows (32)

beta .github/workflows/beta.yml
Triggers
workflow_dispatch, schedule
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
sync
Commands
  • bun i -g opencode-ai
  • bun script/beta.ts
View raw YAML
name: beta

on:
  workflow_dispatch:
  schedule:
    - cron: "0 * * * *"

jobs:
  sync:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      contents: write
      pull-requests: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Bun
        uses: ./.github/actions/setup-bun

      - name: Setup Git Committer
        id: setup-git-committer
        uses: ./.github/actions/setup-git-committer
        with:
          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}

      - name: Install OpenCode
        run: bun i -g opencode-ai

      - name: Sync beta branch
        env:
          GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
        run: bun script/beta.ts
close-issues .github/workflows/close-issues.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
close
Actions
oven-sh/setup-bun
Commands
  • bun script/github/close-issues.ts
View raw YAML
name: close-issues

on:
  schedule:
    - cron: "0 2 * * *" # Daily at 2:00 AM
  workflow_dispatch:

jobs:
  close:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      issues: write
    steps:
      - uses: actions/checkout@v4

      - uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest

      - name: Close stale issues
        env:
          GITHUB_TOKEN: ${{ github.token }}
        run: bun script/github/close-issues.ts
close-stale-prs perms .github/workflows/close-stale-prs.yml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-latest
Jobs
close-stale-prs
View raw YAML
name: close-stale-prs

on:
  workflow_dispatch:
    inputs:
      dryRun:
        description: "Log actions without closing PRs"
        type: boolean
        default: false
  schedule:
    - cron: "0 6 * * *"

permissions:
  contents: read
  issues: write
  pull-requests: write

jobs:
  close-stale-prs:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - name: Close inactive PRs
        uses: actions/github-script@v8
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const DAYS_INACTIVE = 60
            const MAX_RETRIES = 3

            // Adaptive delay: fast for small batches, slower for large to respect
            // GitHub's 80 content-generating requests/minute limit
            const SMALL_BATCH_THRESHOLD = 10
            const SMALL_BATCH_DELAY_MS = 1000  // 1s for daily operations (≤10 PRs)
            const LARGE_BATCH_DELAY_MS = 2000  // 2s for backlog (>10 PRs) = ~30 ops/min, well under 80 limit

            const startTime = Date.now()
            const cutoff = new Date(Date.now() - DAYS_INACTIVE * 24 * 60 * 60 * 1000)
            const { owner, repo } = context.repo
            const dryRun = context.payload.inputs?.dryRun === "true"

            core.info(`Dry run mode: ${dryRun}`)
            core.info(`Cutoff date: ${cutoff.toISOString()}`)

            function sleep(ms) {
              return new Promise(resolve => setTimeout(resolve, ms))
            }

            async function withRetry(fn, description = 'API call') {
              let lastError
              for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
                try {
                  const result = await fn()
                  return result
                } catch (error) {
                  lastError = error
                  const isRateLimited = error.status === 403 &&
                    (error.message?.includes('rate limit') || error.message?.includes('secondary'))

                  if (!isRateLimited) {
                    throw error
                  }

                  // Parse retry-after header, default to 60 seconds
                  const retryAfter = error.response?.headers?.['retry-after']
                    ? parseInt(error.response.headers['retry-after'])
                    : 60

                  // Exponential backoff: retryAfter * 2^attempt
                  const backoffMs = retryAfter * 1000 * Math.pow(2, attempt)

                  core.warning(`${description}: Rate limited (attempt ${attempt + 1}/${MAX_RETRIES}). Waiting ${backoffMs / 1000}s before retry...`)

                  await sleep(backoffMs)
                }
              }
              core.error(`${description}: Max retries (${MAX_RETRIES}) exceeded`)
              throw lastError
            }

            const query = `
              query($owner: String!, $repo: String!, $cursor: String) {
                repository(owner: $owner, name: $repo) {
                  pullRequests(first: 100, states: OPEN, after: $cursor) {
                    pageInfo {
                      hasNextPage
                      endCursor
                    }
                    nodes {
                      number
                      title
                      author {
                        login
                      }
                      createdAt
                      commits(last: 1) {
                        nodes {
                          commit {
                            committedDate
                          }
                        }
                      }
                      comments(last: 1) {
                        nodes {
                          createdAt
                        }
                      }
                      reviews(last: 1) {
                        nodes {
                          createdAt
                        }
                      }
                    }
                  }
                }
              }
            `

            const allPrs = []
            let cursor = null
            let hasNextPage = true
            let pageCount = 0

            while (hasNextPage) {
              pageCount++
              core.info(`Fetching page ${pageCount} of open PRs...`)

              const result = await withRetry(
                () => github.graphql(query, { owner, repo, cursor }),
                `GraphQL page ${pageCount}`
              )

              allPrs.push(...result.repository.pullRequests.nodes)
              hasNextPage = result.repository.pullRequests.pageInfo.hasNextPage
              cursor = result.repository.pullRequests.pageInfo.endCursor

              core.info(`Page ${pageCount}: fetched ${result.repository.pullRequests.nodes.length} PRs (total: ${allPrs.length})`)

              // Delay between pagination requests (use small batch delay for reads)
              if (hasNextPage) {
                await sleep(SMALL_BATCH_DELAY_MS)
              }
            }

            core.info(`Found ${allPrs.length} open pull requests`)

            const stalePrs = allPrs.filter((pr) => {
              const dates = [
                new Date(pr.createdAt),
                pr.commits.nodes[0] ? new Date(pr.commits.nodes[0].commit.committedDate) : null,
                pr.comments.nodes[0] ? new Date(pr.comments.nodes[0].createdAt) : null,
                pr.reviews.nodes[0] ? new Date(pr.reviews.nodes[0].createdAt) : null,
              ].filter((d) => d !== null)

              const lastActivity = dates.sort((a, b) => b.getTime() - a.getTime())[0]

              if (!lastActivity || lastActivity > cutoff) {
                core.info(`PR #${pr.number} is fresh (last activity: ${lastActivity?.toISOString() || "unknown"})`)
                return false
              }

              core.info(`PR #${pr.number} is STALE (last activity: ${lastActivity.toISOString()})`)
              return true
            })

            if (!stalePrs.length) {
              core.info("No stale pull requests found.")
              return
            }

            core.info(`Found ${stalePrs.length} stale pull requests`)

            // ============================================
            // Close stale PRs
            // ============================================
            const requestDelayMs = stalePrs.length > SMALL_BATCH_THRESHOLD
              ? LARGE_BATCH_DELAY_MS
              : SMALL_BATCH_DELAY_MS

            core.info(`Using ${requestDelayMs}ms delay between operations (${stalePrs.length > SMALL_BATCH_THRESHOLD ? 'large' : 'small'} batch mode)`)

            let closedCount = 0
            let skippedCount = 0

            for (const pr of stalePrs) {
              const issue_number = pr.number
              const closeComment = `Closing this pull request because it has had no updates for more than ${DAYS_INACTIVE} days. If you plan to continue working on it, feel free to reopen or open a new PR.`

              if (dryRun) {
                core.info(`[dry-run] Would close PR #${issue_number} from ${pr.author?.login || 'unknown'}: ${pr.title}`)
                continue
              }

              try {
                // Add comment
                await withRetry(
                  () => github.rest.issues.createComment({
                    owner,
                    repo,
                    issue_number,
                    body: closeComment,
                  }),
                  `Comment on PR #${issue_number}`
                )

                // Close PR
                await withRetry(
                  () => github.rest.pulls.update({
                    owner,
                    repo,
                    pull_number: issue_number,
                    state: "closed",
                  }),
                  `Close PR #${issue_number}`
                )

                closedCount++
                core.info(`Closed PR #${issue_number} from ${pr.author?.login || 'unknown'}: ${pr.title}`)

                // Delay before processing next PR
                await sleep(requestDelayMs)
              } catch (error) {
                skippedCount++
                core.error(`Failed to close PR #${issue_number}: ${error.message}`)
              }
            }

            const elapsed = Math.round((Date.now() - startTime) / 1000)
            core.info(`\n========== Summary ==========`)
            core.info(`Total open PRs found: ${allPrs.length}`)
            core.info(`Stale PRs identified: ${stalePrs.length}`)
            core.info(`PRs closed: ${closedCount}`)
            core.info(`PRs skipped (errors): ${skippedCount}`)
            core.info(`Elapsed time: ${elapsed}s`)
            core.info(`=============================`)
compliance-close perms .github/workflows/compliance-close.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
close-non-compliant
View raw YAML
name: compliance-close

on:
  schedule:
    # Run every 30 minutes to check for expired compliance windows
    - cron: "*/30 * * * *"
  workflow_dispatch:

permissions:
  contents: read
  issues: write
  pull-requests: write

jobs:
  close-non-compliant:
    runs-on: ubuntu-latest
    steps:
      - name: Close non-compliant issues and PRs after 2 hours
        uses: actions/github-script@v7
        with:
          script: |
            const { data: items } = await github.rest.issues.listForRepo({
              owner: context.repo.owner,
              repo: context.repo.repo,
              labels: 'needs:compliance',
              state: 'open',
              per_page: 100,
            });

            if (items.length === 0) {
              core.info('No open issues/PRs with needs:compliance label');
              return;
            }

            const now = Date.now();
            const twoHours = 2 * 60 * 60 * 1000;

            for (const item of items) {
              const isPR = !!item.pull_request;
              const kind = isPR ? 'PR' : 'issue';

              const { data: comments } = await github.rest.issues.listComments({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: item.number,
              });

              const complianceComment = comments.find(c => c.body.includes('<!-- issue-compliance -->'));
              if (!complianceComment) continue;

              const commentAge = now - new Date(complianceComment.created_at).getTime();
              if (commentAge < twoHours) {
                core.info(`${kind} #${item.number} still within 2-hour window (${Math.round(commentAge / 60000)}m elapsed)`);
                continue;
              }

              const closeMessage = isPR
                ? 'This pull request has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) within the 2-hour window.\n\nFeel free to open a new pull request that follows our guidelines.'
                : 'This issue has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) within the 2-hour window.\n\nFeel free to open a new issue that follows our issue templates.';

              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: item.number,
                body: closeMessage,
              });

              try {
                await github.rest.issues.removeLabel({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: item.number,
                  name: 'needs:compliance',
                });
              } catch (e) {}

              if (isPR) {
                await github.rest.pulls.update({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  pull_number: item.number,
                  state: 'closed',
                });
              } else {
                await github.rest.issues.update({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: item.number,
                  state: 'closed',
                  state_reason: 'not_planned',
                });
              }

              core.info(`Closed non-compliant ${kind} #${item.number} after 2-hour window`);
            }
containers perms .github/workflows/containers.yml
Triggers
push, workflow_dispatch
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
build
Actions
docker/setup-qemu-action, docker/setup-buildx-action, docker/login-action
Commands
  • bun ./packages/containers/script/build.ts --push
View raw YAML
name: containers

on:
  push:
    branches:
      - dev
    paths:
      - packages/containers/**
      - .github/workflows/containers.yml
      - package.json
  workflow_dispatch:

permissions:
  contents: read
  packages: write

jobs:
  build:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    env:
      REGISTRY: ghcr.io/${{ github.repository_owner }}
      TAG: "24.04"
    steps:
      - uses: actions/checkout@v4

      - uses: ./.github/actions/setup-bun

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push containers
        run: bun ./packages/containers/script/build.ts --push
        env:
          REGISTRY: ${{ env.REGISTRY }}
          TAG: ${{ env.TAG }}
daily-issues-recap .github/workflows/daily-issues-recap.yml
Triggers
schedule, workflow_dispatch
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
daily-recap
Commands
  • curl -fsSL https://opencode.ai/install | bash
  • # Get today's date range TODAY=$(date -u +%Y-%m-%d) opencode run -m opencode/claude-sonnet-4-5 "Generate a daily issues recap for the OpenCode repository. TODAY'S DATE: ${TODAY} STEP 1: Gather today's issues Search for all OPEN issues created today (${TODAY}) using: gh issue list --repo ${{ github.repository }} --state open --search \"created:${TODAY}\" --json number,title,body,labels,state,comments,createdAt,author --limit 500 IMPORTANT: EXCLUDE all issues authored by Anomaly team members. Filter out issues where the author login matches ANY of these: adamdotdevin, Brendonovich, fwang, Hona, iamdavidhill, jayair, kitlangton, kommander, MrMushrooooom, R44VC0RP, rekram1-node, thdxr This recap is specifically for COMMUNITY (external) issues only. STEP 2: Analyze and categorize For each issue created today, categorize it: **Severity Assessment:** - CRITICAL: Crashes, data loss, security issues, blocks major functionality - HIGH: Significant bugs affecting many users, important features broken - MEDIUM: Bugs with workarounds, minor features broken - LOW: Minor issues, cosmetic, nice-to-haves **Activity Assessment:** - Note issues with high comment counts or engagement - Note issues from repeat reporters (check if author has filed before) STEP 3: Cross-reference with existing issues For issues that seem like feature requests or recurring bugs: - Search for similar older issues to identify patterns - Note if this is a frequently requested feature - Identify any issues that are duplicates of long-standing requests STEP 4: Generate the recap Create a structured recap with these sections: ===DISCORD_START=== **Daily Issues Recap - ${TODAY}** **Summary Stats** - Total issues opened today: [count] - By category: [bugs/features/questions] **Critical/High Priority Issues** [List any CRITICAL or HIGH severity issues with brief descriptions and issue numbers] **Most Active/Discussed** [Issues with significant engagement or from active community members] **Trending Topics** [Patterns noticed - e.g., 'Multiple reports about X', 'Continued interest in Y feature'] **Duplicates & Related** [Issues that relate to existing open issues] ===DISCORD_END=== STEP 5: Format for Discord Format the recap as a Discord-compatible message: - Use Discord markdown (**, __, etc.) - BE EXTREMELY CONCISE - this is an EOD summary, not a detailed report - Use hyperlinked issue numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/issues/1234>) - Group related issues on single lines where possible - Add emoji sparingly for critical items only - HARD LIMIT: Keep under 1800 characters total - Skip sections that have nothing notable (e.g., if no critical issues, omit that section) - Prioritize signal over completeness - only surface what matters OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/recap_raw.txt # Extract only the Discord message between markers sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/recap_raw.txt | grep -v '===DISCORD' > /tmp/recap.txt echo "recap_file=/tmp/recap.txt" >> $GITHUB_OUTPUT
  • if [ -z "$DISCORD_WEBHOOK_URL" ]; then echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post" cat /tmp/recap.txt exit 0 fi # Read the recap RECAP_RAW=$(cat /tmp/recap.txt) RECAP_LENGTH=${#RECAP_RAW} echo "Recap length: ${RECAP_LENGTH} chars" # Function to post a message to Discord post_to_discord() { local msg="$1" local content=$(echo "$msg" | jq -Rs '.') curl -s -H "Content-Type: application/json" \ -X POST \ -d "{\"content\": ${content}}" \ "$DISCORD_WEBHOOK_URL" sleep 1 } # If under limit, send as single message if [ "$RECAP_LENGTH" -le 1950 ]; then post_to_discord "$RECAP_RAW" else echo "Splitting into multiple messages..." remaining="$RECAP_RAW" while [ ${#remaining} -gt 0 ]; do if [ ${#remaining} -le 1950 ]; then post_to_discord "$remaining" break else chunk="${remaining:0:1900}" last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1) if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then chunk="${remaining:0:$last_newline}" remaining="${remaining:$((last_newline+1))}" else chunk="${remaining:0:1900}" remaining="${remaining:1900}" fi post_to_discord "$chunk" fi done fi echo "Posted daily recap to Discord"
View raw YAML
name: daily-issues-recap

on:
  schedule:
    # Run at 6 PM EST (23:00 UTC, or 22:00 UTC during daylight saving)
    - cron: "0 23 * * *"
  workflow_dispatch: # Allow manual trigger for testing

jobs:
  daily-recap:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      contents: read
      issues: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - uses: ./.github/actions/setup-bun

      - name: Install opencode
        run: curl -fsSL https://opencode.ai/install | bash

      - name: Generate daily issues recap
        id: recap
        env:
          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OPENCODE_PERMISSION: |
            {
              "bash": {
                "*": "deny",
                "gh issue*": "allow",
                "gh search*": "allow"
              },
              "webfetch": "deny",
              "edit": "deny",
              "write": "deny"
            }
        run: |
          # Get today's date range
          TODAY=$(date -u +%Y-%m-%d)

          opencode run -m opencode/claude-sonnet-4-5 "Generate a daily issues recap for the OpenCode repository.

          TODAY'S DATE: ${TODAY}

          STEP 1: Gather today's issues
          Search for all OPEN issues created today (${TODAY}) using:
          gh issue list --repo ${{ github.repository }} --state open --search \"created:${TODAY}\" --json number,title,body,labels,state,comments,createdAt,author --limit 500

          IMPORTANT: EXCLUDE all issues authored by Anomaly team members. Filter out issues where the author login matches ANY of these:
          adamdotdevin, Brendonovich, fwang, Hona, iamdavidhill, jayair, kitlangton, kommander, MrMushrooooom, R44VC0RP, rekram1-node, thdxr
          This recap is specifically for COMMUNITY (external) issues only.

          STEP 2: Analyze and categorize
          For each issue created today, categorize it:

          **Severity Assessment:**
          - CRITICAL: Crashes, data loss, security issues, blocks major functionality
          - HIGH: Significant bugs affecting many users, important features broken
          - MEDIUM: Bugs with workarounds, minor features broken
          - LOW: Minor issues, cosmetic, nice-to-haves

          **Activity Assessment:**
          - Note issues with high comment counts or engagement
          - Note issues from repeat reporters (check if author has filed before)

          STEP 3: Cross-reference with existing issues
          For issues that seem like feature requests or recurring bugs:
          - Search for similar older issues to identify patterns
          - Note if this is a frequently requested feature
          - Identify any issues that are duplicates of long-standing requests

          STEP 4: Generate the recap
          Create a structured recap with these sections:

          ===DISCORD_START===
          **Daily Issues Recap - ${TODAY}**

          **Summary Stats**
          - Total issues opened today: [count]
          - By category: [bugs/features/questions]

          **Critical/High Priority Issues**
          [List any CRITICAL or HIGH severity issues with brief descriptions and issue numbers]

          **Most Active/Discussed**
          [Issues with significant engagement or from active community members]

          **Trending Topics**
          [Patterns noticed - e.g., 'Multiple reports about X', 'Continued interest in Y feature']

          **Duplicates & Related**
          [Issues that relate to existing open issues]
          ===DISCORD_END===

          STEP 5: Format for Discord
          Format the recap as a Discord-compatible message:
          - Use Discord markdown (**, __, etc.)
          - BE EXTREMELY CONCISE - this is an EOD summary, not a detailed report
          - Use hyperlinked issue numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/issues/1234>)
          - Group related issues on single lines where possible
          - Add emoji sparingly for critical items only
          - HARD LIMIT: Keep under 1800 characters total
          - Skip sections that have nothing notable (e.g., if no critical issues, omit that section)
          - Prioritize signal over completeness - only surface what matters

          OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/recap_raw.txt

          # Extract only the Discord message between markers
          sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/recap_raw.txt | grep -v '===DISCORD' > /tmp/recap.txt

          echo "recap_file=/tmp/recap.txt" >> $GITHUB_OUTPUT

      - name: Post to Discord
        env:
          DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
        run: |
          if [ -z "$DISCORD_WEBHOOK_URL" ]; then
            echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
            cat /tmp/recap.txt
            exit 0
          fi

          # Read the recap
          RECAP_RAW=$(cat /tmp/recap.txt)
          RECAP_LENGTH=${#RECAP_RAW}

          echo "Recap length: ${RECAP_LENGTH} chars"

          # Function to post a message to Discord
          post_to_discord() {
            local msg="$1"
            local content=$(echo "$msg" | jq -Rs '.')
            curl -s -H "Content-Type: application/json" \
                 -X POST \
                 -d "{\"content\": ${content}}" \
                 "$DISCORD_WEBHOOK_URL"
            sleep 1
          }

          # If under limit, send as single message
          if [ "$RECAP_LENGTH" -le 1950 ]; then
            post_to_discord "$RECAP_RAW"
          else
            echo "Splitting into multiple messages..."
            remaining="$RECAP_RAW"
            while [ ${#remaining} -gt 0 ]; do
              if [ ${#remaining} -le 1950 ]; then
                post_to_discord "$remaining"
                break
              else
                chunk="${remaining:0:1900}"
                last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
                if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
                  chunk="${remaining:0:$last_newline}"
                  remaining="${remaining:$((last_newline+1))}"
                else
                  chunk="${remaining:0:1900}"
                  remaining="${remaining:1900}"
                fi
                post_to_discord "$chunk"
              fi
            done
          fi

          echo "Posted daily recap to Discord"
daily-pr-recap .github/workflows/daily-pr-recap.yml
Triggers
schedule, workflow_dispatch
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
pr-recap
Commands
  • curl -fsSL https://opencode.ai/install | bash
  • TODAY=$(date -u +%Y-%m-%d) opencode run -m opencode/claude-sonnet-4-5 "Generate a daily PR activity recap for the OpenCode repository. TODAY'S DATE: ${TODAY} STEP 1: Gather PR data Run these commands to gather PR information. ONLY include OPEN PRs created or updated TODAY (${TODAY}): # Open PRs created today gh pr list --repo ${{ github.repository }} --state open --search \"created:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100 # Open PRs with activity today (updated today) gh pr list --repo ${{ github.repository }} --state open --search \"updated:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100 IMPORTANT: EXCLUDE all PRs authored by Anomaly team members. Filter out PRs where the author login matches ANY of these: adamdotdevin, Brendonovich, fwang, Hona, iamdavidhill, jayair, kitlangton, kommander, MrMushrooooom, R44VC0RP, rekram1-node, thdxr This recap is specifically for COMMUNITY (external) contributions only. STEP 2: For high-activity PRs, check comment counts For promising PRs, run: gh pr view [NUMBER] --repo ${{ github.repository }} --json comments --jq '[.comments[] | select(.author.login != \"copilot-pull-request-reviewer\" and .author.login != \"github-actions\")] | length' IMPORTANT: When counting comments/activity, EXCLUDE these bot accounts: - copilot-pull-request-reviewer - github-actions STEP 3: Identify what matters (ONLY from today's PRs) **Bug Fixes From Today:** - PRs with 'fix' or 'bug' in title created/updated today - Small bug fixes (< 100 lines changed) that are easy to review - Bug fixes from community contributors **High Activity Today:** - PRs with significant human comments today (excluding bots listed above) - PRs with back-and-forth discussion today **Quick Wins:** - Small PRs (< 50 lines) that are approved or nearly approved - PRs that just need a final review STEP 4: Generate the recap Create a structured recap: ===DISCORD_START=== **Daily PR Recap - ${TODAY}** **New PRs Today** [PRs opened today - group by type: bug fixes, features, etc.] **Active PRs Today** [PRs with activity/updates today - significant discussion] **Quick Wins** [Small PRs ready to merge] ===DISCORD_END=== STEP 5: Format for Discord - Use Discord markdown (**, __, etc.) - BE EXTREMELY CONCISE - surface what we might miss - Use hyperlinked PR numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/pull/1234>) - Include PR author: [#1234](<url>) (@author) - For bug fixes, add brief description of what it fixes - Show line count for quick wins: \"(+15/-3 lines)\" - HARD LIMIT: Keep under 1800 characters total - Skip empty sections - Focus on PRs that need human eyes OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/pr_recap_raw.txt # Extract only the Discord message between markers sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/pr_recap_raw.txt | grep -v '===DISCORD' > /tmp/pr_recap.txt echo "recap_file=/tmp/pr_recap.txt" >> $GITHUB_OUTPUT
  • if [ -z "$DISCORD_WEBHOOK_URL" ]; then echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post" cat /tmp/pr_recap.txt exit 0 fi # Read the recap RECAP_RAW=$(cat /tmp/pr_recap.txt) RECAP_LENGTH=${#RECAP_RAW} echo "Recap length: ${RECAP_LENGTH} chars" # Function to post a message to Discord post_to_discord() { local msg="$1" local content=$(echo "$msg" | jq -Rs '.') curl -s -H "Content-Type: application/json" \ -X POST \ -d "{\"content\": ${content}}" \ "$DISCORD_WEBHOOK_URL" sleep 1 } # If under limit, send as single message if [ "$RECAP_LENGTH" -le 1950 ]; then post_to_discord "$RECAP_RAW" else echo "Splitting into multiple messages..." remaining="$RECAP_RAW" while [ ${#remaining} -gt 0 ]; do if [ ${#remaining} -le 1950 ]; then post_to_discord "$remaining" break else chunk="${remaining:0:1900}" last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1) if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then chunk="${remaining:0:$last_newline}" remaining="${remaining:$((last_newline+1))}" else chunk="${remaining:0:1900}" remaining="${remaining:1900}" fi post_to_discord "$chunk" fi done fi echo "Posted daily PR recap to Discord"
View raw YAML
name: daily-pr-recap

on:
  schedule:
    # Run at 5pm EST (22:00 UTC, or 21:00 UTC during daylight saving)
    - cron: "0 22 * * *"
  workflow_dispatch: # Allow manual trigger for testing

jobs:
  pr-recap:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      contents: read
      pull-requests: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - uses: ./.github/actions/setup-bun

      - name: Install opencode
        run: curl -fsSL https://opencode.ai/install | bash

      - name: Generate daily PR recap
        id: recap
        env:
          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OPENCODE_PERMISSION: |
            {
              "bash": {
                "*": "deny",
                "gh pr*": "allow",
                "gh search*": "allow"
              },
              "webfetch": "deny",
              "edit": "deny",
              "write": "deny"
            }
        run: |
          TODAY=$(date -u +%Y-%m-%d)

          opencode run -m opencode/claude-sonnet-4-5 "Generate a daily PR activity recap for the OpenCode repository.

          TODAY'S DATE: ${TODAY}

          STEP 1: Gather PR data
          Run these commands to gather PR information. ONLY include OPEN PRs created or updated TODAY (${TODAY}):

          # Open PRs created today
          gh pr list --repo ${{ github.repository }} --state open --search \"created:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100

          # Open PRs with activity today (updated today)
          gh pr list --repo ${{ github.repository }} --state open --search \"updated:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100

          IMPORTANT: EXCLUDE all PRs authored by Anomaly team members. Filter out PRs where the author login matches ANY of these:
          adamdotdevin, Brendonovich, fwang, Hona, iamdavidhill, jayair, kitlangton, kommander, MrMushrooooom, R44VC0RP, rekram1-node, thdxr
          This recap is specifically for COMMUNITY (external) contributions only.



          STEP 2: For high-activity PRs, check comment counts
          For promising PRs, run:
          gh pr view [NUMBER] --repo ${{ github.repository }} --json comments --jq '[.comments[] | select(.author.login != \"copilot-pull-request-reviewer\" and .author.login != \"github-actions\")] | length'

          IMPORTANT: When counting comments/activity, EXCLUDE these bot accounts:
          - copilot-pull-request-reviewer
          - github-actions

          STEP 3: Identify what matters (ONLY from today's PRs)

          **Bug Fixes From Today:**
          - PRs with 'fix' or 'bug' in title created/updated today
          - Small bug fixes (< 100 lines changed) that are easy to review
          - Bug fixes from community contributors

          **High Activity Today:**
          - PRs with significant human comments today (excluding bots listed above)
          - PRs with back-and-forth discussion today

          **Quick Wins:**
          - Small PRs (< 50 lines) that are approved or nearly approved
          - PRs that just need a final review

          STEP 4: Generate the recap
          Create a structured recap:

          ===DISCORD_START===
          **Daily PR Recap - ${TODAY}**

          **New PRs Today**
          [PRs opened today - group by type: bug fixes, features, etc.]

          **Active PRs Today**
          [PRs with activity/updates today - significant discussion]

          **Quick Wins**
          [Small PRs ready to merge]
          ===DISCORD_END===

          STEP 5: Format for Discord
          - Use Discord markdown (**, __, etc.)
          - BE EXTREMELY CONCISE - surface what we might miss
          - Use hyperlinked PR numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/pull/1234>)
          - Include PR author: [#1234](<url>) (@author)
          - For bug fixes, add brief description of what it fixes
          - Show line count for quick wins: \"(+15/-3 lines)\"
          - HARD LIMIT: Keep under 1800 characters total
          - Skip empty sections
          - Focus on PRs that need human eyes

          OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/pr_recap_raw.txt

          # Extract only the Discord message between markers
          sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/pr_recap_raw.txt | grep -v '===DISCORD' > /tmp/pr_recap.txt

          echo "recap_file=/tmp/pr_recap.txt" >> $GITHUB_OUTPUT

      - name: Post to Discord
        env:
          DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
        run: |
          if [ -z "$DISCORD_WEBHOOK_URL" ]; then
            echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
            cat /tmp/pr_recap.txt
            exit 0
          fi

          # Read the recap
          RECAP_RAW=$(cat /tmp/pr_recap.txt)
          RECAP_LENGTH=${#RECAP_RAW}

          echo "Recap length: ${RECAP_LENGTH} chars"

          # Function to post a message to Discord
          post_to_discord() {
            local msg="$1"
            local content=$(echo "$msg" | jq -Rs '.')
            curl -s -H "Content-Type: application/json" \
                 -X POST \
                 -d "{\"content\": ${content}}" \
                 "$DISCORD_WEBHOOK_URL"
            sleep 1
          }

          # If under limit, send as single message
          if [ "$RECAP_LENGTH" -le 1950 ]; then
            post_to_discord "$RECAP_RAW"
          else
            echo "Splitting into multiple messages..."
            remaining="$RECAP_RAW"
            while [ ${#remaining} -gt 0 ]; do
              if [ ${#remaining} -le 1950 ]; then
                post_to_discord "$remaining"
                break
              else
                chunk="${remaining:0:1900}"
                last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
                if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
                  chunk="${remaining:0:$last_newline}"
                  remaining="${remaining:$((last_newline+1))}"
                else
                  chunk="${remaining:0:1900}"
                  remaining="${remaining:1900}"
                fi
                post_to_discord "$chunk"
              fi
            done
          fi

          echo "Posted daily PR recap to Discord"
deploy .github/workflows/deploy.yml
Triggers
push, workflow_dispatch
Runs on
ubuntu-latest
Jobs
deploy
Commands
  • sudo rm -f /usr/local/bin/pulumi-language-nodejs
  • bun sst deploy --stage=${{ github.ref_name }}
View raw YAML
name: deploy

on:
  push:
    branches:
      - dev
      - production
  workflow_dispatch:

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: ./.github/actions/setup-bun

      - uses: actions/setup-node@v4
        with:
          node-version: "24"

      # Workaround for Pulumi version conflict:
      # GitHub runners have Pulumi 3.212.0+ pre-installed, which removed the -root flag
      # from pulumi-language-nodejs (see https://github.com/pulumi/pulumi/pull/21065).
      # SST 3.17.x uses Pulumi SDK 3.210.0 which still passes -root, causing a conflict.
      # Removing the system language plugin forces SST to use its bundled compatible version.
      # TODO: Remove when sst supports Pulumi >3.210.0
      - name: Fix Pulumi version conflict
        run: sudo rm -f /usr/local/bin/pulumi-language-nodejs

      - run: bun sst deploy --stage=${{ github.ref_name }}
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
          PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
          STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }}
docs-locale-sync .github/workflows/docs-locale-sync.yml
Triggers
push
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
sync-locales
Commands
  • FILES=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- 'packages/web/src/content/docs/*.mdx' || true) if [ -z "$FILES" ]; then echo "has_changes=false" >> "$GITHUB_OUTPUT" echo "No English docs changed in push range" exit 0 fi echo "has_changes=true" >> "$GITHUB_OUTPUT" { echo "files<<EOF" echo "$FILES" echo "EOF" } >> "$GITHUB_OUTPUT"
  • curl -fsSL https://opencode.ai/install | bash
  • opencode run --agent docs --model opencode/gpt-5.3-codex <<'EOF' Update localized docs to match the latest English docs changes. Changed English doc files: <changed_english_docs> ${{ steps.changes.outputs.files }} </changed_english_docs> Requirements: 1. Update all relevant locale docs under packages/web/src/content/docs/<locale>/ so they reflect these English page changes. 2. You MUST use the Task tool for translation work and launch subagents with subagent_type `translator` (defined in .opencode/agent/translator.md). 3. Do not translate directly in the primary agent. Use translator subagent output as the source for locale text updates. 4. Run translator subagent Task calls in parallel whenever file/locale translation work is independent. 5. Use only the minimum tools needed for this task (read/glob, file edits, and translator Task). Do not use shell, web, search, or GitHub tools for translation work. 6. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update. 7. Keep locale docs structure aligned with their corresponding English pages. 8. Do not modify English source docs in packages/web/src/content/docs/*.mdx. 9. If no locale updates are needed, make no changes. EOF
  • if [ -z "$(git status --porcelain)" ]; then echo "No locale docs changes to commit" exit 0 fi git add -A git commit -m "docs(i18n): sync locale docs from english changes" git pull --rebase --autostash origin "$GITHUB_REF_NAME" git push origin HEAD:"$GITHUB_REF_NAME"
View raw YAML
name: docs-locale-sync

on:
  push:
    branches:
      - dev
    paths:
      - packages/web/src/content/docs/*.mdx

jobs:
  sync-locales:
    if: github.actor != 'opencode-agent[bot]'
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      contents: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          persist-credentials: false
          fetch-depth: 0
          ref: ${{ github.ref_name }}

      - name: Setup Bun
        uses: ./.github/actions/setup-bun

      - name: Setup git committer
        id: committer
        uses: ./.github/actions/setup-git-committer
        with:
          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}

      - name: Compute changed English docs
        id: changes
        run: |
          FILES=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- 'packages/web/src/content/docs/*.mdx' || true)
          if [ -z "$FILES" ]; then
            echo "has_changes=false" >> "$GITHUB_OUTPUT"
            echo "No English docs changed in push range"
            exit 0
          fi
          echo "has_changes=true" >> "$GITHUB_OUTPUT"
          {
            echo "files<<EOF"
            echo "$FILES"
            echo "EOF"
          } >> "$GITHUB_OUTPUT"

      - name: Install OpenCode
        if: steps.changes.outputs.has_changes == 'true'
        run: curl -fsSL https://opencode.ai/install | bash

      - name: Sync locale docs with OpenCode
        if: steps.changes.outputs.has_changes == 'true'
        env:
          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
          OPENCODE_CONFIG_CONTENT: |
            {
              "permission": {
                "*": "deny",
                "read": "allow",
                "edit": "allow",
                "glob": "allow",
                "task": "allow"
              }
            }
        run: |
          opencode run --agent docs --model opencode/gpt-5.3-codex <<'EOF'
            Update localized docs to match the latest English docs changes.

            Changed English doc files:
            <changed_english_docs>
            ${{ steps.changes.outputs.files }}
            </changed_english_docs>

            Requirements:
            1. Update all relevant locale docs under packages/web/src/content/docs/<locale>/ so they reflect these English page changes.
            2. You MUST use the Task tool for translation work and launch subagents with subagent_type `translator` (defined in .opencode/agent/translator.md).
            3. Do not translate directly in the primary agent. Use translator subagent output as the source for locale text updates.
            4. Run translator subagent Task calls in parallel whenever file/locale translation work is independent.
            5. Use only the minimum tools needed for this task (read/glob, file edits, and translator Task). Do not use shell, web, search, or GitHub tools for translation work.
            6. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
            7. Keep locale docs structure aligned with their corresponding English pages.
            8. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
            9. If no locale updates are needed, make no changes.
          EOF

      - name: Commit and push locale docs updates
        if: steps.changes.outputs.has_changes == 'true'
        run: |
          if [ -z "$(git status --porcelain)" ]; then
            echo "No locale docs changes to commit"
            exit 0
          fi
          git add -A
          git commit -m "docs(i18n): sync locale docs from english changes"
          git pull --rebase --autostash origin "$GITHUB_REF_NAME"
          git push origin HEAD:"$GITHUB_REF_NAME"
docs-update .github/workflows/docs-update.yml
Triggers
schedule, workflow_dispatch
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
update-docs
Actions
sst/opencode/github
Commands
  • COMMITS=$(git log --since="${{ env.LOOKBACK_HOURS }} hours ago" --pretty=format:"- %h %s" 2>/dev/null || echo "") if [ -z "$COMMITS" ]; then echo "No commits in the last ${{ env.LOOKBACK_HOURS }} hours" echo "has_commits=false" >> $GITHUB_OUTPUT else echo "has_commits=true" >> $GITHUB_OUTPUT { echo "list<<EOF" echo "$COMMITS" echo "EOF" } >> $GITHUB_OUTPUT fi
View raw YAML
name: docs-update

on:
  schedule:
    - cron: "0 */12 * * *"
  workflow_dispatch:

env:
  LOOKBACK_HOURS: 4

jobs:
  update-docs:
    if: github.repository == 'sst/opencode'
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      id-token: write
      contents: write
      pull-requests: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0 # Fetch full history to access commits

      - name: Setup Bun
        uses: ./.github/actions/setup-bun

      - name: Get recent commits
        id: commits
        run: |
          COMMITS=$(git log --since="${{ env.LOOKBACK_HOURS }} hours ago" --pretty=format:"- %h %s" 2>/dev/null || echo "")
          if [ -z "$COMMITS" ]; then
            echo "No commits in the last ${{ env.LOOKBACK_HOURS }} hours"
            echo "has_commits=false" >> $GITHUB_OUTPUT
          else
            echo "has_commits=true" >> $GITHUB_OUTPUT
            {
              echo "list<<EOF"
              echo "$COMMITS"
              echo "EOF"
            } >> $GITHUB_OUTPUT
          fi

      - name: Run opencode
        if: steps.commits.outputs.has_commits == 'true'
        uses: sst/opencode/github@latest
        env:
          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
        with:
          model: opencode/gpt-5.2
          agent: docs
          prompt: |
            Review the following commits from the last ${{ env.LOOKBACK_HOURS }} hours and identify any new features that may need documentation.

            <recent_commits>
            ${{ steps.commits.outputs.list }}
            </recent_commits>

            Steps:
            1. For each commit that looks like a new feature or significant change:
               - Read the changed files to understand what was added
               - Check if the feature is already documented in packages/web/src/content/docs/*
            2. If you find undocumented features:
               - Update the relevant documentation files in packages/web/src/content/docs/*
               - Follow the existing documentation style and structure
               - Make sure to document the feature clearly with examples where appropriate
            3. If all new features are already documented, report that no updates are needed
            4. If you are creating a new documentation file be sure to update packages/web/astro.config.mjs too.

            Focus on user-facing features and API changes. Skip internal refactors, bug fixes, and test updates unless they affect user-facing behavior.
            Don't feel the need to document every little thing. It is perfectly okay to make 0 changes at all.
            Try to keep documentation only for large features or changes that already have a good spot to be documented.
duplicate-issues .github/workflows/duplicate-issues.yml
Triggers
issues
Runs on
blacksmith-4vcpu-ubuntu-2404, blacksmith-4vcpu-ubuntu-2404
Jobs
check-duplicates, recheck-compliance
Commands
  • curl -fsSL https://opencode.ai/install | bash
  • opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created: Issue number: ${{ github.event.issue.number }} Lookup this issue with gh issue view ${{ github.event.issue.number }}. You have TWO tasks. Perform both, then post a SINGLE comment (if needed). --- TASK 1: CONTRIBUTING GUIDELINES COMPLIANCE CHECK Check whether the issue follows our contributing guidelines and issue templates. This project has three issue templates that every issue MUST use one of: 1. Bug Report - requires a Description field with real content 2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]: 3. Question - requires the Question field with real content Additionally check: - No AI-generated walls of text (long, AI-generated descriptions are not acceptable) - The issue has real content, not just template placeholder text left unchanged - Bug reports should include some context about how to reproduce - Feature requests should explain the problem or need - We want to push for having the user provide system description & information Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content. --- TASK 2: DUPLICATE CHECK Search through existing issues (excluding #${{ github.event.issue.number }}) to find potential duplicates. Consider: 1. Similar titles or descriptions 2. Same error messages or symptoms 3. Related functionality or components 4. Similar feature requests Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, note the pinned keybinds issue #4997. --- POSTING YOUR COMMENT: Based on your findings, post a SINGLE comment on issue #${{ github.event.issue.number }}. Build the comment as follows: If the issue is NOT compliant, start the comment with: <!-- issue-compliance --> Then explain what needs to be fixed and that they have 2 hours to edit the issue before it is automatically closed. Also add the label needs:compliance to the issue using: gh issue edit ${{ github.event.issue.number }} --add-label needs:compliance If duplicates were found, include a section about potential duplicates with links. If the issue mentions keybinds/keyboard shortcuts, include a note about #4997. If the issue IS compliant AND no duplicates were found AND no keybind reference, do NOT comment at all. Use this format for the comment: [If not compliant:] <!-- issue-compliance --> This issue doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md). **What needs to be fixed:** - [specific reasons] Please edit this issue to address the above within **2 hours**, or it will be automatically closed. [If duplicates found, add:] --- This issue might be a duplicate of existing issues. Please check: - #[issue_number]: [brief description of similarity] [If keybind-related, add:] For keybind-related issues, please also check our pinned keybinds documentation: #4997 [End with if not compliant:] If you believe this was flagged incorrectly, please let a maintainer know. Remember: post at most ONE comment combining all findings. If everything is fine, post nothing."
  • curl -fsSL https://opencode.ai/install | bash
  • opencode run -m opencode/claude-sonnet-4-6 "Issue #${{ github.event.issue.number }} was previously flagged as non-compliant and has been edited. Lookup this issue with gh issue view ${{ github.event.issue.number }}. Re-check whether the issue now follows our contributing guidelines and issue templates. This project has three issue templates that every issue MUST use one of: 1. Bug Report - requires a Description field with real content 2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]: 3. Question - requires the Question field with real content Additionally check: - No AI-generated walls of text (long, AI-generated descriptions are not acceptable) - The issue has real content, not just template placeholder text left unchanged - Bug reports should include some context about how to reproduce - Feature requests should explain the problem or need - We want to push for having the user provide system description & information Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content. If the issue is NOW compliant: 1. Remove the needs:compliance label: gh issue edit ${{ github.event.issue.number }} --remove-label needs:compliance 2. Find and delete the previous compliance comment (the one containing <!-- issue-compliance -->) using: gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments --jq '.[] | select(.body | contains(\"<!-- issue-compliance -->\")) | .id' then delete it with: gh api -X DELETE repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments/{id} 3. Post a short comment thanking them for updating the issue. If the issue is STILL not compliant: Post a comment explaining what still needs to be fixed. Keep the needs:compliance label."
View raw YAML
name: duplicate-issues

on:
  issues:
    types: [opened, edited]

jobs:
  check-duplicates:
    if: github.event.action == 'opened'
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      contents: read
      issues: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - uses: ./.github/actions/setup-bun

      - name: Install opencode
        run: curl -fsSL https://opencode.ai/install | bash

      - name: Check duplicates and compliance
        env:
          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OPENCODE_PERMISSION: |
            {
              "bash": {
                "*": "deny",
                "gh issue*": "allow"
              },
              "webfetch": "deny"
            }
        run: |
          opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created:

          Issue number: ${{ github.event.issue.number }}

          Lookup this issue with gh issue view ${{ github.event.issue.number }}.

          You have TWO tasks. Perform both, then post a SINGLE comment (if needed).

          ---

          TASK 1: CONTRIBUTING GUIDELINES COMPLIANCE CHECK

          Check whether the issue follows our contributing guidelines and issue templates.

          This project has three issue templates that every issue MUST use one of:

          1. Bug Report - requires a Description field with real content
          2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]:
          3. Question - requires the Question field with real content

          Additionally check:
          - No AI-generated walls of text (long, AI-generated descriptions are not acceptable)
          - The issue has real content, not just template placeholder text left unchanged
          - Bug reports should include some context about how to reproduce
          - Feature requests should explain the problem or need
          - We want to push for having the user provide system description & information

          Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content.

          ---

          TASK 2: DUPLICATE CHECK

          Search through existing issues (excluding #${{ github.event.issue.number }}) to find potential duplicates.
          Consider:
          1. Similar titles or descriptions
          2. Same error messages or symptoms
          3. Related functionality or components
          4. Similar feature requests

          Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, note the pinned keybinds issue #4997.

          ---

          POSTING YOUR COMMENT:

          Based on your findings, post a SINGLE comment on issue #${{ github.event.issue.number }}. Build the comment as follows:

          If the issue is NOT compliant, start the comment with:
          <!-- issue-compliance -->
          Then explain what needs to be fixed and that they have 2 hours to edit the issue before it is automatically closed. Also add the label needs:compliance to the issue using: gh issue edit ${{ github.event.issue.number }} --add-label needs:compliance

          If duplicates were found, include a section about potential duplicates with links.

          If the issue mentions keybinds/keyboard shortcuts, include a note about #4997.

          If the issue IS compliant AND no duplicates were found AND no keybind reference, do NOT comment at all.

          Use this format for the comment:

          [If not compliant:]
          <!-- issue-compliance -->
          This issue doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md).

          **What needs to be fixed:**
          - [specific reasons]

          Please edit this issue to address the above within **2 hours**, or it will be automatically closed.

          [If duplicates found, add:]
          ---
          This issue might be a duplicate of existing issues. Please check:
          - #[issue_number]: [brief description of similarity]

          [If keybind-related, add:]
          For keybind-related issues, please also check our pinned keybinds documentation: #4997

          [End with if not compliant:]
          If you believe this was flagged incorrectly, please let a maintainer know.

          Remember: post at most ONE comment combining all findings. If everything is fine, post nothing."

  recheck-compliance:
    if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance')
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      contents: read
      issues: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - uses: ./.github/actions/setup-bun

      - name: Install opencode
        run: curl -fsSL https://opencode.ai/install | bash

      - name: Recheck compliance
        env:
          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OPENCODE_PERMISSION: |
            {
              "bash": {
                "*": "deny",
                "gh issue*": "allow"
              },
              "webfetch": "deny"
            }
        run: |
          opencode run -m opencode/claude-sonnet-4-6 "Issue #${{ github.event.issue.number }} was previously flagged as non-compliant and has been edited.

          Lookup this issue with gh issue view ${{ github.event.issue.number }}.

          Re-check whether the issue now follows our contributing guidelines and issue templates.

          This project has three issue templates that every issue MUST use one of:

          1. Bug Report - requires a Description field with real content
          2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]:
          3. Question - requires the Question field with real content

          Additionally check:
          - No AI-generated walls of text (long, AI-generated descriptions are not acceptable)
          - The issue has real content, not just template placeholder text left unchanged
          - Bug reports should include some context about how to reproduce
          - Feature requests should explain the problem or need
          - We want to push for having the user provide system description & information

          Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content.

          If the issue is NOW compliant:
          1. Remove the needs:compliance label: gh issue edit ${{ github.event.issue.number }} --remove-label needs:compliance
          2. Find and delete the previous compliance comment (the one containing <!-- issue-compliance -->) using: gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments --jq '.[] | select(.body | contains(\"<!-- issue-compliance -->\")) | .id' then delete it with: gh api -X DELETE repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments/{id}
          3. Post a short comment thanking them for updating the issue.

          If the issue is STILL not compliant:
          Post a comment explaining what still needs to be fixed. Keep the needs:compliance label."
generate .github/workflows/generate.yml
Triggers
push
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
generate
Commands
  • ./script/generate.ts
  • if [ -z "$(git status --porcelain)" ]; then echo "No changes to commit" exit 0 fi git add -A git commit -m "chore: generate" --allow-empty git push origin HEAD:${{ github.ref_name }} --no-verify # if ! git push origin HEAD:${{ github.event.pull_request.head.ref || github.ref_name }} --no-verify; then # echo "" # echo "============================================" # echo "Failed to push generated code." # echo "Please run locally and push:" # echo "" # echo " ./script/generate.ts" # echo " git add -A && git commit -m \"chore: generate\" && git push" # echo "" # echo "============================================" # exit 1 # fi
View raw YAML
name: generate

on:
  push:
    branches:
      - dev

jobs:
  generate:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      contents: write
      pull-requests: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Bun
        uses: ./.github/actions/setup-bun

      - name: Setup git committer
        id: committer
        uses: ./.github/actions/setup-git-committer
        with:
          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}

      - name: Generate
        run: ./script/generate.ts

      - name: Commit and push
        run: |
          if [ -z "$(git status --porcelain)" ]; then
            echo "No changes to commit"
            exit 0
          fi
          git add -A
          git commit -m "chore: generate" --allow-empty
          git push origin HEAD:${{ github.ref_name }} --no-verify
          # if ! git push origin HEAD:${{ github.event.pull_request.head.ref || github.ref_name }} --no-verify; then
          #   echo ""
          #   echo "============================================"
          #   echo "Failed to push generated code."
          #   echo "Please run locally and push:"
          #   echo ""
          #   echo "  ./script/generate.ts"
          #   echo "  git add -A && git commit -m \"chore: generate\" && git push"
          #   echo ""
          #   echo "============================================"
          #   exit 1
          # fi
nix-eval perms .github/workflows/nix-eval.yml
Triggers
push, pull_request, workflow_dispatch
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
nix-eval
Actions
nixbuild/nix-quick-install-action
Commands
  • set -euo pipefail nix --version echo "=== Flake metadata ===" nix flake metadata echo "" echo "=== Flake structure ===" nix flake show --all-systems SYSTEMS="x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin" PACKAGES="opencode" # TODO: move 'desktop' to PACKAGES when #11755 is fixed OPTIONAL_PACKAGES="desktop" echo "" echo "=== Evaluating packages for all systems ===" for system in $SYSTEMS; do echo "" echo "--- $system ---" for pkg in $PACKAGES; do printf " %s: " "$pkg" if output=$(nix eval ".#packages.$system.$pkg.drvPath" --raw 2>&1); then echo "✓" else echo "✗" echo "::error::Evaluation failed for packages.$system.$pkg" echo "$output" exit 1 fi done done echo "" echo "=== Evaluating optional packages ===" for system in $SYSTEMS; do echo "" echo "--- $system ---" for pkg in $OPTIONAL_PACKAGES; do printf " %s: " "$pkg" if output=$(nix eval ".#packages.$system.$pkg.drvPath" --raw 2>&1); then echo "✓" else echo "✗" echo "::warning::Evaluation failed for packages.$system.$pkg" echo "$output" fi done done echo "" echo "=== Evaluating devShells for all systems ===" for system in $SYSTEMS; do printf "%s: " "$system" if output=$(nix eval ".#devShells.$system.default.drvPath" --raw 2>&1); then echo "✓" else echo "✗" echo "::error::Evaluation failed for devShells.$system.default" echo "$output" exit 1 fi done echo "" echo "=== All evaluations passed ==="
View raw YAML
name: nix-eval

on:
  push:
    branches: [dev]
  pull_request:
    branches: [dev]
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

jobs:
  nix-eval:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    timeout-minutes: 15
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Setup Nix
        uses: nixbuild/nix-quick-install-action@v34

      - name: Evaluate flake outputs (all systems)
        run: |
          set -euo pipefail
          nix --version

          echo "=== Flake metadata ==="
          nix flake metadata

          echo ""
          echo "=== Flake structure ==="
          nix flake show --all-systems

          SYSTEMS="x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin"
          PACKAGES="opencode"
          # TODO: move 'desktop' to PACKAGES when #11755 is fixed
          OPTIONAL_PACKAGES="desktop"

          echo ""
          echo "=== Evaluating packages for all systems ==="
          for system in $SYSTEMS; do
            echo ""
            echo "--- $system ---"
            for pkg in $PACKAGES; do
              printf "  %s: " "$pkg"
              if output=$(nix eval ".#packages.$system.$pkg.drvPath" --raw 2>&1); then
                echo "✓"
              else
                echo "✗"
                echo "::error::Evaluation failed for packages.$system.$pkg"
                echo "$output"
                exit 1
              fi
            done
          done

          echo ""
          echo "=== Evaluating optional packages ==="
          for system in $SYSTEMS; do
            echo ""
            echo "--- $system ---"
            for pkg in $OPTIONAL_PACKAGES; do
              printf "  %s: " "$pkg"
              if output=$(nix eval ".#packages.$system.$pkg.drvPath" --raw 2>&1); then
                echo "✓"
              else
                echo "✗"
                echo "::warning::Evaluation failed for packages.$system.$pkg"
                echo "$output"
              fi
            done
          done

          echo ""
          echo "=== Evaluating devShells for all systems ==="
          for system in $SYSTEMS; do
            printf "%s: " "$system"
            if output=$(nix eval ".#devShells.$system.default.drvPath" --raw 2>&1); then
              echo "✓"
            else
              echo "✗"
              echo "::error::Evaluation failed for devShells.$system.default"
              echo "$output"
              exit 1
            fi
          done

          echo ""
          echo "=== All evaluations passed ==="
nix-hashes matrix perms .github/workflows/nix-hashes.yml
Triggers
workflow_dispatch, push
Runs on
${{ matrix.runner }}, blacksmith-4vcpu-ubuntu-2404
Jobs
compute-hash, update-hashes
Matrix
include, include.runner, include.system→ aarch64-darwin, aarch64-linux, blacksmith-4vcpu-ubuntu-2404, blacksmith-4vcpu-ubuntu-2404-arm, macos-15-intel, macos-latest, x86_64-darwin, x86_64-linux
Actions
nixbuild/nix-quick-install-action
Commands
  • set -euo pipefail BUILD_LOG=$(mktemp) trap 'rm -f "$BUILD_LOG"' EXIT # Build with fakeHash to trigger hash mismatch and reveal correct hash nix build ".#packages.${SYSTEM}.node_modules_updater" --no-link 2>&1 | tee "$BUILD_LOG" || true # Extract hash from build log with portability HASH="$(nix run --inputs-from . nixpkgs#gnugrep -- -oP 'got:\s*\Ksha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | tail -n1 || true)" if [ -z "$HASH" ]; then echo "::error::Failed to compute hash for ${SYSTEM}" cat "$BUILD_LOG" exit 1 fi echo "$HASH" > hash.txt echo "Computed hash for ${SYSTEM}: $HASH"
  • git pull --rebase --autostash origin "$GITHUB_REF_NAME"
  • set -euo pipefail HASH_FILE="nix/hashes.json" [ -f "$HASH_FILE" ] || echo '{"nodeModules":{}}' > "$HASH_FILE" for SYSTEM in x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin; do FILE="hashes/hash-${SYSTEM}/hash.txt" if [ -f "$FILE" ]; then HASH="$(tr -d '[:space:]' < "$FILE")" echo "${SYSTEM}: ${HASH}" jq --arg sys "$SYSTEM" --arg h "$HASH" '.nodeModules[$sys] = $h' "$HASH_FILE" > tmp.json mv tmp.json "$HASH_FILE" else echo "::warning::Missing hash for ${SYSTEM}" fi done cat "$HASH_FILE"
  • set -euo pipefail HASH_FILE="nix/hashes.json" if [ -z "$(git status --short -- "$HASH_FILE")" ]; then echo "No changes to commit" echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY" echo "Status: no changes" >> "$GITHUB_STEP_SUMMARY" exit 0 fi git add "$HASH_FILE" git commit -m "chore: update nix node_modules hashes" git pull --rebase --autostash origin "$GITHUB_REF_NAME" git push origin HEAD:"$GITHUB_REF_NAME" echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY" echo "Status: committed $(git rev-parse --short HEAD)" >> "$GITHUB_STEP_SUMMARY"
View raw YAML
name: nix-hashes

permissions:
  contents: write

on:
  workflow_dispatch:
  push:
    branches: [dev, beta]
    paths:
      - "bun.lock"
      - "package.json"
      - "packages/*/package.json"
      - "flake.lock"
      - "nix/node_modules.nix"
      - "nix/scripts/**"
      - "patches/**"
      - ".github/workflows/nix-hashes.yml"

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # Native runners required: bun install cross-compilation flags (--os/--cpu)
  # do not produce byte-identical node_modules as native installs.
  compute-hash:
    strategy:
      fail-fast: false
      matrix:
        include:
          - system: x86_64-linux
            runner: blacksmith-4vcpu-ubuntu-2404
          - system: aarch64-linux
            runner: blacksmith-4vcpu-ubuntu-2404-arm
          - system: x86_64-darwin
            runner: macos-15-intel
          - system: aarch64-darwin
            runner: macos-latest
    runs-on: ${{ matrix.runner }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Setup Nix
        uses: nixbuild/nix-quick-install-action@v34

      - name: Compute node_modules hash
        id: hash
        env:
          SYSTEM: ${{ matrix.system }}
        run: |
          set -euo pipefail

          BUILD_LOG=$(mktemp)
          trap 'rm -f "$BUILD_LOG"' EXIT

          # Build with fakeHash to trigger hash mismatch and reveal correct hash
          nix build ".#packages.${SYSTEM}.node_modules_updater" --no-link 2>&1 | tee "$BUILD_LOG" || true

          # Extract hash from build log with portability
          HASH="$(nix run --inputs-from . nixpkgs#gnugrep -- -oP 'got:\s*\Ksha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | tail -n1 || true)"

          if [ -z "$HASH" ]; then
            echo "::error::Failed to compute hash for ${SYSTEM}"
            cat "$BUILD_LOG"
            exit 1
          fi

          echo "$HASH" > hash.txt
          echo "Computed hash for ${SYSTEM}: $HASH"

      - name: Upload hash
        uses: actions/upload-artifact@v4
        with:
          name: hash-${{ matrix.system }}
          path: hash.txt
          retention-days: 1

  update-hashes:
    needs: compute-hash
    if: github.event_name != 'pull_request'
    runs-on: blacksmith-4vcpu-ubuntu-2404

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          persist-credentials: false
          fetch-depth: 0
          ref: ${{ github.ref_name }}

      - name: Setup git committer
        uses: ./.github/actions/setup-git-committer
        with:
          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}

      - name: Pull latest changes
        run: |
          git pull --rebase --autostash origin "$GITHUB_REF_NAME"

      - name: Download hash artifacts
        uses: actions/download-artifact@v4
        with:
          path: hashes
          pattern: hash-*

      - name: Update hashes.json
        run: |
          set -euo pipefail

          HASH_FILE="nix/hashes.json"

          [ -f "$HASH_FILE" ] || echo '{"nodeModules":{}}' > "$HASH_FILE"

          for SYSTEM in x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin; do
            FILE="hashes/hash-${SYSTEM}/hash.txt"
            if [ -f "$FILE" ]; then
              HASH="$(tr -d '[:space:]' < "$FILE")"
              echo "${SYSTEM}: ${HASH}"
              jq --arg sys "$SYSTEM" --arg h "$HASH" '.nodeModules[$sys] = $h' "$HASH_FILE" > tmp.json
              mv tmp.json "$HASH_FILE"
            else
              echo "::warning::Missing hash for ${SYSTEM}"
            fi
          done

          cat "$HASH_FILE"

      - name: Commit changes
        run: |
          set -euo pipefail

          HASH_FILE="nix/hashes.json"

          if [ -z "$(git status --short -- "$HASH_FILE")" ]; then
            echo "No changes to commit"
            echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY"
            echo "Status: no changes" >> "$GITHUB_STEP_SUMMARY"
            exit 0
          fi

          git add "$HASH_FILE"
          git commit -m "chore: update nix node_modules hashes"

          git pull --rebase --autostash origin "$GITHUB_REF_NAME"
          git push origin HEAD:"$GITHUB_REF_NAME"

          echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY"
          echo "Status: committed $(git rev-parse --short HEAD)" >> "$GITHUB_STEP_SUMMARY"
notify-discord .github/workflows/notify-discord.yml
Triggers
release
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
notify
Actions
SethCohen/github-releases-to-discord
View raw YAML
name: notify-discord

on:
  release:
    types: [released] # fires when a draft release is published

jobs:
  notify:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    steps:
      - name: Send nicely-formatted embed to Discord
        uses: SethCohen/github-releases-to-discord@v1
        with:
          webhook_url: ${{ secrets.DISCORD_WEBHOOK }}
opencode .github/workflows/opencode.yml
Triggers
issue_comment, pull_request_review_comment
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
opencode
Actions
anomalyco/opencode/github
View raw YAML
name: opencode

on:
  issue_comment:
    types: [created]
  pull_request_review_comment:
    types: [created]

jobs:
  opencode:
    if: |
      contains(github.event.comment.body, ' /oc') ||
      startsWith(github.event.comment.body, '/oc') ||
      contains(github.event.comment.body, ' /opencode') ||
      startsWith(github.event.comment.body, '/opencode')
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      id-token: write
      contents: read
      pull-requests: read
      issues: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - uses: ./.github/actions/setup-bun

      - name: Run opencode
        uses: anomalyco/opencode/github@latest
        env:
          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
          OPENCODE_PERMISSION: '{"bash": "deny"}'
        with:
          model: opencode/claude-opus-4-5
pr-management .github/workflows/pr-management.yml
Triggers
pull_request_target
Runs on
blacksmith-4vcpu-ubuntu-2404, ubuntu-latest
Jobs
check-duplicates, add-contributor-label
Commands
  • LOGIN="${{ github.event.pull_request.user.login }}" if [ "$LOGIN" = "opencode-agent[bot]" ] || grep -qxF "$LOGIN" .github/TEAM_MEMBERS; then echo "is_team=true" >> "$GITHUB_OUTPUT" echo "Skipping: $LOGIN is a team member or bot" else echo "is_team=false" >> "$GITHUB_OUTPUT" fi
  • bun install
  • curl -fsSL https://opencode.ai/install | bash
  • { echo "Check for duplicate PRs related to this new PR:" echo "" echo "CURRENT_PR_NUMBER: $PR_NUMBER" echo "" echo "Title: $(gh pr view "$PR_NUMBER" --json title --jq .title)" echo "" echo "Description:" gh pr view "$PR_NUMBER" --json body --jq .body } > pr_info.txt
  • COMMENT=$(bun script/duplicate-pr.ts -f pr_info.txt "Check the attached file for PR details and search for duplicates") if [ "$COMMENT" != "No duplicate PRs found" ]; then gh pr comment "$PR_NUMBER" --body "_The following comment was made by an LLM, it may be inaccurate:_ $COMMENT" fi
View raw YAML
name: pr-management

on:
  pull_request_target:
    types: [opened]

jobs:
  check-duplicates:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      contents: read
      pull-requests: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Check team membership
        id: team-check
        run: |
          LOGIN="${{ github.event.pull_request.user.login }}"
          if [ "$LOGIN" = "opencode-agent[bot]" ] || grep -qxF "$LOGIN" .github/TEAM_MEMBERS; then
            echo "is_team=true" >> "$GITHUB_OUTPUT"
            echo "Skipping: $LOGIN is a team member or bot"
          else
            echo "is_team=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Setup Bun
        if: steps.team-check.outputs.is_team != 'true'
        uses: ./.github/actions/setup-bun

      - name: Install dependencies
        if: steps.team-check.outputs.is_team != 'true'
        run: bun install

      - name: Install opencode
        if: steps.team-check.outputs.is_team != 'true'
        run: curl -fsSL https://opencode.ai/install | bash

      - name: Build prompt
        if: steps.team-check.outputs.is_team != 'true'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
        run: |
          {
            echo "Check for duplicate PRs related to this new PR:"
            echo ""
            echo "CURRENT_PR_NUMBER: $PR_NUMBER"
            echo ""
            echo "Title: $(gh pr view "$PR_NUMBER" --json title --jq .title)"
            echo ""
            echo "Description:"
            gh pr view "$PR_NUMBER" --json body --jq .body
          } > pr_info.txt

      - name: Check for duplicate PRs
        if: steps.team-check.outputs.is_team != 'true'
        env:
          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
        run: |
          COMMENT=$(bun script/duplicate-pr.ts -f pr_info.txt "Check the attached file for PR details and search for duplicates")

          if [ "$COMMENT" != "No duplicate PRs found" ]; then
            gh pr comment "$PR_NUMBER" --body "_The following comment was made by an LLM, it may be inaccurate:_

          $COMMENT"
          fi

  add-contributor-label:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      issues: write
    steps:
      - name: Add Contributor Label
        uses: actions/github-script@v8
        with:
          script: |
            const isPR = !!context.payload.pull_request;
            const issueNumber = isPR ? context.payload.pull_request.number : context.payload.issue.number;
            const authorAssociation = isPR ? context.payload.pull_request.author_association : context.payload.issue.author_association;

            if (authorAssociation === 'CONTRIBUTOR') {
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: issueNumber,
                labels: ['contributor']
              });
            }
pr-standards .github/workflows/pr-standards.yml
Triggers
pull_request_target
Runs on
ubuntu-latest, ubuntu-latest
Jobs
check-standards, check-compliance
View raw YAML
name: pr-standards

on:
  pull_request_target:
    types: [opened, edited, synchronize]

jobs:
  check-standards:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - name: Check PR standards
        uses: actions/github-script@v7
        with:
          script: |
            const pr = context.payload.pull_request;
            const login = pr.user.login;

            // Skip PRs older than Feb 18, 2026 at 6PM EST (Feb 19, 2026 00:00 UTC)
            const cutoff = new Date('2026-02-19T00:00:00Z');
            const prCreated = new Date(pr.created_at);
            if (prCreated < cutoff) {
              console.log(`Skipping: PR #${pr.number} was created before cutoff (${prCreated.toISOString()})`);
              return;
            }

            // Check if author is a team member or bot
            if (login === 'opencode-agent[bot]') return;
            const { data: file } = await github.rest.repos.getContent({
              owner: context.repo.owner,
              repo: context.repo.repo,
              path: '.github/TEAM_MEMBERS',
              ref: 'dev'
            });
            const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean);
            if (members.includes(login)) {
              console.log(`Skipping: ${login} is a team member`);
              return;
            }

            const title = pr.title;

            async function addLabel(label) {
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                labels: [label]
              });
            }

            async function removeLabel(label) {
              try {
                await github.rest.issues.removeLabel({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: pr.number,
                  name: label
                });
              } catch (e) {
                // Label wasn't present, ignore
              }
            }

            async function comment(marker, body) {
              const markerText = `<!-- pr-standards:${marker} -->`;
              const { data: comments } = await github.rest.issues.listComments({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number
              });
              
              const existing = comments.find(c => c.body.includes(markerText));
              if (existing) return;
              
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                body: markerText + '\n' + body
              });
            }

            // Step 1: Check title format
            // Matches: feat:, feat(scope):, feat (scope):, etc.
            const titlePattern = /^(feat|fix|docs|chore|refactor|test)\s*(\([a-zA-Z0-9-]+\))?\s*:/;
            const hasValidTitle = titlePattern.test(title);

            if (!hasValidTitle) {
              await addLabel('needs:title');
              await comment('title', `Hey! Your PR title \`${title}\` doesn't follow conventional commit format.

            Please update it to start with one of:
            - \`feat:\` or \`feat(scope):\` new feature
            - \`fix:\` or \`fix(scope):\` bug fix
            - \`docs:\` or \`docs(scope):\` documentation changes
            - \`chore:\` or \`chore(scope):\` maintenance tasks
            - \`refactor:\` or \`refactor(scope):\` code refactoring
            - \`test:\` or \`test(scope):\` adding or updating tests

            Where \`scope\` is the package name (e.g., \`app\`, \`desktop\`, \`opencode\`).

            See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#pr-titles) for details.`);
              return;
            }

            await removeLabel('needs:title');

            // Step 2: Check for linked issue (skip for docs/refactor/feat PRs)
            const skipIssueCheck = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
            if (skipIssueCheck) {
              await removeLabel('needs:issue');
              console.log('Skipping issue check for docs/refactor/feat PR');
              return;
            }
            const query = `
              query($owner: String!, $repo: String!, $number: Int!) {
                repository(owner: $owner, name: $repo) {
                  pullRequest(number: $number) {
                    closingIssuesReferences(first: 1) {
                      totalCount
                    }
                  }
                }
              }
            `;

            const result = await github.graphql(query, {
              owner: context.repo.owner,
              repo: context.repo.repo,
              number: pr.number
            });

            const linkedIssues = result.repository.pullRequest.closingIssuesReferences.totalCount;

            if (linkedIssues === 0) {
              await addLabel('needs:issue');
              await comment('issue', `Thanks for your contribution!

            This PR doesn't have a linked issue. All PRs must reference an existing issue.

            Please:
            1. Open an issue describing the bug/feature (if one doesn't exist)
            2. Add \`Fixes #<number>\` or \`Closes #<number>\` to this PR description

            See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#issue-first-policy) for details.`);
              return;
            }

            await removeLabel('needs:issue');
            console.log('PR meets all standards');

  check-compliance:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - name: Check PR template compliance
        uses: actions/github-script@v7
        with:
          script: |
            const pr = context.payload.pull_request;
            const login = pr.user.login;

            // Skip PRs older than Feb 18, 2026 at 6PM EST (Feb 19, 2026 00:00 UTC)
            const cutoff = new Date('2026-02-19T00:00:00Z');
            const prCreated = new Date(pr.created_at);
            if (prCreated < cutoff) {
              console.log(`Skipping: PR #${pr.number} was created before cutoff (${prCreated.toISOString()})`);
              return;
            }

            // Check if author is a team member or bot
            if (login === 'opencode-agent[bot]') return;
            const { data: file } = await github.rest.repos.getContent({
              owner: context.repo.owner,
              repo: context.repo.repo,
              path: '.github/TEAM_MEMBERS',
              ref: 'dev'
            });
            const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean);
            if (members.includes(login)) {
              console.log(`Skipping: ${login} is a team member`);
              return;
            }

            const body = pr.body || '';
            const title = pr.title;
            const isDocsRefactorOrFeat = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);

            const issues = [];

            // Check: template sections exist
            const hasWhatSection = /### What does this PR do\?/.test(body);
            const hasTypeSection = /### Type of change/.test(body);
            const hasVerifySection = /### How did you verify your code works\?/.test(body);
            const hasChecklistSection = /### Checklist/.test(body);
            const hasIssueSection = /### Issue for this PR/.test(body);

            if (!hasWhatSection || !hasTypeSection || !hasVerifySection || !hasChecklistSection || !hasIssueSection) {
              issues.push('PR description is missing required template sections. Please use the [PR template](../blob/dev/.github/pull_request_template.md).');
            }

            // Check: "What does this PR do?" has real content (not just placeholder text)
            if (hasWhatSection) {
              const whatMatch = body.match(/### What does this PR do\?\s*\n([\s\S]*?)(?=###|$)/);
              const whatContent = whatMatch ? whatMatch[1].trim() : '';
              const placeholder = 'Please provide a description of the issue';
              const onlyPlaceholder = whatContent.includes(placeholder) && whatContent.replace(placeholder, '').replace(/[*\s]/g, '').length < 20;
              if (!whatContent || onlyPlaceholder) {
                issues.push('"What does this PR do?" section is empty or only contains placeholder text. Please describe your changes.');
              }
            }

            // Check: at least one "Type of change" checkbox is checked
            if (hasTypeSection) {
              const typeMatch = body.match(/### Type of change\s*\n([\s\S]*?)(?=###|$)/);
              const typeContent = typeMatch ? typeMatch[1] : '';
              const hasCheckedBox = /- \[x\]/i.test(typeContent);
              if (!hasCheckedBox) {
                issues.push('No "Type of change" checkbox is checked. Please select at least one.');
              }
            }

            // Check: issue reference (skip for docs/refactor/feat)
            if (!isDocsRefactorOrFeat && hasIssueSection) {
              const issueMatch = body.match(/### Issue for this PR\s*\n([\s\S]*?)(?=###|$)/);
              const issueContent = issueMatch ? issueMatch[1].trim() : '';
              const hasIssueRef = /(closes|fixes|resolves)\s+#\d+/i.test(issueContent) || /#\d+/.test(issueContent);
              if (!hasIssueRef) {
                issues.push('No issue referenced. Please add `Closes #<number>` linking to the relevant issue.');
              }
            }

            // Check: "How did you verify" has content
            if (hasVerifySection) {
              const verifyMatch = body.match(/### How did you verify your code works\?\s*\n([\s\S]*?)(?=###|$)/);
              const verifyContent = verifyMatch ? verifyMatch[1].trim() : '';
              if (!verifyContent) {
                issues.push('"How did you verify your code works?" section is empty. Please explain how you tested.');
              }
            }

            // Check: checklist boxes are checked
            if (hasChecklistSection) {
              const checklistMatch = body.match(/### Checklist\s*\n([\s\S]*?)(?=###|$)/);
              const checklistContent = checklistMatch ? checklistMatch[1] : '';
              const unchecked = (checklistContent.match(/- \[ \]/g) || []).length;
              const checked = (checklistContent.match(/- \[x\]/gi) || []).length;
              if (checked < 2) {
                issues.push('Not all checklist items are checked. Please confirm you have tested locally and have not included unrelated changes.');
              }
            }

            // Helper functions
            async function addLabel(label) {
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                labels: [label]
              });
            }

            async function removeLabel(label) {
              try {
                await github.rest.issues.removeLabel({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: pr.number,
                  name: label
                });
              } catch (e) {}
            }

            const hasComplianceLabel = pr.labels.some(l => l.name === 'needs:compliance');

            if (issues.length > 0) {
              // Non-compliant
              if (!hasComplianceLabel) {
                await addLabel('needs:compliance');
              }

              const marker = '<!-- issue-compliance -->';
              const { data: comments } = await github.rest.issues.listComments({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number
              });
              const existing = comments.find(c => c.body.includes(marker));

              const body_text = `${marker}
            This PR doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) and [PR template](../blob/dev/.github/pull_request_template.md).

            **What needs to be fixed:**
            ${issues.map(i => `- ${i}`).join('\n')}

            Please edit this PR description to address the above within **2 hours**, or it will be automatically closed.

            If you believe this was flagged incorrectly, please let a maintainer know.`;

              if (existing) {
                await github.rest.issues.updateComment({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  comment_id: existing.id,
                  body: body_text
                });
              } else {
                await github.rest.issues.createComment({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: pr.number,
                  body: body_text
                });
              }

              console.log(`PR #${pr.number} is non-compliant: ${issues.join(', ')}`);
            } else if (hasComplianceLabel) {
              // Was non-compliant, now fixed
              await removeLabel('needs:compliance');

              const { data: comments } = await github.rest.issues.listComments({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number
              });
              const marker = '<!-- issue-compliance -->';
              const existing = comments.find(c => c.body.includes(marker));
              if (existing) {
                await github.rest.issues.deleteComment({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  comment_id: existing.id
                });
              }

              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                body: 'Thanks for updating your PR! It now meets our contributing guidelines. :+1:'
              });

              console.log(`PR #${pr.number} is now compliant, label removed`);
            } else {
              console.log(`PR #${pr.number} is compliant`);
            }
publish matrix perms .github/workflows/publish.yml
Triggers
push, workflow_dispatch
Runs on
blacksmith-4vcpu-ubuntu-2404, blacksmith-4vcpu-ubuntu-2404, blacksmith-4vcpu-windows-2025, ${{ matrix.settings.host }}, ${{ matrix.settings.host }}, blacksmith-4vcpu-ubuntu-2404
Jobs
version, build-cli, sign-cli-windows, build-tauri, build-electron, publish
Matrix
settings, settings.host, settings.platform_flag, settings.target→ --linux, --mac --arm64, --mac --x64, --win, --win --arm64, aarch64-apple-darwin, aarch64-pc-windows-msvc, aarch64-unknown-linux-gnu, blacksmith-4vcpu-ubuntu-2404, blacksmith-4vcpu-windows-2025, blacksmith-8vcpu-ubuntu-2404-arm, macos-latest, windows-2025, x86_64-apple-darwin, x86_64-pc-windows-msvc, x86_64-unknown-linux-gnu
Actions
azure/login, azure/artifact-signing-action, apple-actions/import-codesign-certs, azure/login, dtolnay/rust-toolchain, Swatinem/rust-cache, taiki-e/cache-cargo-install-action, tauri-apps/tauri-action, apple-actions/import-codesign-certs, azure/login, docker/login-action, docker/setup-qemu-action, docker/setup-buildx-action
Commands
  • bun i -g opencode-ai
  • ./script/version.ts
  • ./packages/opencode/script/build.ts
  • $files = @( "${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64\bin\opencode.exe", "${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64\bin\opencode.exe", "${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline\bin\opencode.exe" ) foreach ($file in $files) { $sig = Get-AuthenticodeSignature $file if ($sig.Status -ne "Valid") { throw "Invalid signature for ${file}: $($sig.Status)" } }
  • Compress-Archive -Path "opencode-windows-arm64\bin\*" -DestinationPath "opencode-windows-arm64.zip" -Force Compress-Archive -Path "opencode-windows-x64\bin\*" -DestinationPath "opencode-windows-x64.zip" -Force Compress-Archive -Path "opencode-windows-x64-baseline\bin\*" -DestinationPath "opencode-windows-x64-baseline.zip" -Force
  • gh release upload "v${{ needs.version.outputs.version }}" ` "${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64.zip" ` "${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64.zip" ` "${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline.zip" ` --clobber ` --repo "${{ needs.version.outputs.repo }}"
  • CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application") CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV echo "Certificate imported."
  • echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8
View raw YAML
name: publish
run-name: "${{ format('release {0}', inputs.bump) }}"

on:
  push:
    branches:
      - ci
      - dev
      - beta
      - snapshot-*
  workflow_dispatch:
    inputs:
      bump:
        description: "Bump major, minor, or patch"
        required: false
        type: choice
        options:
          - major
          - minor
          - patch
      version:
        description: "Override version (optional)"
        required: false
        type: string

concurrency: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.version || inputs.bump }}

permissions:
  id-token: write
  contents: write
  packages: write

jobs:
  version:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    if: github.repository == 'anomalyco/opencode'
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - uses: ./.github/actions/setup-bun

      - name: Setup git committer
        id: committer
        uses: ./.github/actions/setup-git-committer
        with:
          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}

      - name: Install OpenCode
        if: inputs.bump || inputs.version
        run: bun i -g opencode-ai

      - id: version
        run: |
          ./script/version.ts
        env:
          GH_TOKEN: ${{ steps.committer.outputs.token }}
          OPENCODE_BUMP: ${{ inputs.bump }}
          OPENCODE_VERSION: ${{ inputs.version }}
          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
          GH_REPO: ${{ (github.ref_name == 'beta' && 'anomalyco/opencode-beta') || github.repository }}
    outputs:
      version: ${{ steps.version.outputs.version }}
      release: ${{ steps.version.outputs.release }}
      tag: ${{ steps.version.outputs.tag }}
      repo: ${{ steps.version.outputs.repo }}

  build-cli:
    needs: version
    runs-on: blacksmith-4vcpu-ubuntu-2404
    if: github.repository == 'anomalyco/opencode'
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-tags: true

      - uses: ./.github/actions/setup-bun

      - name: Setup git committer
        id: committer
        uses: ./.github/actions/setup-git-committer
        with:
          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}

      - name: Build
        id: build
        run: |
          ./packages/opencode/script/build.ts
        env:
          OPENCODE_VERSION: ${{ needs.version.outputs.version }}
          OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
          GH_REPO: ${{ needs.version.outputs.repo }}
          GH_TOKEN: ${{ steps.committer.outputs.token }}

      - uses: actions/upload-artifact@v4
        with:
          name: opencode-cli
          path: |
            packages/opencode/dist/opencode-darwin*
            packages/opencode/dist/opencode-linux*

      - uses: actions/upload-artifact@v4
        with:
          name: opencode-cli-windows
          path: packages/opencode/dist/opencode-windows*
    outputs:
      version: ${{ needs.version.outputs.version }}

  sign-cli-windows:
    needs:
      - build-cli
      - version
    runs-on: blacksmith-4vcpu-windows-2025
    if: github.repository == 'anomalyco/opencode'
    env:
      AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
      AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      AZURE_TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
      AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
      AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
    steps:
      - uses: actions/checkout@v3

      - uses: actions/download-artifact@v4
        with:
          name: opencode-cli-windows
          path: packages/opencode/dist

      - name: Setup git committer
        id: committer
        uses: ./.github/actions/setup-git-committer
        with:
          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}

      - name: Azure login
        uses: azure/login@v2
        with:
          client-id: ${{ env.AZURE_CLIENT_ID }}
          tenant-id: ${{ env.AZURE_TENANT_ID }}
          subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}

      - uses: azure/artifact-signing-action@v1
        with:
          endpoint: ${{ env.AZURE_TRUSTED_SIGNING_ENDPOINT }}
          signing-account-name: ${{ env.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
          certificate-profile-name: ${{ env.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
          files: |
            ${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64\bin\opencode.exe
            ${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64\bin\opencode.exe
            ${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline\bin\opencode.exe
          exclude-environment-credential: true
          exclude-workload-identity-credential: true
          exclude-managed-identity-credential: true
          exclude-shared-token-cache-credential: true
          exclude-visual-studio-credential: true
          exclude-visual-studio-code-credential: true
          exclude-azure-cli-credential: false
          exclude-azure-powershell-credential: true
          exclude-azure-developer-cli-credential: true
          exclude-interactive-browser-credential: true

      - name: Verify Windows CLI signatures
        shell: pwsh
        run: |
          $files = @(
            "${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64\bin\opencode.exe",
            "${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64\bin\opencode.exe",
            "${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline\bin\opencode.exe"
          )

          foreach ($file in $files) {
            $sig = Get-AuthenticodeSignature $file
            if ($sig.Status -ne "Valid") {
              throw "Invalid signature for ${file}: $($sig.Status)"
            }
          }

      - name: Repack Windows CLI archives
        working-directory: packages/opencode/dist
        shell: pwsh
        run: |
          Compress-Archive -Path "opencode-windows-arm64\bin\*" -DestinationPath "opencode-windows-arm64.zip" -Force
          Compress-Archive -Path "opencode-windows-x64\bin\*" -DestinationPath "opencode-windows-x64.zip" -Force
          Compress-Archive -Path "opencode-windows-x64-baseline\bin\*" -DestinationPath "opencode-windows-x64-baseline.zip" -Force

      - name: Upload signed Windows CLI release assets
        if: needs.version.outputs.release != ''
        shell: pwsh
        env:
          GH_TOKEN: ${{ steps.committer.outputs.token }}
        run: |
          gh release upload "v${{ needs.version.outputs.version }}" `
            "${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64.zip" `
            "${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64.zip" `
            "${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline.zip" `
            --clobber `
            --repo "${{ needs.version.outputs.repo }}"

      - uses: actions/upload-artifact@v4
        with:
          name: opencode-cli-signed-windows
          path: |
            packages/opencode/dist/opencode-windows-arm64
            packages/opencode/dist/opencode-windows-x64
            packages/opencode/dist/opencode-windows-x64-baseline

  build-tauri:
    needs:
      - build-cli
      - version
    continue-on-error: false
    env:
      AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
      AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      AZURE_TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
      AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
      AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
    strategy:
      fail-fast: false
      matrix:
        settings:
          - host: macos-latest
            target: x86_64-apple-darwin
          - host: macos-latest
            target: aarch64-apple-darwin
          # github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain
          - host: windows-2025
            target: aarch64-pc-windows-msvc
          - host: blacksmith-4vcpu-windows-2025
            target: x86_64-pc-windows-msvc
          - host: blacksmith-4vcpu-ubuntu-2404
            target: x86_64-unknown-linux-gnu
          - host: blacksmith-8vcpu-ubuntu-2404-arm
            target: aarch64-unknown-linux-gnu
    runs-on: ${{ matrix.settings.host }}
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-tags: true

      - uses: apple-actions/import-codesign-certs@v2
        if: ${{ runner.os == 'macOS' }}
        with:
          keychain: build
          p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
          p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}

      - name: Verify Certificate
        if: ${{ runner.os == 'macOS' }}
        run: |
          CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application")
          CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
          echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
          echo "Certificate imported."

      - name: Setup Apple API Key
        if: ${{ runner.os == 'macOS' }}
        run: |
          echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8

      - uses: ./.github/actions/setup-bun

      - name: Azure login
        if: runner.os == 'Windows'
        uses: azure/login@v2
        with:
          client-id: ${{ env.AZURE_CLIENT_ID }}
          tenant-id: ${{ env.AZURE_TENANT_ID }}
          subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}

      - uses: actions/setup-node@v4
        with:
          node-version: "24"

      - name: Cache apt packages
        if: contains(matrix.settings.host, 'ubuntu')
        uses: actions/cache@v4
        with:
          path: ~/apt-cache
          key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-${{ hashFiles('.github/workflows/publish.yml') }}
          restore-keys: |
            ${{ runner.os }}-${{ matrix.settings.target }}-apt-

      - name: install dependencies (ubuntu only)
        if: contains(matrix.settings.host, 'ubuntu')
        run: |
          mkdir -p ~/apt-cache && chmod -R a+rw ~/apt-cache
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends -o dir::cache::archives="$HOME/apt-cache" libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
          sudo chmod -R a+rw ~/apt-cache

      - name: install Rust stable
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.settings.target }}

      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: packages/desktop/src-tauri
          shared-key: ${{ matrix.settings.target }}

      - name: Prepare
        run: |
          cd packages/desktop
          bun ./scripts/prepare.ts
        env:
          OPENCODE_VERSION: ${{ needs.version.outputs.version }}
          GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
          OPENCODE_CLI_ARTIFACT: ${{ (runner.os == 'Windows' && 'opencode-cli-windows') || 'opencode-cli' }}
          RUST_TARGET: ${{ matrix.settings.target }}
          GH_TOKEN: ${{ github.token }}
          GITHUB_RUN_ID: ${{ github.run_id }}

      - name: Resolve tauri portable SHA
        if: contains(matrix.settings.host, 'ubuntu')
        run: echo "TAURI_PORTABLE_SHA=$(git ls-remote https://github.com/tauri-apps/tauri.git refs/heads/feat/truly-portable-appimage | cut -f1)" >> "$GITHUB_ENV"

      # Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released
      - name: Install tauri-cli from portable appimage branch
        uses: taiki-e/cache-cargo-install-action@v3
        if: contains(matrix.settings.host, 'ubuntu')
        with:
          tool: tauri-cli
          git: https://github.com/tauri-apps/tauri
          # branch: feat/truly-portable-appimage
          rev: ${{ env.TAURI_PORTABLE_SHA }}

      - name: Show tauri-cli version
        if: contains(matrix.settings.host, 'ubuntu')
        run: cargo tauri --version

      - name: Setup git committer
        id: committer
        uses: ./.github/actions/setup-git-committer
        with:
          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}

      - name: Build and upload artifacts
        uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
        timeout-minutes: 60
        with:
          projectPath: packages/desktop
          uploadWorkflowArtifacts: true
          tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
          args: --target ${{ matrix.settings.target }} --config ${{ (github.ref_name == 'beta' && './src-tauri/tauri.beta.conf.json') || './src-tauri/tauri.prod.conf.json' }} --verbose
          updaterJsonPreferNsis: true
          releaseId: ${{ needs.version.outputs.release }}
          tagName: ${{ needs.version.outputs.tag }}
          releaseDraft: true
          releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
          repo: ${{ (github.ref_name == 'beta' && 'opencode-beta') || '' }}
          releaseCommitish: ${{ github.sha }}
        env:
          GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
          TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
          TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
          TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
          APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
          APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
          APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
          APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
          APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
          APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8

      - name: Verify signed Windows desktop artifacts
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $files = @(
            "${{ github.workspace }}\packages\desktop\src-tauri\sidecars\opencode-cli-${{ matrix.settings.target }}.exe"
          )
          $files += Get-ChildItem "${{ github.workspace }}\packages\desktop\src-tauri\target\${{ matrix.settings.target }}\release\bundle\nsis\*.exe" | Select-Object -ExpandProperty FullName

          foreach ($file in $files) {
            $sig = Get-AuthenticodeSignature $file
            if ($sig.Status -ne "Valid") {
              throw "Invalid signature for ${file}: $($sig.Status)"
            }
          }

  build-electron:
    needs:
      - build-cli
      - version
    continue-on-error: false
    env:
      AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
      AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      AZURE_TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
      AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
      AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
    strategy:
      fail-fast: false
      matrix:
        settings:
          - host: macos-latest
            target: x86_64-apple-darwin
            platform_flag: --mac --x64
          - host: macos-latest
            target: aarch64-apple-darwin
            platform_flag: --mac --arm64
          # github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain
          - host: "windows-2025"
            target: aarch64-pc-windows-msvc
            platform_flag: --win --arm64
          - host: "blacksmith-4vcpu-windows-2025"
            target: x86_64-pc-windows-msvc
            platform_flag: --win
          - host: "blacksmith-4vcpu-ubuntu-2404"
            target: x86_64-unknown-linux-gnu
            platform_flag: --linux
          - host: "blacksmith-4vcpu-ubuntu-2404"
            target: aarch64-unknown-linux-gnu
            platform_flag: --linux
    runs-on: ${{ matrix.settings.host }}
    # if: github.ref_name == 'beta'
    steps:
      - uses: actions/checkout@v3

      - uses: apple-actions/import-codesign-certs@v2
        if: runner.os == 'macOS'
        with:
          keychain: build
          p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
          p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}

      - name: Setup Apple API Key
        if: runner.os == 'macOS'
        run: echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8

      - uses: ./.github/actions/setup-bun

      - name: Azure login
        if: runner.os == 'Windows'
        uses: azure/login@v2
        with:
          client-id: ${{ env.AZURE_CLIENT_ID }}
          tenant-id: ${{ env.AZURE_TENANT_ID }}
          subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}

      - uses: actions/setup-node@v4
        with:
          node-version: "24"

      - name: Cache apt packages
        if: contains(matrix.settings.host, 'ubuntu')
        uses: actions/cache@v4
        with:
          path: ~/apt-cache
          key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-${{ hashFiles('.github/workflows/publish.yml') }}
          restore-keys: |
            ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-

      - name: Install dependencies (ubuntu only)
        if: contains(matrix.settings.host, 'ubuntu')
        run: |
          mkdir -p ~/apt-cache && chmod -R a+rw ~/apt-cache
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends -o dir::cache::archives="$HOME/apt-cache" rpm
          sudo chmod -R a+rw ~/apt-cache

      - name: Setup git committer
        id: committer
        uses: ./.github/actions/setup-git-committer
        with:
          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}

      - name: Prepare
        run: bun ./scripts/prepare.ts
        working-directory: packages/desktop-electron
        env:
          OPENCODE_VERSION: ${{ needs.version.outputs.version }}
          OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
          OPENCODE_CLI_ARTIFACT: ${{ (runner.os == 'Windows' && 'opencode-cli-windows') || 'opencode-cli' }}
          RUST_TARGET: ${{ matrix.settings.target }}
          GH_TOKEN: ${{ github.token }}
          GITHUB_RUN_ID: ${{ github.run_id }}

      - name: Build
        run: bun run build
        working-directory: packages/desktop-electron
        env:
          OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}

      - name: Package and publish
        if: needs.version.outputs.release
        run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish always --config electron-builder.config.ts
        working-directory: packages/desktop-electron
        timeout-minutes: 60
        env:
          OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
          GH_TOKEN: ${{ steps.committer.outputs.token }}
          CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
          CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
          APPLE_API_KEY: ${{ runner.temp }}/apple-api-key.p8
          APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
          APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}

      - name: Package (no publish)
        if: ${{ !needs.version.outputs.release }}
        run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish never --config electron-builder.config.ts
        working-directory: packages/desktop-electron
        timeout-minutes: 60
        env:
          OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}

      - name: Verify signed Windows Electron artifacts
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $files = @()
          $files += Get-ChildItem "${{ github.workspace }}\packages\desktop-electron\dist\*.exe" | Select-Object -ExpandProperty FullName
          $files += Get-ChildItem "${{ github.workspace }}\packages\desktop-electron\dist\*unpacked\*.exe" | Select-Object -ExpandProperty FullName
          $files += Get-ChildItem "${{ github.workspace }}\packages\desktop-electron\dist\*unpacked\resources\opencode-cli.exe" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName

          foreach ($file in $files | Select-Object -Unique) {
            $sig = Get-AuthenticodeSignature $file
            if ($sig.Status -ne "Valid") {
              throw "Invalid signature for ${file}: $($sig.Status)"
            }
          }

      - uses: actions/upload-artifact@v4
        with:
          name: opencode-electron-${{ matrix.settings.target }}
          path: packages/desktop-electron/dist/*

      - uses: actions/upload-artifact@v4
        if: needs.version.outputs.release
        with:
          name: latest-yml-${{ matrix.settings.target }}
          path: packages/desktop-electron/dist/latest*.yml

  publish:
    needs:
      - version
      - build-cli
      - sign-cli-windows
      - build-tauri
      - build-electron
    runs-on: blacksmith-4vcpu-ubuntu-2404
    steps:
      - uses: actions/checkout@v3

      - uses: ./.github/actions/setup-bun

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - uses: actions/setup-node@v4
        with:
          node-version: "24"
          registry-url: "https://registry.npmjs.org"

      - name: Setup git committer
        id: committer
        uses: ./.github/actions/setup-git-committer
        with:
          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}

      - uses: actions/download-artifact@v4
        with:
          name: opencode-cli
          path: packages/opencode/dist

      - uses: actions/download-artifact@v4
        with:
          name: opencode-cli-windows
          path: packages/opencode/dist

      - uses: actions/download-artifact@v4
        with:
          name: opencode-cli-signed-windows
          path: packages/opencode/dist

      - uses: actions/download-artifact@v4
        if: needs.version.outputs.release
        with:
          pattern: latest-yml-*
          path: /tmp/latest-yml

      - name: Cache apt packages (AUR)
        uses: actions/cache@v4
        with:
          path: /var/cache/apt/archives
          key: ${{ runner.os }}-apt-aur-${{ hashFiles('.github/workflows/publish.yml') }}
          restore-keys: |
            ${{ runner.os }}-apt-aur-

      - name: Setup SSH for AUR
        run: |
          sudo apt-get update
          sudo apt-get install -y pacman-package-manager
          mkdir -p ~/.ssh
          echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          git config --global user.email "opencode@sst.dev"
          git config --global user.name "opencode"
          ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true

      - run: ./script/publish.ts
        env:
          OPENCODE_VERSION: ${{ needs.version.outputs.version }}
          OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
          AUR_KEY: ${{ secrets.AUR_KEY }}
          GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
          GH_REPO: ${{ needs.version.outputs.repo }}
          NPM_CONFIG_PROVENANCE: false
          LATEST_YML_DIR: /tmp/latest-yml
publish-github-action perms .github/workflows/publish-github-action.yml
Triggers
workflow_dispatch, push
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
publish
Commands
  • git fetch --force --tags
  • git config --global user.email "opencode@sst.dev" git config --global user.name "opencode" ./script/publish
View raw YAML
name: publish-github-action

on:
  workflow_dispatch:
  push:
    tags:
      - "github-v*.*.*"
      - "!github-v1"

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
  contents: write

jobs:
  publish:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - run: git fetch --force --tags

      - name: Publish
        run: |
          git config --global user.email "opencode@sst.dev"
          git config --global user.name "opencode"
          ./script/publish
        working-directory: ./github
publish-vscode perms .github/workflows/publish-vscode.yml
Triggers
workflow_dispatch, push
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
publish
Commands
  • git fetch --force --tags
  • bun install -g @vscode/vsce
  • bun install
  • ./script/publish
View raw YAML
name: publish-vscode

on:
  workflow_dispatch:
  push:
    tags:
      - "vscode-v*.*.*"

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
  contents: write

jobs:
  publish:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - uses: ./.github/actions/setup-bun

      - run: git fetch --force --tags
      - run: bun install -g @vscode/vsce

      - name: Install extension dependencies
        run: bun install
        working-directory: ./sdks/vscode

      - name: Publish
        run: |
          ./script/publish
        working-directory: ./sdks/vscode
        env:
          VSCE_PAT: ${{ secrets.VSCE_PAT }}
          OPENVSX_TOKEN: ${{ secrets.OPENVSX_TOKEN }}
release-github-action perms .github/workflows/release-github-action.yml
Triggers
push
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
release
Commands
  • git fetch --force --tags
  • git config --global user.email "opencode@sst.dev" git config --global user.name "opencode" ./github/script/release
View raw YAML
name: release-github-action

on:
  push:
    branches:
      - dev
    paths:
      - "github/**"

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
  contents: write

jobs:
  release:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - run: git fetch --force --tags

      - name: Release
        run: |
          git config --global user.email "opencode@sst.dev"
          git config --global user.name "opencode"
          ./github/script/release
review .github/workflows/review.yml
Triggers
issue_comment
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
check-guidelines
Commands
  • if [ "${{ github.event_name }}" = "pull_request_target" ]; then echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT else echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT fi
  • curl -fsSL https://opencode.ai/install | bash
  • gh api /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }} > pr_data.json echo "title=$(jq -r .title pr_data.json)" >> $GITHUB_OUTPUT echo "sha=$(jq -r .head.sha pr_data.json)" >> $GITHUB_OUTPUT
  • PR_BODY=$(jq -r .body pr_data.json) opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${PR_TITLE}' <pr-number> ${{ steps.pr-number.outputs.number }} </pr-number> <pr-description> $PR_BODY </pr-description> Please check all the code changes in this pull request against the style guide, also look for any bugs if they exist. Diffs are important but make sure you read the entire file to get proper context. Make it clear the suggestions are merely suggestions and the human can decide what to do When critiquing code against the style guide, be sure that the code is ACTUALLY in violation, don't complain about else statements if they already use early returns there. You may complain about excessive nesting though, regardless of else statement usage. When critiquing code style don't be a zealot, we don't like "let" statements but sometimes they are the simplest option, if someone does a bunch of nesting with let, they should consider using iife (see packages/opencode/src/util.iife.ts) Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block. If you are writing suggested fixes, BE SURE THAT the change you are recommending is actually valid typescript, often I have seen missing closing "}" or other syntax errors. Generally, write a comment instead of writing suggested change if you can help it. Command MUST be like this. \`\`\` gh api \ --method POST \ -H \"Accept: application/vnd.github+json\" \ -H \"X-GitHub-Api-Version: 2022-11-28\" \ /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }}/comments \ -f 'body=[summary of issue]' -f 'commit_id=${{ steps.pr-details.outputs.sha }}' -f 'path=[path-to-file]' -F \"line=[line]\" -f 'side=RIGHT' \`\`\` Only create comments for actual violations. If the code follows all guidelines, comment on the issue using gh cli: 'lgtm' AND NOTHING ELSE!!!!."
View raw YAML
name: review

on:
  issue_comment:
    types: [created]

jobs:
  check-guidelines:
    if: |
      github.event.issue.pull_request &&
      startsWith(github.event.comment.body, '/review') &&
      contains(fromJson('["OWNER","MEMBER"]'), github.event.comment.author_association)
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      contents: read
      pull-requests: write
    steps:
      - name: Get PR number
        id: pr-number
        run: |
          if [ "${{ github.event_name }}" = "pull_request_target" ]; then
            echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
          else
            echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
          fi

      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - uses: ./.github/actions/setup-bun

      - name: Install opencode
        run: curl -fsSL https://opencode.ai/install | bash

      - name: Get PR details
        id: pr-details
        run: |
          gh api /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }} > pr_data.json
          echo "title=$(jq -r .title pr_data.json)" >> $GITHUB_OUTPUT
          echo "sha=$(jq -r .head.sha pr_data.json)" >> $GITHUB_OUTPUT
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Check PR guidelines compliance
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OPENCODE_PERMISSION: '{ "bash": {  "*": "deny", "gh*": "allow", "gh pr review*": "deny" } }'
          PR_TITLE: ${{ steps.pr-details.outputs.title }}
        run: |
          PR_BODY=$(jq -r .body pr_data.json)
          opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${PR_TITLE}'

          <pr-number>
          ${{ steps.pr-number.outputs.number }}
          </pr-number>

          <pr-description>
          $PR_BODY
          </pr-description>

          Please check all the code changes in this pull request against the style guide, also look for any bugs if they exist. Diffs are important but make sure you read the entire file to get proper context. Make it clear the suggestions are merely suggestions and the human can decide what to do

          When critiquing code against the style guide, be sure that the code is ACTUALLY in violation, don't complain about else statements if they already use early returns there. You may complain about excessive nesting though, regardless of else statement usage.
          When critiquing code style don't be a zealot, we don't like "let" statements but sometimes they are the simplest option, if someone does a bunch of nesting with let, they should consider using iife (see packages/opencode/src/util.iife.ts)

          Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block.
          If you are writing suggested fixes, BE SURE THAT the change you are recommending is actually valid typescript, often I have seen missing closing "}" or other syntax errors.
          Generally, write a comment instead of writing suggested change if you can help it.

          Command MUST be like this.
          \`\`\`
          gh api \
            --method POST \
            -H \"Accept: application/vnd.github+json\" \
            -H \"X-GitHub-Api-Version: 2022-11-28\" \
            /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }}/comments \
            -f 'body=[summary of issue]' -f 'commit_id=${{ steps.pr-details.outputs.sha }}' -f 'path=[path-to-file]' -F \"line=[line]\" -f 'side=RIGHT'
          \`\`\`

          Only create comments for actual violations. If the code follows all guidelines, comment on the issue using gh cli: 'lgtm' AND NOTHING ELSE!!!!."
stats .github/workflows/stats.yml
Triggers
schedule, workflow_dispatch
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
stats
Commands
  • bun script/stats.ts
  • git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git add STATS.md git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)" git push
View raw YAML
name: stats

on:
  schedule:
    - cron: "0 12 * * *" # Run daily at 12:00 UTC
  workflow_dispatch: # Allow manual trigger

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  stats:
    if: github.repository == 'anomalyco/opencode'
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      contents: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Bun
        uses: ./.github/actions/setup-bun

      - name: Run stats script
        run: bun script/stats.ts

      - name: Commit stats
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add STATS.md
          git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
          git push
        env:
          POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
storybook .github/workflows/storybook.yml
Triggers
push, pull_request, workflow_dispatch
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
build
Commands
  • bun --cwd packages/storybook build
View raw YAML
name: storybook

on:
  push:
    branches: [dev]
    paths:
      - ".github/workflows/storybook.yml"
      - "package.json"
      - "bun.lock"
      - "packages/storybook/**"
      - "packages/ui/**"
  pull_request:
    branches: [dev]
    paths:
      - ".github/workflows/storybook.yml"
      - "package.json"
      - "bun.lock"
      - "packages/storybook/**"
      - "packages/ui/**"
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build:
    name: storybook build
    runs-on: blacksmith-4vcpu-ubuntu-2404
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Bun
        uses: ./.github/actions/setup-bun

      - name: Build Storybook
        run: bun --cwd packages/storybook build
sync-zed-extension .github/workflows/sync-zed-extension.yml
Triggers
workflow_dispatch, release
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
zed
Commands
  • if [ "${{ github.event_name }}" = "release" ]; then TAG="${{ github.event.release.tag_name }}" else TAG=$(git tag --list 'v[0-9]*.*' --sort=-version:refname | head -n 1) fi echo "tag=${TAG}" >> $GITHUB_OUTPUT echo "Using tag: ${TAG}"
  • ./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }}
View raw YAML
name: "sync-zed-extension"

on:
  workflow_dispatch:
  release:
    types: [published]

jobs:
  zed:
    name: Release Zed Extension
    runs-on: blacksmith-4vcpu-ubuntu-2404
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: ./.github/actions/setup-bun

      - name: Get version tag
        id: get_tag
        run: |
          if [ "${{ github.event_name }}" = "release" ]; then
            TAG="${{ github.event.release.tag_name }}"
          else
            TAG=$(git tag --list 'v[0-9]*.*' --sort=-version:refname | head -n 1)
          fi
          echo "tag=${TAG}" >> $GITHUB_OUTPUT
          echo "Using tag: ${TAG}"

      - name: Sync Zed extension
        run: |
          ./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }}
        env:
          ZED_EXTENSIONS_PAT: ${{ secrets.ZED_EXTENSIONS_PAT }}
          ZED_PR_PAT: ${{ secrets.ZED_PR_PAT }}
test matrix perms .github/workflows/test.yml
Triggers
push, pull_request, workflow_dispatch
Runs on
${{ matrix.settings.host }}, ${{ matrix.settings.host }}
Jobs
unit, e2e
Matrix
settings, settings.host, settings.name→ blacksmith-4vcpu-ubuntu-2404, blacksmith-4vcpu-windows-2025, linux, windows
Commands
  • git config --global user.email "bot@opencode.ai" git config --global user.name "opencode"
  • bun turbo test
  • version=$(node -e 'console.log(require("./packages/app/package.json").devDependencies["@playwright/test"])') echo "version=$version" >> "$GITHUB_OUTPUT"
  • bunx playwright install-deps chromium
  • bunx playwright install chromium
  • bun --cwd packages/app test:e2e:local
View raw YAML
name: test

on:
  push:
    branches:
      - dev
  pull_request:
  workflow_dispatch:

concurrency:
  # Keep every run on dev so cancelled checks do not pollute the default branch
  # commit history. PRs and other branches still share a group and cancel stale runs.
  group: ${{ case(github.ref == 'refs/heads/dev', format('{0}-{1}', github.workflow, github.run_id), format('{0}-{1}', github.workflow, github.event.pull_request.number || github.ref)) }}
  cancel-in-progress: true

permissions:
  contents: read

jobs:
  unit:
    name: unit (${{ matrix.settings.name }})
    strategy:
      fail-fast: false
      matrix:
        settings:
          - name: linux
            host: blacksmith-4vcpu-ubuntu-2404
          - name: windows
            host: blacksmith-4vcpu-windows-2025
    runs-on: ${{ matrix.settings.host }}
    defaults:
      run:
        shell: bash
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup Bun
        uses: ./.github/actions/setup-bun

      - name: Configure git identity
        run: |
          git config --global user.email "bot@opencode.ai"
          git config --global user.name "opencode"

      - name: Run unit tests
        run: bun turbo test

  e2e:
    name: e2e (${{ matrix.settings.name }})
    strategy:
      fail-fast: false
      matrix:
        settings:
          - name: linux
            host: blacksmith-4vcpu-ubuntu-2404
          - name: windows
            host: blacksmith-4vcpu-windows-2025
    runs-on: ${{ matrix.settings.host }}
    env:
      PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers
    defaults:
      run:
        shell: bash
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup Bun
        uses: ./.github/actions/setup-bun

      - name: Read Playwright version
        id: playwright-version
        run: |
          version=$(node -e 'console.log(require("./packages/app/package.json").devDependencies["@playwright/test"])')
          echo "version=$version" >> "$GITHUB_OUTPUT"

      - name: Cache Playwright browsers
        id: playwright-cache
        uses: actions/cache@v4
        with:
          path: ${{ github.workspace }}/.playwright-browsers
          key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright-version.outputs.version }}-chromium

      - name: Install Playwright system dependencies
        if: runner.os == 'Linux'
        working-directory: packages/app
        run: bunx playwright install-deps chromium

      - name: Install Playwright browsers
        if: steps.playwright-cache.outputs.cache-hit != 'true'
        working-directory: packages/app
        run: bunx playwright install chromium

      - name: Run app e2e tests
        run: bun --cwd packages/app test:e2e:local
        env:
          CI: true
        timeout-minutes: 30

      - name: Upload Playwright artifacts
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-${{ matrix.settings.name }}-${{ github.run_attempt }}
          if-no-files-found: ignore
          retention-days: 7
          path: |
            packages/app/e2e/test-results
            packages/app/e2e/playwright-report
triage .github/workflows/triage.yml
Triggers
issues
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
triage
Commands
  • curl -fsSL https://opencode.ai/install | bash
  • opencode run --agent triage "The following issue was just opened, triage it: Title: $ISSUE_TITLE $ISSUE_BODY"
View raw YAML
name: triage

on:
  issues:
    types: [opened]

jobs:
  triage:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    permissions:
      contents: read
      issues: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Setup Bun
        uses: ./.github/actions/setup-bun

      - name: Install opencode
        run: curl -fsSL https://opencode.ai/install | bash

      - name: Triage issue
        env:
          OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          ISSUE_NUMBER: ${{ github.event.issue.number }}
          ISSUE_TITLE: ${{ github.event.issue.title }}
          ISSUE_BODY: ${{ github.event.issue.body }}
        run: |
          opencode run --agent triage "The following issue was just opened, triage it:

          Title: $ISSUE_TITLE

          $ISSUE_BODY"
typecheck .github/workflows/typecheck.yml
Triggers
push, pull_request, workflow_dispatch
Runs on
blacksmith-4vcpu-ubuntu-2404
Jobs
typecheck
Commands
  • bun typecheck
View raw YAML
name: typecheck

on:
  push:
    branches: [dev]
  pull_request:
    branches: [dev]
  workflow_dispatch:

jobs:
  typecheck:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Bun
        uses: ./.github/actions/setup-bun

      - name: Run typecheck
        run: bun typecheck
vouch-check-issue perms .github/workflows/vouch-check-issue.yml
Triggers
issues
Runs on
ubuntu-latest
Jobs
check
View raw YAML
name: vouch-check-issue

on:
  issues:
    types: [opened]

permissions:
  contents: read
  issues: write

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Check if issue author is denounced
        uses: actions/github-script@v7
        with:
          script: |
            const author = context.payload.issue.user.login;
            const issueNumber = context.payload.issue.number;

            // Skip bots
            if (author.endsWith('[bot]')) {
              core.info(`Skipping bot: ${author}`);
              return;
            }

            // Read the VOUCHED.td file via API (no checkout needed)
            let content;
            try {
              const response = await github.rest.repos.getContent({
                owner: context.repo.owner,
                repo: context.repo.repo,
                path: '.github/VOUCHED.td',
              });
              content = Buffer.from(response.data.content, 'base64').toString('utf-8');
            } catch (error) {
              if (error.status === 404) {
                core.info('No .github/VOUCHED.td file found, skipping check.');
                return;
              }
              throw error;
            }

            // Parse the .td file for vouched and denounced users
            const vouched = new Set();
            const denounced = new Map();
            for (const line of content.split('\n')) {
              const trimmed = line.trim();
              if (!trimmed || trimmed.startsWith('#')) continue;

              const isDenounced = trimmed.startsWith('-');
              const rest = isDenounced ? trimmed.slice(1).trim() : trimmed;
              if (!rest) continue;

              const spaceIdx = rest.indexOf(' ');
              const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
              const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();

              // Handle platform:username or bare username
              // Only match bare usernames or github: prefix (skip other platforms)
              const colonIdx = handle.indexOf(':');
              if (colonIdx !== -1) {
                const platform = handle.slice(0, colonIdx).toLowerCase();
                if (platform !== 'github') continue;
              }
              const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
              if (!username) continue;

              if (isDenounced) {
                denounced.set(username.toLowerCase(), reason);
                continue;
              }

              vouched.add(username.toLowerCase());
            }

            // Check if the author is denounced
            const reason = denounced.get(author.toLowerCase());
            if (reason !== undefined) {
              // Author is denounced — close the issue
              const body = 'This issue has been automatically closed.';

              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: issueNumber,
                body,
              });

              await github.rest.issues.update({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: issueNumber,
                state: 'closed',
                state_reason: 'not_planned',
              });

              core.info(`Closed issue #${issueNumber} from denounced user ${author}`);
              return;
            }

            // Author is positively vouched — add label
            if (!vouched.has(author.toLowerCase())) {
              core.info(`User ${author} is not denounced or vouched. Allowing issue.`);
              return;
            }

            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: issueNumber,
              labels: ['Vouched'],
            });

            core.info(`Added vouched label to issue #${issueNumber} from ${author}`);
vouch-check-pr perms .github/workflows/vouch-check-pr.yml
Triggers
pull_request_target
Runs on
ubuntu-latest
Jobs
check
View raw YAML
name: vouch-check-pr

on:
  pull_request_target:
    types: [opened]

permissions:
  contents: read
  issues: write
  pull-requests: write

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Check if PR author is denounced
        uses: actions/github-script@v7
        with:
          script: |
            const author = context.payload.pull_request.user.login;
            const prNumber = context.payload.pull_request.number;

            // Skip bots
            if (author.endsWith('[bot]')) {
              core.info(`Skipping bot: ${author}`);
              return;
            }

            // Read the VOUCHED.td file via API (no checkout needed)
            let content;
            try {
              const response = await github.rest.repos.getContent({
                owner: context.repo.owner,
                repo: context.repo.repo,
                path: '.github/VOUCHED.td',
              });
              content = Buffer.from(response.data.content, 'base64').toString('utf-8');
            } catch (error) {
              if (error.status === 404) {
                core.info('No .github/VOUCHED.td file found, skipping check.');
                return;
              }
              throw error;
            }

            // Parse the .td file for vouched and denounced users
            const vouched = new Set();
            const denounced = new Map();
            for (const line of content.split('\n')) {
              const trimmed = line.trim();
              if (!trimmed || trimmed.startsWith('#')) continue;

              const isDenounced = trimmed.startsWith('-');
              const rest = isDenounced ? trimmed.slice(1).trim() : trimmed;
              if (!rest) continue;

              const spaceIdx = rest.indexOf(' ');
              const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
              const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();

              // Handle platform:username or bare username
              // Only match bare usernames or github: prefix (skip other platforms)
              const colonIdx = handle.indexOf(':');
              if (colonIdx !== -1) {
                const platform = handle.slice(0, colonIdx).toLowerCase();
                if (platform !== 'github') continue;
              }
              const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
              if (!username) continue;

              if (isDenounced) {
                denounced.set(username.toLowerCase(), reason);
                continue;
              }

              vouched.add(username.toLowerCase());
            }

            // Check if the author is denounced
            const reason = denounced.get(author.toLowerCase());
            if (reason !== undefined) {
              // Author is denounced — close the PR
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: prNumber,
                body: 'This pull request has been automatically closed.',
              });

              await github.rest.pulls.update({
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: prNumber,
                state: 'closed',
              });

              core.info(`Closed PR #${prNumber} from denounced user ${author}`);
              return;
            }

            // Author is positively vouched — add label
            if (!vouched.has(author.toLowerCase())) {
              core.info(`User ${author} is not denounced or vouched. Allowing PR.`);
              return;
            }

            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: prNumber,
              labels: ['Vouched'],
            });

            core.info(`Added vouched label to PR #${prNumber} from ${author}`);
vouch-manage-by-issue perms .github/workflows/vouch-manage-by-issue.yml
Triggers
issue_comment
Runs on
ubuntu-latest
Jobs
manage
Actions
mitchellh/vouch/action/manage-by-issue
View raw YAML
name: vouch-manage-by-issue

on:
  issue_comment:
    types: [created]

concurrency:
  group: vouch-manage
  cancel-in-progress: false

permissions:
  contents: write
  issues: write
  pull-requests: read

jobs:
  manage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false
          fetch-depth: 0

      - name: Setup git committer
        id: committer
        uses: ./.github/actions/setup-git-committer
        with:
          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}

      - uses: mitchellh/vouch/action/manage-by-issue@main
        with:
          issue-id: ${{ github.event.issue.number }}
          comment-id: ${{ github.event.comment.id }}
          roles: admin,maintain,write
        env:
          GITHUB_TOKEN: ${{ steps.committer.outputs.token }}