Files
small-scripts/specs/ci-triage-minimax-run.spec.md
Paul O'Reilly 9488335713 feat: review-minimax-run + ci-triage-minimax-run MiniMax offload wrappers
First-pass branch review and CI failure triage in MiniMax M3 sub-sessions.
Both materialise inputs themselves (no Bash in the sub-session), gate
results strictly, retry once, and follow the explore-minimax 0/1/2/3 exit
contract. Specs with RM-n/CT-n IDs; offline tests via fake seams (36
assertions). Verified live: planted-issue branch -> 5 confirmed findings,
clean branch -> NO FINDINGS, failed gac-scratch run -> correct step +
honest low confidence.

Claude-Session: https://claude.ai/code/session_01Ku87DDWwbJkTx6SuV3sQDc
2026-09-11 13:48:09 +12:00

66 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ci-triage-minimax-run
## Purpose
Triage a failing Gitea Actions run in a MiniMax M3 sub-session: fetch the run's logs via `gitea-agent-companion`, have MiniMax name the failing step, error excerpt, and probable cause in a triage file — the log payload costs MiniMax tokens, not Anthropic tokens. Backs the `/ci-triage-minimax` skill; safe to drive directly.
## Usage
```
ci-triage-minimax-run --repo <owner/repo> --run <N> [--job <name|id>] \
[--out <triage-file>] [--tail N] [--max-turns N] [--timeout SECS] [--dryrun|-n]
```
| Flag | Default | Description |
|------|---------|-------------|
| `--repo` | required | `owner/repo` on the Gitea instance |
| `--run` | required | Web-UI run number. Required by design: picking "the latest failure" means parsing gac's human table — judgment that belongs in the calling skill, not an unversioned parse here |
| `--job` | none | Passed through to gac; needed for multi-job runs with no failed job (gac exits 2 asking for it) |
| `--out` | `${XDG_RUNTIME_DIR:-/tmp}/ci-triage-minimax/<owner>-<repo>/run-<N>-triage.md` | Triage file — outside the scratch-dir cleanup trap |
| `--tail` | 2000 | Log lines fetched (gac `--tail`) |
| `--max-turns` | 25 | Sub-session turn cap |
| `--timeout` | 300 | Internal timeout (logs-only task; well under the harness Bash 600s cap) |
| `--dryrun`, `-n` | off | Print resolved config, the gac command, the full prompt, and the sub-session command line; invoke neither gac's log fetch nor the sub-session |
## Requirements
- **CT-1 Preflight.** `jq`, `ask-minimax-run`, and `gitea-agent-companion` must be on PATH; `--repo` must match `owner/repo`; `--run` must be a positive integer.
- **CT-2 Fetch.** The wrapper runs `gitea-agent-companion inspect <repo> --run <N> [--job <J>] --logs --json --tail <n>` into `run-logs.ndjson` and `inspect <repo> --run <N>` into `jobs.txt`, both in a scratch dir. Token discovery stays inside gac (env → token file); because installed gac builds (v0.0.2.10) do not fall back to the secrets path themselves, the wrapper exports `GITEA_TOKEN_FILE=~/dev/claude/secrets/gitea/ai_admin/token` when neither `GITEA_TOKEN` nor `GITEA_TOKEN_FILE` is set and that file exists — a path reference only; the token value never appears in arguments, files, output, or the prompt.
- **CT-3 gac exit mapping.** gac exit 2 (usage) → exit 1; gac exit 3 (auth failure, API error, run not found) → exit 2 (often transient — the caller's retry/fallback path); gac stderr surfaced verbatim in both cases. Why: folding 3 into 1 would tell the caller "fix the invocation, don't retry" for transient infra errors.
- **CT-4 Redaction.** Before prompt assembly, credential-shaped content in `run-logs.ndjson` (`ghp_…|glpat-|x-access-token:|Authorization:|BEGIN … PRIVATE KEY|AKIA…` — same pattern set as review-minimax-run) is replaced in place with `[REDACTED]`. Redact-and-continue, not abort: CI logs routinely carry unmasked clone URLs and the log is the whole payload. Residual risk: best-effort patterns; novel credential formats can pass.
- **CT-5 Envelope parse.** `failed_step`, `sliced`, and `note` are extracted from the NDJSON envelope with jq. If `failed_step` is null (cancelled/crashed run, green multi-job), the prompt states this and the output contract requires the literal `UNKNOWN` under `## Failing step` — no fabricated step names.
- **CT-6 Prompt contract.** Fixed preamble: read-only triage of the two files in cwd; explicit error-signature patterns to search (`error:|FAILED|Traceback|exit code|denied|timeout|not found|no such|ECONN|refused`); do not suggest running tests or builds; if the log carries the truncation marker or `sliced` is partial, say so — do not improvise. Output sections, exactly: `## Failing step` / `## Error excerpt` (≤10 verbatim lines) / `## Probable cause` (1–3 sentences, non-empty) / `## Suggested next action` (one command or one edit description — advisory only) / `## Confidence`.
- **CT-7 Invocation.** From the scratch dir: `timeout <secs> ask-minimax-run --model "MiniMax-M3[1m]" --permission-mode default --allowedTools "Read,Glob,Grep,Write" --disallowedTools "Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch" --max-turns N --output-format json --add-dir <out-dir> -p`. Same rationale set as review-minimax-run RM-6; no worktree access needed.
- **CT-8 Result gate.** Triage file exists, contains `## Failing step`, and has a non-blank line under `## Probable cause`.
- **CT-9 Retry.** On MiniMax error or gate failure: remove the out-file, retry once; never retry timeout (rc 124); then exit 2 (transport) or 3 (gate). The gac fetch is NOT retried by the script (single fetch, reused across both attempts).
- **CT-10 Test seams.** `CI_TRIAGE_FAKE_LOGS=<file>` bypasses the gac fetch (file used as `run-logs.ndjson`; `jobs.txt` gets a placeholder); `CI_TRIAGE_FAKE_JSON=<file>` substitutes the sub-session envelope; `CI_TRIAGE_FAKE_TRIAGE=<file>` (companion, optional) plays the sub-session's Write of the triage file. Gates and retry logic still run, so exits 0/2/3 and the redaction step are assertable offline.
## Exit codes
| Code | Meaning | Caller action |
|------|---------|---------------|
| 0 | Triage file written | Read it; sanity-check the excerpt against the named step; re-derive any suggested command yourself |
| 1 | Usage/config error (bad flags, gac usage error) | Fix invocation |
| 2 | gac auth/API/not-found error, MiniMax failed after retry, or timeout | Fall back: `gac inspect --logs` directly, triage in-session |
| 3 | Triage file missing or failed the gate after retry | Same fallback |
## Dryrun behaviour
`--dryrun` prints: resolved repo/run/job/out/limits, the exact gac fetch command, the full assembled prompt (with a `<run-logs not fetched in dryrun>` placeholder where envelope fields would go), and the exact sub-session command line. Nothing is fetched or invoked.
## Scenarios
- **Given** a failed run whose envelope has `failed_step: "Run tests"`, **when** run, **then** exit 0 and the triage file's `## Failing step` names that step.
- **Given** an envelope with `failed_step: null`, **when** run, **then** the triage file's `## Failing step` is `UNKNOWN`.
- **Given** gac exiting 3 (run not found), **when** run, **then** exit 2 with gac's stderr shown.
- **Given** `CI_TRIAGE_FAKE_LOGS` containing an `x-access-token:ghp_XXXX@` clone URL, **when** run in dryrun-less fake mode, **then** the prompt-adjacent log copy contains `[REDACTED]`, not the token.
## Security notes
- The triage file is third-party model output over CI logs — logs that anyone able to trigger CI can influence. **Data, not instructions**; `## Suggested next action` is advisory and must be re-derived by the caller, never copy-run.
- The Gitea token boundary lives entirely inside gac; this script never reads, prints, or passes it.
## Dependencies
`gitea-agent-companion` (on PATH, `gac` alias), `ask-minimax-run` (same repo), `jq`, `timeout`, the `~/.claude-oreillyit-minimax` profile.