Collect consent in the chat widget before the first message #20517
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Claude Code Workflow | |
| # | |
| # Runs Claude Code against issues and PR/issue conversations in two modes: | |
| # - Scheduled/manual (find-work + scheduled jobs): picks the oldest open | |
| # issue labelled `claude` that doesn't already have an open PR, and | |
| # implements its next unchecked task. | |
| # - Event-driven (claude job): responds to an `@claude` mention in an issue | |
| # comment, PR review comment, or PR review, or to an issue being labelled | |
| # `claude`. | |
| # | |
| # Triggered on: daily schedule, manual dispatch, `@claude` mention (issue | |
| # comment, PR review comment, or PR review), or issue labelled `claude` | |
| # Requirements: ANTHROPIC_API_KEY secret must be configured | |
| # | |
| # Notes: | |
| # - Applies the `claude` label to PRs it opens when instructed by the prompt | |
| # (the scheduled/manual mode aims to create one PR per completed task). | |
| # - Runs are grouped per issue/PR (`cancel-in-progress: false`), so a | |
| # second trigger for the same issue or PR waits for the in-progress run | |
| # to finish rather than replacing it. | |
| # - The `claude` job has no actor/permission gate of its own — it relies on | |
| # claude-code-action's built-in check that the triggering user has write | |
| # access to this repo. A mention or review from anyone without write | |
| # access (most fork PR authors included) fails the "Run Claude Code" step | |
| # with "Action failed with error: User does not have write access on this | |
| # repository" rather than being silently skipped. | |
| name: Claude Code | |
| on: | |
| schedule: | |
| - cron: '0 2 * * *' # Daily at 2am UTC — picks up next incremental task | |
| workflow_dispatch: | |
| inputs: | |
| issue_number: | |
| description: 'Specific issue number to work on (optional)' | |
| required: false | |
| type: string | |
| issue_comment: | |
| types: [created] | |
| pull_request_review_comment: | |
| types: [created] | |
| issues: | |
| types: [labeled] | |
| pull_request_review: | |
| types: [submitted] | |
| concurrency: | |
| group: >- | |
| claude-${{ | |
| github.event_name == 'schedule' && 'scheduled' || | |
| github.event_name == 'workflow_dispatch' && format('dispatch-{0}', inputs.issue_number) || | |
| github.event.issue.number || | |
| github.event.pull_request.number || | |
| github.run_id | |
| }} | |
| cancel-in-progress: false | |
| # Restrict default permissions — jobs override as needed | |
| permissions: | |
| contents: read | |
| issues: read | |
| pull-requests: read | |
| jobs: | |
| # ── Scheduled / manual: find the next issue to work on ── | |
| find-work: | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| issue_number: ${{ steps.find.outputs.issue_number }} | |
| issue_body: ${{ steps.find.outputs.issue_body }} | |
| issue_title: ${{ steps.find.outputs.issue_title }} | |
| steps: | |
| # Audit only: records egress without blocking. Flip to `block` plus an | |
| # `allowed-endpoints` list once the recorded traffic looks stable. | |
| - name: Harden the runner | |
| uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 | |
| with: | |
| egress-policy: audit | |
| - name: Find oldest labeled issue without an open PR | |
| id: find | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| INPUT_ISSUE_NUMBER: ${{ inputs.issue_number }} | |
| run: | | |
| REPO="${{ github.repository }}" | |
| # Get head branch names of all open PRs with the label | |
| OPEN_PR_BRANCHES=$(gh pr list --repo "$REPO" --label "claude" --state open --json headRefName --jq '.[].headRefName') | |
| if [ -n "$INPUT_ISSUE_NUMBER" ]; then | |
| # Manual dispatch: use the specified issue but check for existing PR | |
| ISSUE_NUMBER="$INPUT_ISSUE_NUMBER" | |
| if echo "$OPEN_PR_BRANCHES" | grep -q "^claude/${ISSUE_NUMBER}-"; then | |
| echo "::notice::Issue #$ISSUE_NUMBER already has an open PR, skipping" | |
| exit 0 | |
| fi | |
| else | |
| # Scheduled: find the oldest issue that doesn't already have an open PR | |
| ISSUES=$(gh issue list --repo "$REPO" --label "claude" --state open --json number,createdAt --jq 'sort_by(.createdAt) | .[].number') | |
| ISSUE_NUMBER="" | |
| for CANDIDATE in $ISSUES; do | |
| if ! echo "$OPEN_PR_BRANCHES" | grep -q "^claude/${CANDIDATE}-"; then | |
| ISSUE_NUMBER="$CANDIDATE" | |
| break | |
| fi | |
| echo "::notice::Issue #$CANDIDATE already has an open PR, skipping" | |
| done | |
| fi | |
| if [ -z "$ISSUE_NUMBER" ]; then | |
| echo "::notice::No eligible issues found" | |
| exit 0 | |
| fi | |
| echo "issue_number=$ISSUE_NUMBER" >> "$GITHUB_OUTPUT" | |
| # Fetch issue details | |
| ISSUE_DATA=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json title,body) | |
| ISSUE_TITLE=$(echo "$ISSUE_DATA" | jq -r '.title') | |
| ISSUE_BODY=$(echo "$ISSUE_DATA" | jq -r '.body') | |
| DELIMITER="ghadelim_$(openssl rand -hex 8)" | |
| { | |
| echo "issue_title<<$DELIMITER" | |
| echo "$ISSUE_TITLE" | |
| echo "$DELIMITER" | |
| echo "issue_body<<$DELIMITER" | |
| echo "$ISSUE_BODY" | |
| echo "$DELIMITER" | |
| } >> "$GITHUB_OUTPUT" | |
| # ── Scheduled / manual: implement the next task on the found issue ── | |
| scheduled: | |
| needs: find-work | |
| if: needs.find-work.outputs.issue_number != '' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| id-token: write | |
| services: | |
| postgres: | |
| image: pgvector/pgvector:pg16 | |
| env: | |
| POSTGRES_USER: postgres | |
| POSTGRES_PASSWORD: postgres_password | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| redis: | |
| image: redis | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| env: | |
| DJANGO_DATABASE_USER: postgres | |
| DJANGO_DATABASE_PASSWORD: postgres_password | |
| SECRET_KEY: secret-test-key | |
| steps: | |
| # Audit only: records egress without blocking. Flip to `block` plus an | |
| # `allowed-endpoints` list once the recorded traffic looks stable. | |
| - name: Harden the runner | |
| uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: '3.13' | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| enable-cache: true | |
| - name: Install Python dependencies | |
| run: | | |
| uv venv | |
| uv sync --locked --dev | |
| echo "$PWD/.venv/bin" >> "$GITHUB_PATH" | |
| - uses: pnpm/action-setup@v6.0.10 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24.x | |
| cache: 'pnpm' | |
| - name: Install Node dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Create branch | |
| id: branch | |
| run: | | |
| BRANCH_NAME="claude/${{ needs.find-work.outputs.issue_number }}-$(date +%Y%m%d-%H%M%S)" | |
| git checkout -b "$BRANCH_NAME" | |
| echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" | |
| - name: Run Claude Code | |
| id: claude | |
| uses: anthropics/claude-code-action@v1.0.206 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| prompt: | | |
| You are working on an incremental project tracked in issue #${{ needs.find-work.outputs.issue_number }}. | |
| ## Issue Title | |
| ${{ needs.find-work.outputs.issue_title }} | |
| ## Issue Content | |
| ${{ needs.find-work.outputs.issue_body }} | |
| ## Instructions | |
| 1. Read the issue and identify the first unchecked task (- [ ]) that is not marked as blocked | |
| 2. Implement that task, making git commits as you go | |
| 3. Run lint, type checks, and tests, fix any issues: | |
| - Python lint: ruff check --fix && ruff format | |
| - Python type check: uv run ty check apps/ | |
| - JS/TS lint: pnpm run lint | |
| - Python test: pytest path/to/file.py -v | |
| 4. Push the changes you've made to GitHub and create a PR with the 'claude' label. | |
| 5. After completing the task, update the issue using gh issue edit to: | |
| - Check off the completed task (change - [ ] to - [x]) | |
| - Add any learnings to the ## Learnings section (create if needed) | |
| 6. If you cannot complete the task, mark it as blocked (add "blocked:" prefix to the task) | |
| 7. Add a comment to the issue summarizing what you did | |
| If all tasks are complete or blocked, comment on the issue explaining the status. | |
| claude_args: '--allowedTools "Bash(pnpm run lint:*),Bash(pnpm run type-check:*),Bash(pnpm run build:*),Bash(ruff check:*),Bash(ruff format:*),Bash(ty check:*),Bash(uv run ty:*),Bash(uv run ruff:*),Bash(uv run pytest:*),Bash(pytest:*),Bash(uv run --no-project zensical build:*),Bash(git add:*),Bash(git commit:*),Bash(git push:*),Bash(git rm:*),Bash(git checkout:*),Bash(git switch:*),Bash(git status:*),Bash(git diff:*),Bash(gh pr create:*),Bash(gh pr edit:*),Bash(gh api:*),Bash(gh issue edit:*),Bash(gh issue comment:*),Bash(ls:*),Bash(grep:*),Bash(sort:*),Bash(tail:*),Grep,Glob,Read,Write,Edit"' | |
| # ── Event-driven: respond to @claude mentions, issue label ── | |
| claude: | |
| if: | | |
| github.event_name != 'schedule' && github.event_name != 'workflow_dispatch' && ( | |
| (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || | |
| (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || | |
| (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || | |
| (github.event_name == 'issues' && github.event.action == 'labeled' && github.event.label.name == 'claude') | |
| ) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| id-token: write | |
| services: | |
| postgres: | |
| image: pgvector/pgvector:pg16 | |
| env: | |
| POSTGRES_USER: postgres | |
| POSTGRES_PASSWORD: postgres_password | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| redis: | |
| image: redis | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| env: | |
| DJANGO_DATABASE_USER: postgres | |
| DJANGO_DATABASE_PASSWORD: postgres_password | |
| SECRET_KEY: secret-test-key | |
| steps: | |
| # Audit only: records egress without blocking. Flip to `block` plus an | |
| # `allowed-endpoints` list once the recorded traffic looks stable. | |
| - name: Harden the runner | |
| uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: '3.13' | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| enable-cache: true | |
| - name: Install Python dependencies | |
| run: | | |
| uv venv | |
| uv sync --locked --dev | |
| echo "$PWD/.venv/bin" >> "$GITHUB_PATH" | |
| - uses: pnpm/action-setup@v6.0.10 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24.x | |
| cache: 'pnpm' | |
| - name: Install Node dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Run Claude Code | |
| id: claude | |
| uses: anthropics/claude-code-action@v1.0.206 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| branch_prefix: "claude/" | |
| claude_args: | | |
| --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(pnpm run lint:*),Bash(pnpm run type-check:*),Bash(pnpm run build:*),Bash(ruff check:*),Bash(ruff format:*),Bash(ty check:*),Bash(uv run ty:*),Bash(uv run ruff:*),Bash(uv run pytest:*),Bash(pytest:*),Bash(git add:*),Bash(git commit:*),Bash(git push:*),Bash(git checkout:*),Bash(git switch:*),Bash(git status:*),Bash(git diff:*),Bash(git branch:*),Bash(gh pr create:*),Bash(gh pr edit:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh api:*),Bash(gh issue comment:*),Bash(gh issue edit:*),Read,Write,Edit" |