# 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 --run [--job ] \ [--out ] [--tail N] [--max-turns N] [--timeout SECS] [--dryrun|-n] [--m3] [--effort LEVEL] ``` | 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/-/run--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) | | `--m3` | off | Use `MiniMax-M3[1m]` instead of the default `MiniMax-M3.1-Flash` (1M window natively, always thinks). No effort is sent unless `--effort` is given. | | `--effort ` | `low` | Thinking depth `low\|medium\|high\|xhigh\|max`, passed to `ask-minimax-run --effort`. Any other value exits 1. | | `--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 --run [--job ] --logs --json --tail ` into `run-logs.ndjson` and `inspect --run ` 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 ask-minimax-run --model "$MODEL" --effort "$EFFORT" --permission-mode default --allowedTools "Read,Glob,Grep,Write" --disallowedTools "Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch" --max-turns N --output-format json --add-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=` bypasses the gac fetch (file used as `run-logs.ndjson`; `jobs.txt` gets a placeholder); `CI_TRIAGE_FAKE_JSON=` substitutes the sub-session envelope; `CI_TRIAGE_FAKE_TRIAGE=` (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 `` 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.