diff --git a/.github/workflows/ai-investigate.yml b/.github/workflows/ai-investigate.yml new file mode 100644 index 000000000..b2365f7c0 --- /dev/null +++ b/.github/workflows/ai-investigate.yml @@ -0,0 +1,130 @@ +# AI failure investigation — when a gate fails, the AI investigates with FULL +# context, because everything it needs is co-located in the repo: +# tests + PRD + DESIGN + ADRs + schemas + code, per component +# (docs//specs/* and src/**/specs/*, registered in artifacts.toml). +# +# Trigger: any mandatory gate workflow finishing red on a PR branch. +# Output: an investigation comment on the PR — root-cause hypothesis, the +# spec section the failure violates (PRD/DESIGN traceability), suspect files, +# and a proposed fix or test. Advisory, never a gate: humans decide, AI digs. +# +# Optional: ANTHROPIC_API_KEY repo secret. When it is not set, the job skips +# its work and passes cleanly — this workflow is advisory and never a gate. + +name: AI Investigate Failure + +on: + workflow_run: + workflows: + - "Backend Lint & Test" + - "E2E — Bronze to API" + - "Security Gates" + - "Helm Validate" + - "Docs Gates" + - "Nightly Install Test" + - "Release Train" + types: [completed] + +permissions: + contents: read + actions: read + pull-requests: write + issues: write + +jobs: + investigate: + # Only for failures on SAME-REPO branches. A malicious fork cannot trigger + # this (which would run on the default branch with secret access + Bash and + # a head_sha checkout) — closes the workflow_run secret-exfiltration vector. + if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.head_repository.full_name == github.repository }} + runs-on: ubuntu-latest + timeout-minutes: 15 + # Job-level env can read `secrets`; step-level `if` then gates on it. The + # ANTHROPIC_API_KEY secret is optional, so when it is absent the heavy steps + # are skipped and the workflow still passes (advisory, never a gate) instead + # of erroring out inside the action with no key. + env: + HAS_ANTHROPIC_KEY: ${{ secrets.ANTHROPIC_API_KEY != '' }} + # Defense-in-depth: best-effort scrub of ANTHROPIC_API_KEY from the + # subprocesses the action's Bash tool spawns, so a same-repo branch + # can't exfiltrate it via a planted command. The same-repo gate above + # already blocks forks; this hardens the same-repo path. + CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: "1" + steps: + # nosemgrep: yaml.github-actions.security.workflow-run-target-code-checkout.workflow-run-target-code-checkout -- mitigated: job gated on head_repository == this repo (same-repo only), persist-credentials:false, no secrets used + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ github.event.workflow_run.head_sha }} + persist-credentials: false + + - name: Note when skipped (no API key) + if: env.HAS_ANTHROPIC_KEY != 'true' + run: echo "ANTHROPIC_API_KEY not configured — advisory AI investigation skipped; workflow passes." + + - name: Collect failing job logs + if: env.HAS_ANTHROPIC_KEY == 'true' + # Untrusted workflow_run.* values are passed via env (shell quotes them), + # never interpolated into the script body — avoids template injection. + env: + GH_TOKEN: ${{ github.token }} + RUN_ID: ${{ github.event.workflow_run.id }} + WF_NAME: ${{ github.event.workflow_run.name }} + WF_BRANCH: ${{ github.event.workflow_run.head_branch }} + WF_SHA: ${{ github.event.workflow_run.head_sha }} + run: | + set -euo pipefail + mkdir -p /tmp/failure + # Best-effort: this is an advisory job, so log retrieval must never + # abort it (under pipefail a failing fallback pipeline would, which + # then skips the AI step via implicit success()). + if ! gh run view "$RUN_ID" --repo "$GITHUB_REPOSITORY" --log-failed \ + > /tmp/failure/failed-jobs.log 2>/dev/null; then + gh run view "$RUN_ID" --repo "$GITHUB_REPOSITORY" --log 2>/dev/null \ + | tail -800 > /tmp/failure/failed-jobs.log || true + fi + [ -s /tmp/failure/failed-jobs.log ] || echo "(no logs retrieved)" > /tmp/failure/failed-jobs.log + { + echo "workflow: $WF_NAME" + echo "branch: $WF_BRANCH" + echo "sha: $WF_SHA" + } > /tmp/failure/meta.txt + + - name: AI investigation (full co-located context) + if: env.HAS_ANTHROPIC_KEY == 'true' + # SHA-pinned to v1 per the repo's hash-pin policy (CI supply-chain + # hardening — same as actions/checkout above); the trailing `# v1` + # documents the channel so Dependabot/Renovate can bump the pin. Runs + # only on workflow_run after a gate failed; advisory, no release artifact. + uses: anthropics/claude-code-action@30544b674398ee15c84819bd87caf8a87e8c7b55 # v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + prompt: | + A mandatory CI gate failed. Investigate the root cause using the + full co-located context of this repository. + + Failure metadata: /tmp/failure/meta.txt (workflow, branch, sha) + Failing job logs: /tmp/failure/failed-jobs.log + + Method: + 1. Read the logs; identify the first real failure (not cascades). + 2. Locate the failing code/test/model. Read its CO-LOCATED specs: + the component's specs/PRD.md, specs/DESIGN.md, specs/ADR/*, + specs/schemas/* (see docs/DOCS_MAP.md and + cypilot/config/artifacts.toml for the code↔spec mapping). + 3. Determine: is the code violating the spec, is the test wrong, + or is the spec stale? Cite the exact PRD/DESIGN section. + 4. Identify suspect files/commits (git log on the touched paths). + + Then post ONE comment on the PR for the branch named in + /tmp/failure/meta.txt (use `gh pr comment`) containing: ① root-cause + hypothesis with confidence, ② the spec section it traces to, + ③ suspect files with line refs, ④ a concrete proposed fix or + failing-test reproduction, ⑤ what to check if the hypothesis is + wrong. Be brief and factual. If no open PR exists for the branch, + open an issue titled "CI failure investigation" instead. + # Public-repo hardening: NO blanket Bash. The agent gets read/analysis + # tools plus only the exact git/gh commands it needs to inspect history + # and post its findings — so a prompt-injection in the failing logs + # can't run an arbitrary command (e.g. curl out ANTHROPIC_API_KEY). + # Layered with CLAUDE_CODE_SUBPROCESS_ENV_SCRUB + the same-repo gate. + claude_args: "--allowedTools Read,Grep,Glob,Bash(git log:*),Bash(gh pr list:*),Bash(gh pr view:*),Bash(gh pr comment:*),Bash(gh issue create:*),Bash(gh issue list:*) --max-turns 30"