Never edit this file directly. To make additions or modifications, edit the appropriate section in
./vstack.toml. Then runvstack refresh.
Second Opinion
Cross-model second opinion via external AI CLI. Auto-detects the current harness and calls the opposite:
| Running in | Calls |
|---|---|
| Claude Code | Codex |
| Codex | Claude |
| Pi | Claude |
| OpenCode / Cursor / unknown | Claude (prefers cross-model) |
Override with SECOND_OPINION_TARGET=claude|codex in committed vstack.settings.toml for shared defaults, or .env.local for personal overrides.
.agents/skills/second-opinion/scripts/second-opinion <mode> [options]
Workflows
| Command | Workflow | Output |
|---|---|---|
review [scope] |
workflows/review.md | Review finding JSON |
challenge [description] |
workflows/challenge.md | Structured critique (text) |
audit [path] |
workflows/audit.md | Review finding JSON |
quick [question] |
workflows/quick.md | Text response |
detect |
(built-in) | Target CLI name |
Timestamp is wrapper-stamped. In review and audit modes the JSON timestamp field is overwritten by the wrapper with its own UTC wall clock (date -u) after the model responds — the schema keeps ISO_8601 only as a hint. The written value therefore reflects when the wrapper produced the artifact, not a value the reviewing model serialized (which could be stale or fabricated). Downstream freshness checks (orch review-artifact-check --file <path> [delegated_at]) validate filesystem mtime, and the stamped timestamp stays consistent with it.
Review scope is wrapper-derived. In review mode the wrapper derives the scope from the worktree before invoking the external CLI — current branch, diff range (--range or origin/BASE...HEAD), diffstat, and the changed-file list are embedded in the prompt, so the external model is never asked to guess its own scope. An empty or invalid diff range fails with exit 3 and writes no artifact — an empty-scope review is an error, not a pass. When the first response yields no parseable JSON, or parseable JSON that is structurally incomplete (missing verdict or any of the blockers/suggestions/questions arrays — a truncated extraction that silently lost the findings), the one-shot retry resends the full original request (scope block included) alongside the captured response, so the fresh retry session reviews the same scope instead of answering context-free. A retried response that is still structurally incomplete is preserved as <output>.incomplete.json and exits 4 — the truncated verdict never becomes the artifact. A response whose qa_metadata self-reports that no review happened (review_performed: false, or a no-scope/no-review reason), or that omits the required qa_metadata object entirely, is never written to --output: the wrapper preserves it as <output>.noreview.json and exits 4 — first and retried responses alike. orch review-artifact-check independently rejects self-reported no-review artifacts with reason no_review and qa-shaped artifacts missing their finding arrays with reason incomplete, regardless of verdict.
Common Options
All modes accept:
| Flag | Description |
|---|---|
--target <name> |
Override target: claude or codex |
--cwd <path> |
Working directory for external CLI (default: .) |
--timeout <secs> |
CLI timeout in seconds (default: 300) |
--output <path> |
Write result to file (review/audit modes) |
--prompt <file> |
Prompt file (challenge/audit/quick modes) |
--range <ref> |
Git diff range for review (default: origin/BASE...HEAD) |
Execution Rules
- Execute all workflow sections in order. The workflow decides what to skip via "Skip if" conditions — never skip based on your own scope assessment.
<output_format>tags are literal templates: fill[PLACEHOLDERS], omit empty lines, add nothing else, do not paraphrase.- Pass
--targetwhen the user explicitly requests a specific model/CLI (e.g., "use Claude", "ask Codex"). Otherwise omit it — the script auto-detects from the current harness and project config. - Do not pass
--timeoutunless the user explicitly asks for a different value for this specific call — the script reads the default from project config. - Always pass
--cwdwith the absolute project root path. Never use--cwd .— the external CLI needs the full path to find project files. - For
quickmode, you can pass the question as an inline argument instead of writing a file:second-opinion quick "your question here" --cwd /path
Configuration
Set non-sensitive defaults in vstack.settings.toml under [env]. Existing .env.local and .env values still work; .env.local wins.
Project installs seed vstack.settings.toml from this skill's vstack.settings.toml.example when missing and merge only absent second-opinion keys into existing files.
| Variable | Default | Description |
|---|---|---|
SECOND_OPINION_TARGET |
auto-detect | Force target CLI: claude or codex |
SECOND_OPINION_TIMEOUT |
300 |
CLI timeout in seconds |
SECOND_OPINION_CLAUDE_CMD |
(see below) | Full claude command — all flags |
SECOND_OPINION_CODEX_CMD |
(see below) | Full codex command — all flags |
Default commands
Claude (called when running from Codex):
SECOND_OPINION_CLAUDE_CMD="claude -p --no-session-persistence --model opus --effort max --allowedTools Bash(read-only:true),Read,Glob,Grep"
Codex (called when running from Claude):
SECOND_OPINION_CODEX_CMD="codex exec -m gpt-5.6-sol -s read-only -c model_reasoning_effort=xhigh --ephemeral"
To customize, copy the full command into vstack.settings.toml for shared defaults or .env.local for personal overrides and edit any flags. The entire variable is used as-is.
Error Handling
On script failure (non-zero exit), stderr contains a JSON error object:
{"error": "description", "target": "codex"}
| Exit code | Meaning | Action |
|---|---|---|
| 1 | CLI not found, missing prompt, invalid JSON response | Report error to user, suggest checking CLI installation |
| 3 | review: derived diff scope is empty or invalid — nothing to review |
Report; verify the worktree has committed/pending changes or pass an explicit --range |
| 4 | review/audit: model self-reported no review was performed (qa_metadata.review_performed: false), omitted the required qa_metadata object, or stayed structurally incomplete after the one-shot retry (missing verdict or the blockers/suggestions/questions arrays) |
Report; the response is preserved as <output>.noreview.json / <output>.incomplete.json — never treat it as a pass |
| 124 | Timeout (default 300s) | Report timeout, suggest --timeout increase or narrower --range |
If the script fails during the orch review-pr or submit-pr (local pre-PR review) workflows, continue — external review is advisory.