diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..2aa5d42f --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,5 @@ +{ + "enabledPlugins": { + "scality-skills@scality-agent-hub": true + } +} diff --git a/.claude/skills/review-pr/SKILL.md b/.claude/skills/review-pr/SKILL.md new file mode 100644 index 00000000..bcbc4390 --- /dev/null +++ b/.claude/skills/review-pr/SKILL.md @@ -0,0 +1,127 @@ +--- +name: review-pr +description: Review a PR on runner-manager (Python FastAPI service managing GitHub Actions self-hosted runners on cloud VMs) +argument-hint: +disable-model-invocation: true +allowed-tools: Read, Bash(gh repo view *), Bash(gh pr view *), Bash(gh pr diff *), Bash(gh pr comment *), Bash(gh api *), Bash(git diff *), Bash(git log *), Bash(git show *) +--- + +# Review GitHub PR + +You are an expert code reviewer. Review this PR: $ARGUMENTS + +## Determine PR target + +Parse `$ARGUMENTS` to extract the repo and PR number: + +- If arguments contain `REPO:` and `PR_NUMBER:` (CI mode), use those values directly. +- If the argument is a GitHub URL (starts with `https://github.com/`), extract `owner/repo` and the PR number from it. +- If the argument is just a number, use the current repo from `gh repo view --json nameWithOwner -q .nameWithOwner`. + +## Output mode + +- **CI mode** (arguments contain `REPO:` and `PR_NUMBER:`): post inline comments and summary to GitHub. +- **Local mode** (all other cases): output the review as text directly. Do NOT post anything to GitHub. + +## Steps + +1. **Fetch PR details:** + +```bash +gh pr view --repo --json title,body,headRefOid,author,files +gh pr diff --repo +``` + +1. **Read changed files** to understand the full context around each change (not just the diff hunks). + +1. **Analyze the changes** against these criteria: + +| Area | What to check | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Async error handling | Uncaught exceptions in async FastAPI endpoints; background RQ jobs that swallow errors silently; missing `try/except` around cloud provider API calls that can fail transiently | +| Pydantic / redis-om model changes | Breaking schema changes (removed or renamed fields) that would invalidate existing Redis data; missing `index=True` on fields used in queries; `PrimaryKey` changes that break key lookup | +| Backend abstraction compliance | New or modified cloud backends implement all abstract methods from `BaseBackend`; runner lifecycle methods (create/delete/update) handle partial failures; no credentials or tokens hardcoded | +| Type hints | New functions and methods have type annotations (pyright is enforced); avoid `Any` unless unavoidable; use `Optional[X]` or `X \| None` consistently | +| Exception handling | No bare `except:`; catch specific exception types; cloud SDK exceptions wrapped with context before re-raising | +| Dependency management | `pyproject.toml` changes have a matching `poetry.lock` update; no version pins removed without justification | +| RQ job patterns | Jobs are idempotent (safe to retry); long-running jobs handle cancellation; job timeouts set appropriately | +| GitHub webhook handling | Webhook signature validation not bypassed; event payload fields accessed safely (use `.get()` or model validation); state transitions in `workflow_job` events handled correctly | +| Security | No credentials, tokens, or keys in plain text; no new `# type: ignore` that masks a real type error; no `shell=True` in subprocess calls | +| Breaking changes | Public API route changes (path, method, request/response schema); `Settings` model changes that break existing deployments; runner group config schema changes | +| Tests | New backend logic or job logic has corresponding unit tests; API-level changes covered by `tests/api/`; fixtures properly clean up Redis state | + +1. **Deliver your review:** + +### If CI mode: post to GitHub + +#### Part A: Inline file comments + +For each specific issue, post a comment on the exact file and line: + +```bash +gh api -X POST -H "Accept: application/vnd.github+json" "repos//pulls//comments" -f body="Your comment

— Claude Code" -f path="path/to/file" -F line= -f side="RIGHT" -f commit_id="" +``` + +**The command must stay on a single bash line.** Never use newlines in bash commands — use `
` for line breaks in comment bodies. Never put `
` inside code blocks or suggestion blocks. + +Each inline comment must: + +- Be short and direct — say what's wrong, why it's wrong, and how to fix it in 1-3 sentences +- No filler, no complex words, no long explanations +- When the fix is a concrete line change (not architectural), include a GitHub suggestion block so the author can apply it in one click: + ````markdown + ```suggestion + corrected-line-here + ``` + ```` + Only suggest when you can show the exact replacement. For architectural or design issues, just describe the problem. + Example with a suggestion block: + ````bash + gh api ... -f body=$'Missing the shared-guidelines update command.

\n```suggestion\n/plugin update shared-guidelines@scality-agent-hub\n/plugin update scality-skills@scality-agent-hub\n```\n

— Claude Code' ... + ```` +- When the comment contains a suggestion block, use `$'...'` quoting with `\n` for code fence boundaries. Escape single quotes as `\'` (e.g., `don\'t`) +- End with: `— Claude Code` + +Use the line number from the **new version** of the file (the line number you'd see after the PR is merged), which corresponds to the `line` parameter in the GitHub API. + +#### Part B: Summary comment + +```bash +gh pr comment --repo --body "LGTM

Review by Claude Code" +``` + +**The command must stay on a single bash line.** Never use newlines in bash commands — use `
` for line breaks in comment bodies. + +Do not describe or summarize the PR. For each issue, state the problem on one line, then list one or more suggestions below it: + +```markdown +- + - + - +``` + +If no issues: just say "LGTM". End with: `Review by Claude Code` + +### If local mode: output the review as text + +Do NOT post anything to GitHub. Instead, output the review directly as text. + +For each issue found, output: + +```markdown +**:** — +``` + +When the fix is a concrete line change, include a fenced code block showing the suggested replacement. + +At the end, output a summary section listing all issues. If no issues: just say "LGTM". + +End with: `Review by Claude Code` + +## What NOT to do + +- Do not comment on markdown formatting preferences +- Do not suggest refactors unrelated to the PR's purpose +- Do not praise code — only flag problems or stay silent +- If no issues are found, post only a summary saying "LGTM" +- Do not flag style issues already covered by the project's linter (ruff, black, pyright) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml new file mode 100644 index 00000000..4b299586 --- /dev/null +++ b/.github/workflows/review.yml @@ -0,0 +1,14 @@ +name: Code Review + +on: + pull_request: + types: [opened, synchronize] + +jobs: + review: + uses: scality/workflows/.github/workflows/claude-code-review.yml@v2 + secrets: + GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + GCP_SERVICE_ACCOUNT: ${{ secrets.GCP_SERVICE_ACCOUNT }} + ANTHROPIC_VERTEX_PROJECT_ID: ${{ secrets.ANTHROPIC_VERTEX_PROJECT_ID }} + CLOUD_ML_REGION: ${{ secrets.CLOUD_ML_REGION }} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..63e61c15 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,21 @@ +# runner-manager + +This is a **Python FastAPI service** that manages GitHub Actions self-hosted runners on cloud VMs. It: + +- Provisions and deprovisions VMs across multiple cloud backends (AWS, GCP, vSphere, OpenStack, Docker, Scaleway) +- Processes GitHub webhook events (`workflow_job`) to scale runners up/down +- Uses **Redis** (via redis-om) for persistent model storage and **RQ** (Redis Queue) for background job processing +- Exposes a REST API for runner group management, health checks, and metrics + +Key directories: + +- `runner_manager/backend/` — cloud provider integrations, each extending `BaseBackend` +- `runner_manager/models/` — Pydantic/redis-om models (`Runner`, `RunnerGroup`, `Settings`) +- `runner_manager/routers/` — FastAPI route handlers (webhooks, health, metrics, runner_groups) +- `runner_manager/jobs/` — RQ background jobs (startup, workflow_job, healthcheck, leaks, reset) +- `runner_manager/clients/` — GitHub API client (githubkit) +- `tests/unit/` and `tests/api/` — pytest test suite + +Tech stack: Python 3.11+, FastAPI, Pydantic v2, redis-om, RQ, Poetry, Trunk (ruff/black/pyright for lint/format/types). + +No internal Scality shared libraries (no arsenal, vaultclient, etc.).