# cc-triage: switch to opus alias + log stdout on failure

Even after #1622, #1629, #1630, and #1631, cc-triage still fails on every version with `claude exit 1` and empty stderr (~2–3s per attempt). Two changes: use the `opus` alias instead of a literal model ID, and log stdout when status is non-zero so the next silent failure isn't silent.

## The progression of failures 5th iteration

Run | Symptom | Cause | Fixed by
25443026871 | parse_failed × 3, ~5ms each | "claude native binary not installed" | #1622 (install.cjs)
25487401956 | parse_failed × 4, ~700ms each | --bare ignores OAuth token | #1629 (drop --bare)
25498358709 | parse_failed × 4, ~2–3s each | (this PR) | opus alias + stdout logging

## Hypothesis

Run 25498358709 took 2–3s per attempt — claude is starting up and trying to authenticate but failing silently. The script logs only stderr; if claude emits the actual error to stdout (e.g. "model claude-opus-4-7 not recognized"), we never see it. The pinned `@anthropic-ai/claude-code@2.1.122` may not know `claude-opus-4-7` as a valid model ID — Opus 4.7 was released around the time CC 2.1.122 shipped, so it's possible the alias table only got the literal ID later.

The fix sidesteps the question: use the `opus` alias, which CC has supported since at least 2.1.0 and which always resolves to the current Opus. If the diagnosis is wrong, the new stdout logging will surface the real error on the next dispatch.

## Diff

### Before

    '-p',
    '--max-turns', '1',
    '--output-format', 'text',
    '--model', 'claude-opus-4-7'

    console.error(
      `claude exit ${res.status}:` +
      ` ${(res.stderr || '').slice(0, 500)}`
    );

### After

    '-p',
    '--max-turns', '1',
    '--output-format', 'text',
    '--model', 'opus'

    const stderrSlice = (res.stderr || '').slice(0, 500).trim();
    const stdoutSlice = (res.stdout || '').slice(0, 500).trim();
    const detail = stderrSlice || stdoutSlice
      || '(no output captured)';
    const channel = stderrSlice
      ? 'stderr'
      : (stdoutSlice ? 'stdout' : 'silent');
    console.error(
      `claude exit ${res.status} [${channel}]:` +
      ` ${detail}`
    );

## Why the alias is the right move

- Forward compatibility: when Opus 4.8 ships, `opus` picks it up automatically; `claude-opus-4-7` would silently keep using 4.7 (or break if deprecated).
- Backward compatibility: older CC versions know `opus` as a long-standing alias; the literal ID is newer.
- Reproducibility loss is bounded: the cron already pins CC to a specific version, but it doesn't pin the model — model selection is best-effort across CC releases. Using the alias makes that intent explicit.

## Verification

- ✅ `bash tests/integration/test-cc-triage.sh` — 15/15 pass (fixture path is decoupled from real CLI invocation)
- 🔄 Real CLI behavior validated post-merge: dispatch `claude-release-watch.yml` and check the resulting snapshot PR's `cc-adoption-gaps.json` — should see populated `features[]` arrays for the first time
- 🔄 If alias diagnosis is wrong, the new stdout logging surfaces the real error in workflow logs

After merge: `gh workflow run claude-release-watch.yml` → check the new snapshot PR. If `features[]` is populated, close stale #1628 and #1632 and continue with #1623 floor bump.