d5c7b735d2
# Introduction Getting rid of the fine grained PAT used to dispatch to internal repositories. Repo dispatch requires the contents write permissions which is too wide for such use Refactored all senders and target to pass through a workflow dispatch instead Creating a centralize app that forges a token with actions: write only provided permissions to mitigate any token exfiltrations
209 lines
9.0 KiB
YAML
209 lines
9.0 KiB
YAML
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"
|