Skip to content
Merged

dev #74

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .claude/learnings.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
- Codex Stop-hook protocol accepts BOTH `{"decision":"block","reason":"..."}` (legacy) and `{"continue":true,"stopReason":"..."}` (new) on stdout with exit 0 to force the agent to take another turn — confirmed against developers.openai.com/codex/hooks. Either format works; the codebase uses the legacy one for parity with the Claude commit-guard.
- `fixAndRetryPush` must dispatch the configured agent's CLI, not hardcode `claude`. When AGENT_KIND=codex the claude binary isn't installed and `|| true` swallows the failure, leaving the same broken HEAD to be force-pushed.

## Claude Code agent auth in sandbox
- The Claude Code CLI rejects OAuth tokens (`sk-ant-oat...`, issued by `claude setup-token`) when supplied via `ANTHROPIC_API_KEY` — that variable only accepts standard API keys (`sk-ant-api...`). OAuth tokens must be exported as `CLAUDE_CODE_OAUTH_TOKEN`. Commit `350a754` ("drop CLAUDE_CODE_OAUTH_TOKEN, require ANTHROPIC_API_KEY for Claude") consolidated to a single operator-facing var but lost the OAuth path; agent runs failed with "Invalid API key" at the research phase. Fix in `src/sandbox/agents/claude.ts:configure` — detect `sk-ant-oat` prefix and route the value to `CLAUDE_CODE_OAUTH_TOKEN` inside the sandbox, so the operator still pastes into a single `ANTHROPIC_API_KEY` env var.

## E2E in GitHub Actions
- `@vercel/sandbox` reads credentials from `process.env` — a GH secret is not enough; it must be mapped via the job's `env:` block (e.g. `VERCEL_OIDC_TOKEN: ${{ secrets.VERCEL_OIDC_TOKEN }}`). Prefer long-lived `VERCEL_TOKEN` + `VERCEL_TEAM_ID` + `VERCEL_PROJECT_ID` over OIDC — OIDC tokens expire in ~12h and the SDK's refresh path requires `.vercel/project.json`, which CI doesn't have.
- Reconcile (`src/lib/reconcile.ts`) has a 30s `ORPHAN_GRACE_MS` window that skips entries younger than 30s. Any e2e test seeding a registry entry via `setEntry` and expecting reconcile to cancel it on the next cron tick must backdate the timestamp past the grace window (`setEntry(key, runId, { ageMs: 60_000 })`). Without backdating the test is racy — it only passes if Vercel's 1-min scheduled cron happens to fire at T>30s during the test's wait window.
8 changes: 4 additions & 4 deletions .claude/skills/init-agent/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
name: init-agent
description: Configure or rotate the agent runtime (Claude or Codex) for the Blazebot workflow. Branches on runtime choice and emits a single paste-template for the chosen kind. Defaults to API key with OAuth alternative documented in references. Use for "set up claude", "set up codex", "rotate anthropic key", "switch agent to codex", "configure agent runtime".
description: Configure or rotate the agent runtime (Claude or Codex) for the Blazebot workflow. Branches on runtime choice and emits a single paste-template for the chosen kind. Use for "set up claude", "set up codex", "rotate anthropic key", "switch agent to codex", "configure agent runtime".
---

# Initialize Agent Runtime

Branch-on-choice skill. Asks **Claude or Codex**, then emits a single paste-template for the chosen runtime. Cross-field rule in `env.ts` (`AGENT_KIND=claude` requires `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN`; `AGENT_KIND=codex` requires `CODEX_API_KEY` or `CODEX_CHATGPT_OAUTH_TOKEN`) is enforced by construction.
Branch-on-choice skill. Asks **Claude or Codex**, then emits a single paste-template for the chosen runtime. Cross-field rule in `env.ts` (`AGENT_KIND=claude` requires `ANTHROPIC_API_KEY`; `AGENT_KIND=codex` requires `CODEX_API_KEY` or `CODEX_CHATGPT_OAUTH_TOKEN`) is enforced by construction.

> If you want full project setup (Jira + VCS + Agent + Slack + Upstash + deploy), invoke `init-env` instead. This skill only handles the agent runtime.

Expand All @@ -28,9 +28,9 @@ If switching from a previously-configured runtime, the user should also remove t

## Step 2 — Emit paste-template

### Claude branch (default API key, OAuth alternative)
### Claude branch

Walk the user through https://console.anthropic.com/settings/keys to create an API key. The default flow uses `ANTHROPIC_API_KEY`; OAuth via `CLAUDE_CODE_OAUTH_TOKEN` is documented in `references/oauth-alternative.md`.
Walk the user through https://console.anthropic.com/settings/keys to create an API key. Codex OAuth is documented in `references/oauth-alternative.md`.

Collect:
- `ANTHROPIC_API_KEY` (starts with `sk-ant-`)
Expand Down
14 changes: 2 additions & 12 deletions .claude/skills/init-agent/references/oauth-alternative.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
# OAuth alternative

Both Claude and Codex accept either an API key or an OAuth token. The default `init-agent` flow uses API keys because they're simpler — one secret, no expiration handling. Use OAuth when:
Codex accepts either an API key or an OAuth token. The default `init-agent` flow uses API keys because they're simpler — one secret, no expiration handling. Use OAuth when:

- Your org issues OAuth tokens via SSO and you can't mint long-lived API keys.
- You want the bot to act under a specific human's account (each request shows up under that account in usage logs).
- You're on a managed plan that doesn't expose API key management.

## Claude — `CLAUDE_CODE_OAUTH_TOKEN`

Replace the API_KEY line in the paste-template with:

```
CLAUDE_CODE_OAUTH_TOKEN=<value>
```

The validator in `env.ts:130` accepts either `ANTHROPIC_API_KEY` *or* `CLAUDE_CODE_OAUTH_TOKEN`. Don't paste both — pick one.

OAuth tokens are obtained via Claude Code's `claude login` flow (run locally, copy the token from the resulting credentials file). They expire — track expiry, set a calendar reminder.
> Claude only supports `ANTHROPIC_API_KEY` in this project — OAuth was removed.

## Codex — `CODEX_CHATGPT_OAUTH_TOKEN`

Expand Down
8 changes: 5 additions & 3 deletions .claude/skills/init-env/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ pnpm tsx --env-file=.env.local env.ts
The validator (`env.ts` via `@t3-oss/env-core`) catches:
- Missing required keys.
- URL/email/UUID format violations.
- Cross-field violations (`VCS_KIND=github` requires `GITHUB_TOKEN/OWNER/REPO`; `AGENT_KIND=claude` requires `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN`; etc.).
- Cross-field violations (`VCS_KIND=github` requires `GITHUB_TOKEN/OWNER/REPO`; `AGENT_KIND=claude` requires `ANTHROPIC_API_KEY`; etc.).

**On failure:** the validator prints `Invalid environment variables:` followed by the specific paths. Identify the responsible subskill from the path prefix (`JIRA_*` → init-jira; `GITHUB_*` / `GITLAB_*` → init-vcs; etc.) and direct the user to fix in the Vercel dashboard, then re-run this step.

Expand Down Expand Up @@ -361,8 +361,10 @@ Skipped (see SETUP.md for the full how-to):
- GitLab swap — SETUP.md §12. Flip VCS_KIND=gitlab and provide
GITLAB_TOKEN + GITLAB_PROJECT_ID (+ GITLAB_HOST for self-hosted).
- CI / GitHub Actions — SETUP.md §11. The `e2e` GitHub environment
needs the prod env vars plus E2E_BASE_URL, E2E_GITHUB_TOKEN/OWNER/
REPO, and VERCEL_AUTOMATION_BYPASS_SECRET as secrets.
needs the prod env vars plus E2E_BASE_URL, E2E_GITHUB_APP_ID,
E2E_GITHUB_APP_PRIVATE_KEY (base64 PEM), E2E_GITHUB_INSTALLATION_ID,
E2E_GITHUB_OWNER, E2E_GITHUB_REPO, and VERCEL_AUTOMATION_BYPASS_SECRET
as secrets.
- Custom domain — point a domain at the Vercel project for a stable
webhook URL (then update Jira webhook + Slack request URLs).
- WORKFLOW_POSTGRES_URL — local dev only (SETUP.md §6).
Expand Down
16 changes: 12 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ JIRA_WEBHOOK_SECRET=
VCS_KIND=github

# --- GitHub (active when VCS_KIND=github) ---
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
# GitHub App auth — see docs/GITHUB-APP-SETUP.md for the registration walkthrough.
# GITHUB_APP_PRIVATE_KEY is base64-encoded PEM (so it round-trips cleanly through
# Vercel's env UI): `base64 -i app.private-key.pem | tr -d '\n'`
GITHUB_APP_ID=1234567
GITHUB_APP_PRIVATE_KEY=base64-encoded-pem-contents
GITHUB_INSTALLATION_ID=98765432
GITHUB_OWNER=your-org
GITHUB_REPO=your-repo
GITHUB_BASE_BRANCH=main
Expand All @@ -39,7 +44,6 @@ AGENT_KIND=claude

# Claude (active when AGENT_KIND=claude — one of API_KEY or OAUTH_TOKEN required)
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxx
# CLAUDE_CODE_OAUTH_TOKEN=
CLAUDE_MODEL=claude-opus-4-6

# Codex (active when AGENT_KIND=codex — one of API_KEY or OAUTH_TOKEN required)
Expand All @@ -49,8 +53,12 @@ CLAUDE_MODEL=claude-opus-4-6
# CODEX_PRICING_URL=https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json
# CODEX_PRICING_TTL_MS=3600000

COMMIT_AUTHOR=ai-workflow-blazity
COMMIT_EMAIL=ai-workflow@blazity.com
# Optional commit-author override. Both must be set together.
# - GitHub: leave unset to author commits as the App's bot user (the GitHub UI
# then renders them with the App's avatar and the `[bot]` badge).
# - GitLab: defaults to ai-workflow-blazity / ai-workflow@blazity.com.
# COMMIT_AUTHOR=
# COMMIT_EMAIL=

# Upstash Redis (run registry).
# On Vercel: install the Upstash for Redis Marketplace integration with the
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ jobs:
COLUMN_AI: ${{ secrets.COLUMN_AI }}
COLUMN_AI_REVIEW: ${{ secrets.COLUMN_AI_REVIEW }}
COLUMN_BACKLOG: ${{ secrets.COLUMN_BACKLOG }}
E2E_GITHUB_TOKEN: ${{ secrets.E2E_GITHUB_TOKEN }}
E2E_GITHUB_APP_ID: ${{ secrets.E2E_GITHUB_APP_ID }}
E2E_GITHUB_APP_PRIVATE_KEY: ${{ secrets.E2E_GITHUB_APP_PRIVATE_KEY }}
E2E_GITHUB_INSTALLATION_ID: ${{ secrets.E2E_GITHUB_INSTALLATION_ID }}
E2E_GITHUB_OWNER: ${{ secrets.E2E_GITHUB_OWNER }}
E2E_GITHUB_REPO: ${{ secrets.E2E_GITHUB_REPO }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
Expand Down Expand Up @@ -93,7 +95,9 @@ jobs:
COLUMN_AI: ${{ secrets.COLUMN_AI }}
COLUMN_AI_REVIEW: ${{ secrets.COLUMN_AI_REVIEW }}
COLUMN_BACKLOG: ${{ secrets.COLUMN_BACKLOG }}
E2E_GITHUB_TOKEN: ${{ secrets.E2E_GITHUB_TOKEN }}
E2E_GITHUB_APP_ID: ${{ secrets.E2E_GITHUB_APP_ID }}
E2E_GITHUB_APP_PRIVATE_KEY: ${{ secrets.E2E_GITHUB_APP_PRIVATE_KEY }}
E2E_GITHUB_INSTALLATION_ID: ${{ secrets.E2E_GITHUB_INSTALLATION_ID }}
E2E_GITHUB_OWNER: ${{ secrets.E2E_GITHUB_OWNER }}
E2E_GITHUB_REPO: ${{ secrets.E2E_GITHUB_REPO }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
Expand Down Expand Up @@ -144,7 +148,9 @@ jobs:
COLUMN_AI: ${{ secrets.COLUMN_AI }}
COLUMN_AI_REVIEW: ${{ secrets.COLUMN_AI_REVIEW }}
COLUMN_BACKLOG: ${{ secrets.COLUMN_BACKLOG }}
E2E_GITHUB_TOKEN: ${{ secrets.E2E_GITHUB_TOKEN }}
E2E_GITHUB_APP_ID: ${{ secrets.E2E_GITHUB_APP_ID }}
E2E_GITHUB_APP_PRIVATE_KEY: ${{ secrets.E2E_GITHUB_APP_PRIVATE_KEY }}
E2E_GITHUB_INSTALLATION_ID: ${{ secrets.E2E_GITHUB_INSTALLATION_ID }}
E2E_GITHUB_OWNER: ${{ secrets.E2E_GITHUB_OWNER }}
E2E_GITHUB_REPO: ${{ secrets.E2E_GITHUB_REPO }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ jobs:
COLUMN_AI: ${{ secrets.COLUMN_AI }}
COLUMN_AI_REVIEW: ${{ secrets.COLUMN_AI_REVIEW }}
COLUMN_BACKLOG: ${{ secrets.COLUMN_BACKLOG }}
E2E_GITHUB_TOKEN: ${{ secrets.E2E_GITHUB_TOKEN }}
E2E_GITHUB_APP_ID: ${{ secrets.E2E_GITHUB_APP_ID }}
E2E_GITHUB_APP_PRIVATE_KEY: ${{ secrets.E2E_GITHUB_APP_PRIVATE_KEY }}
E2E_GITHUB_INSTALLATION_ID: ${{ secrets.E2E_GITHUB_INSTALLATION_ID }}
E2E_GITHUB_OWNER: ${{ secrets.E2E_GITHUB_OWNER }}
E2E_GITHUB_REPO: ${{ secrets.E2E_GITHUB_REPO }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
Expand Down Expand Up @@ -90,7 +92,9 @@ jobs:
COLUMN_AI: ${{ secrets.COLUMN_AI }}
COLUMN_AI_REVIEW: ${{ secrets.COLUMN_AI_REVIEW }}
COLUMN_BACKLOG: ${{ secrets.COLUMN_BACKLOG }}
E2E_GITHUB_TOKEN: ${{ secrets.E2E_GITHUB_TOKEN }}
E2E_GITHUB_APP_ID: ${{ secrets.E2E_GITHUB_APP_ID }}
E2E_GITHUB_APP_PRIVATE_KEY: ${{ secrets.E2E_GITHUB_APP_PRIVATE_KEY }}
E2E_GITHUB_INSTALLATION_ID: ${{ secrets.E2E_GITHUB_INSTALLATION_ID }}
E2E_GITHUB_OWNER: ${{ secrets.E2E_GITHUB_OWNER }}
E2E_GITHUB_REPO: ${{ secrets.E2E_GITHUB_REPO }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
Expand Down Expand Up @@ -144,7 +148,9 @@ jobs:
COLUMN_AI: ${{ secrets.COLUMN_AI }}
COLUMN_AI_REVIEW: ${{ secrets.COLUMN_AI_REVIEW }}
COLUMN_BACKLOG: ${{ secrets.COLUMN_BACKLOG }}
E2E_GITHUB_TOKEN: ${{ secrets.E2E_GITHUB_TOKEN }}
E2E_GITHUB_APP_ID: ${{ secrets.E2E_GITHUB_APP_ID }}
E2E_GITHUB_APP_PRIVATE_KEY: ${{ secrets.E2E_GITHUB_APP_PRIVATE_KEY }}
E2E_GITHUB_INSTALLATION_ID: ${{ secrets.E2E_GITHUB_INSTALLATION_ID }}
E2E_GITHUB_OWNER: ${{ secrets.E2E_GITHUB_OWNER }}
E2E_GITHUB_REPO: ${{ secrets.E2E_GITHUB_REPO }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,12 @@ Operators can drive workflows directly from Slack with `/ai-workflow list | stat
```bash
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxx # Anthropic API key
CLAUDE_MODEL=claude-opus-4-6 # Model to use (default: claude-opus-4-6)
COMMIT_AUTHOR=ai-workflow-blazity # Git commit author name
COMMIT_EMAIL=ai-workflow@blazity.com # Git commit author email
# COMMIT_AUTHOR= # Optional override (set with COMMIT_EMAIL).
# COMMIT_EMAIL= # On GitHub, leave unset to author commits as the App's bot.
```

**GitHub App bot identity** — when `VCS_KIND=github` and both `COMMIT_AUTHOR` / `COMMIT_EMAIL` are unset, the workflow derives the identity from the configured GitHub App (`<app-slug>[bot]` + the `<id>+<slug>[bot]@users.noreply.github.com` noreply address). GitHub then renders commits with the App's avatar and the `[bot]` badge in the UI.

**Switching agents** — ai workflow supports two CLI runtimes. Set `AGENT_KIND` once per deployment:

```bash
Expand Down Expand Up @@ -275,15 +277,14 @@ curl -H "Authorization: Bearer $CRON_SECRET" http://localhost:3000/cron/poll
| **Agent** | | | |
| `AGENT_KIND` | No | `claude` | Runtime: `claude` or `codex` |
| `ANTHROPIC_API_KEY` | Yes‡ | — | Anthropic API key (required when `AGENT_KIND=claude`) |
| `CLAUDE_CODE_OAUTH_TOKEN` | No | — | Alternative to `ANTHROPIC_API_KEY` |
| `CLAUDE_MODEL` | No | `claude-opus-4-6` | Claude model ID |
| `CODEX_API_KEY` | Yes‡ | — | OpenAI Codex API key (required when `AGENT_KIND=codex`) |
| `CODEX_CHATGPT_OAUTH_TOKEN` | No | — | Alternative to `CODEX_API_KEY` |
| `CODEX_MODEL` | No | `gpt-5-codex` | Codex model ID |
| `CODEX_PRICING_URL` | No | LiteLLM JSON | Pricing source for Codex cost reporting |
| `CODEX_PRICING_TTL_MS` | No | `3600000` | Pricing cache TTL (ms) |
| `COMMIT_AUTHOR` | No | `ai-workflow-blazity` | Git author name |
| `COMMIT_EMAIL` | No | `ai-workflow@blazity.com` | Git author email |
| `COMMIT_AUTHOR` | No | _GitHub: App bot / GitLab: `ai-workflow-blazity`_ | Git author name (override; pair with `COMMIT_EMAIL`) |
| `COMMIT_EMAIL` | No | _GitHub: App bot / GitLab: `ai-workflow@blazity.com`_ | Git author email (override; pair with `COMMIT_AUTHOR`) |
| **Sandbox** | | | |
| `MAX_CONCURRENT_AGENTS` | No | `3` | Max parallel sandboxes |
| `JOB_TIMEOUT_MS` | No | `1800000` | Agent timeout (ms) |
Expand Down Expand Up @@ -399,11 +400,11 @@ Each agent run gets a fresh, isolated [Vercel Sandbox](https://vercel.com/docs/s
| Input | How it's provided |
|-------|-------------------|
| Repository source code | Cloned via `git` source at the feature branch (shallow `depth=1`); unshallowed before push if needed |
| Auth env vars | `ANTHROPIC_API_KEY` / `CLAUDE_CODE_OAUTH_TOKEN` (Claude) or `CODEX_API_KEY` / `CODEX_CHATGPT_OAUTH_TOKEN` (Codex) — written to `/tmp/agent-env.sh` (mode 0600) and sourced by each phase script |
| Auth env vars | `ANTHROPIC_API_KEY` (Claude) or `CODEX_API_KEY` / `CODEX_CHATGPT_OAUTH_TOKEN` (Codex) — written to `/tmp/agent-env.sh` (mode 0600) and sourced by each phase script |
| Model | `CLAUDE_MODEL` or `CODEX_MODEL` baked into the phase wrapper script |
| Per-phase input | `/tmp/research-requirements.md` and `/tmp/impl-requirements.md` — assembled by `assembleResearchPlanContext` / `assembleImplementationContext` |
| Attachments | Written to `/tmp/attachments/<filename>` |
| Git identity | `git config user.name` / `user.email` from `COMMIT_AUTHOR` / `COMMIT_EMAIL` |
| Git identity | `git config user.name` / `user.email` from `COMMIT_AUTHOR` / `COMMIT_EMAIL` (or auto-derived from the GitHub App when unset) |
| Agent CLI | `@anthropic-ai/claude-code` (Claude) or `@openai/codex` (Codex), installed globally |
| Skills | Installed via `npx skills add ... -g --agent claude-code codex --copy` to **both** `~/.claude/skills/` and `~/.agents/skills/`. Currently only [`frontend-design`](https://github.com/anthropics/skills) is in `GLOBAL_SKILLS` |
| Arthur tracer (optional) | Python tracer + `~/.claude/arthur_config.json` + hook entries in `~/.claude/settings.json` |
Expand Down
Loading
Loading