name: Claude Code on: issue_comment: types: [created] pull_request_review_comment: types: [created] pull_request_review: types: [submitted] issues: types: [opened] repository_dispatch: types: [claude-core-team-issues] permissions: {} jobs: claude: if: | ( github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.user.type != 'Bot' && contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) ) || ( github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.user.type != 'Bot' && contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) ) || ( github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && github.event.review.user.type != 'Bot' && contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association) ) || ( github.event_name == 'issues' && github.event.action == 'opened' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association) ) # Job-level (not workflow-level) concurrency: skipped jobs never enter the # group, so PR chatter without @claude can't evict a queued Claude run. concurrency: group: claude-code-${{ github.event.issue.number || github.event.pull_request.number }} cancel-in-progress: false runs-on: ubuntu-latest timeout-minutes: 60 permissions: contents: write pull-requests: write issues: write id-token: write services: postgres: image: postgres:16 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 redis: image: redis ports: - 6379:6379 steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 - name: Install dependencies uses: ./.github/actions/yarn-install - name: Run Claude Code id: claude-code uses: anthropics/claude-code-action@ac7e24bf2938964b8ab203e417a2773802392ddd # v1.0.146 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} additional_permissions: | actions: read claude_args: '--max-turns 200 --model opus --allowedTools "Edit,Write,WebFetch,Bash(bash packages/twenty-utils/setup-dev-env.sh),Bash(npx nx *),Bash(npx jest *),Bash(yarn *),Bash(git *),Bash(gh *),Bash(sed *),Bash(python3 *),Bash(rm *),Bash(find *),Bash(grep *),Bash(cat *),Bash(ls *),Bash(head *),Bash(tail *),Bash(wc *),Bash(sort *),Bash(uniq *),Bash(mkdir *),Bash(cp *),Bash(mv *),Bash(touch *),Bash(chmod *),Bash(echo *),Bash(curl *),Bash(cd *),Bash(pwd *),Bash(diff *),Bash(xargs *),Bash(awk *),Bash(cut *),Bash(tee *),Bash(tr *)"' settings: | { "env": { "PG_DATABASE_URL": "postgres://postgres:postgres@localhost:5432/default" } } - name: Post Create-PR link if Claude ran out of turns if: failure() env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | BRANCH=$(git branch --show-current) if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "" ]; then exit 0 fi AHEAD=$(git rev-list --count main.."$BRANCH" 2>/dev/null || echo "0") if [ "$AHEAD" = "0" ]; then exit 0 fi EXISTING_PR=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || echo "") if [ -n "$EXISTING_PR" ]; then exit 0 fi ISSUE_NUMBER="${{ github.event.issue.number || github.event.pull_request.number }}" ENCODED_BRANCH=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1], safe=''))" "$BRANCH") PR_URL="https://github.com/${{ github.repository }}/compare/main...${ENCODED_BRANCH}?quick_pull=1" BODY="⚠️ Claude ran out of turns before creating a PR. Work has been pushed to [\`$BRANCH\`](https://github.com/${{ github.repository }}/tree/$ENCODED_BRANCH).\n\n[**Create PR →**]($PR_URL)" if [ -n "$ISSUE_NUMBER" ]; then gh issue comment "$ISSUE_NUMBER" --body "$(echo -e "$BODY")" fi claude-cross-repo: if: github.event_name == 'repository_dispatch' concurrency: group: claude-cross-repo-${{ github.event.client_payload.repo_full_name }}-${{ github.event.client_payload.issue_number }} cancel-in-progress: false runs-on: ubuntu-latest timeout-minutes: 60 permissions: contents: write pull-requests: write issues: write id-token: write services: postgres: image: postgres:16 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 redis: image: redis ports: - 6379:6379 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 - name: Install dependencies uses: ./.github/actions/yarn-install - name: Build prompt from dispatch payload id: prompt uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const p = context.payload.client_payload; let prompt; if (p.comment_body) { prompt = `You are responding to a comment on issue #${p.issue_number} ("${p.issue_title}") in the ${p.repo_full_name} repository.\n\nThe comment by @${p.sender} says:\n\n${p.comment_body}\n\nIssue body:\n\n${p.issue_body}\n\nPlease help with this request. The code you are working with is the twenty codebase (this repository).`; } else { prompt = `You are responding to issue #${p.issue_number} ("${p.issue_title}") in the ${p.repo_full_name} repository, opened by @${p.sender}.\n\nIssue body:\n\n${p.issue_body}\n\nPlease help with this request. The code you are working with is the twenty codebase (this repository).`; } core.setOutput('prompt', prompt); core.setOutput('repo', p.repo_full_name); core.setOutput('issue_number', p.issue_number); - name: Run Claude Code id: claude uses: anthropics/claude-code-action@ac7e24bf2938964b8ab203e417a2773802392ddd # v1.0.146 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} prompt: ${{ steps.prompt.outputs.prompt }} additional_permissions: | actions: read claude_args: '--max-turns 200 --model opus --allowedTools "Edit,Write,WebFetch,Bash(bash packages/twenty-utils/setup-dev-env.sh),Bash(npx nx *),Bash(npx jest *),Bash(yarn *),Bash(git *),Bash(gh *),Bash(sed *),Bash(python3 *),Bash(rm *),Bash(find *),Bash(grep *),Bash(cat *),Bash(ls *),Bash(head *),Bash(tail *),Bash(wc *),Bash(sort *),Bash(uniq *),Bash(mkdir *),Bash(cp *),Bash(mv *),Bash(touch *),Bash(chmod *),Bash(echo *),Bash(curl *),Bash(cd *),Bash(pwd *),Bash(diff *),Bash(xargs *),Bash(awk *),Bash(cut *),Bash(tee *),Bash(tr *)"' settings: | { "env": { "PG_DATABASE_URL": "postgres://postgres:postgres@localhost:5432/default" } } - name: Mint ci-privileged dispatch token id: app-token if: always() uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ vars.TWENTY_WORKFLOW_DISPATCHER_CLIENT_ID }} private-key: ${{ secrets.TWENTY_WORKFLOW_DISPATCHER_PRIVATE_KEY }} owner: twentyhq repositories: ci-privileged permission-actions: write - name: Dispatch response to ci-privileged if: always() env: GH_TOKEN: ${{ steps.app-token.outputs.token }} REPO: ${{ steps.prompt.outputs.repo }} ISSUE_NUMBER: ${{ steps.prompt.outputs.issue_number }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | gh workflow run post-claude-response.yaml --repo twentyhq/ci-privileged --ref main \ -f repo="$REPO" \ -f issue_number="$ISSUE_NUMBER" \ -f run_url="$RUN_URL"