You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PR #84 (merged 2026-04-25) delivered the bulk of #80 — subagent-style YAML frontmatter on *_SYSTEM_PROMPT.md templates, declarative per-action tools/model/effort, and runner translation to claude CLI flags. One acceptance criterion from #80 was not delivered:
CLI flags and env vars (--effort, --model, ASKCC_CLAUDE_EFFORT_LEVEL, etc.) continue to override per-action frontmatter when explicitly set
Today, --effort / ASKCC_CLAUDE_EFFORT_LEVEL and --max-thinking-tokens / ASKCC_CLAUDE_MAX_THINKING_TOKENS follow the documented precedence chain (CLI > env > frontmatter > built-in default), but model has no runtime override path. The only way to change model: opus in DEVELOP_SYSTEM_PROMPT.md is to edit the template file in ~/.askcc/templates/.
Current State
askcc/runners.py:49-60 (_frontmatter_cli_flags) emits --model from config.model only — no CLI/env consultation.
askcc/cli.py exposes --effort and --max-thinking-tokens argparse flags but no --model.
askcc/settings.py reads ASKCC_CLAUDE_EFFORT_LEVEL and ASKCC_CLAUDE_MAX_THINKING_TOKENS but no ASKCC_CLAUDE_MODEL.
README.md "Override Precedence" section documents the chain for effort and max_thinking_tokens; model is silently not overridable.
VALID_FRONTMATTER_MODELS (askcc/definitions.py:699) already defines the canonical choice set ("opus", "sonnet", "haiku", "inherit") — reused for both CLI choices and env validation.
Implementation Plan
Mirror the existing --effort / ASKCC_CLAUDE_EFFORT_LEVEL precedence chain for model. The runner contract stays symmetric: the CLI resolves precedence, then passes the resolved value to runner.run.
Add --model argparse flag — askcc/cli.py near the existing --effort argument (askcc/cli.py:139-146). Use choices=VALID_FRONTMATTER_MODELS (imported from askcc/definitions.py:699) so argparse rejects invalid values pre-run. Default None. Help text documents the precedence: CLI > env (ASKCC_CLAUDE_MODEL) > frontmatter > claude's built-in default.
Add _resolve_model helper — new function in askcc/cli.py immediately after _resolve_max_thinking_tokens (around askcc/cli.py:84), mirroring _resolve_effort (askcc/cli.py:59-71). Signature: _resolve_model(cli_value: str | None, frontmatter_value: str | None) -> str | None. Precedence:
CLI value if non-None
else env ASKCC_CLAUDE_MODEL if non-empty and in VALID_FRONTMATTER_MODELS (warn-and-ignore on invalid, matching _resolve_effort's warning shape)
else frontmatter_value (may itself be None)
Return None when no source is set — caller suppresses the --model flag.
Wire resolved value into runner call — in askcc/cli.py main() near askcc/cli.py:286-287 where effort_level / max_thinking_tokens are resolved, add model = _resolve_model(args.model, config.model) and pass model=model into the runner.run(...) call at askcc/cli.py:290-299.
Extend Runner.run signature — askcc/runners.py:33-46: add model: str | None = None keyword-only parameter to the abstract method and to ClaudeRunner.run (askcc/runners.py:159-170). Inside ClaudeRunner.run, forward it to _frontmatter_cli_flags so the resolved value (not config.model) drives the emitted flag.
Update _frontmatter_cli_flags — askcc/runners.py:49-60: accept an optional model: str | None = None parameter. Replace the config.model lookup with effective_model = model if model is not None else config.model. Emit ["--model", effective_model] only when truthy — preserves "no flag when unset" behavior for templates without model:.
Document in README — README.md:
"Override Precedence" section (lines 236-244): generalize so it covers effort, max_thinking_tokens, andmodel.
Global options table (lines 110-122): add a --model row.
Environment Variables table (lines 134-148): add an ASKCC_CLAUDE_MODEL row.
Tests — tests/test_askcc.py:
TestModelPrecedence (new class) mirroring TestEffortPrecedence (tests/test_askcc.py:2598-2648): four tests covering CLI > env > frontmatter > unset (no default; None propagates).
TestThinkingSettings extensions (tests/test_askcc.py:1827): test_invalid_askcc_claude_model_logged_and_ignored mirroring test_invalid_effort_level_logged_and_ignored (tests/test_askcc.py:1846-1853), but exercising _resolve_model (env lookup lives in cli.py, not settings.py — adjust import and caplog logger accordingly).
TestRunnerFrontmatterFlags extensions (tests/test_askcc.py:2651-2702): test_model_override_emitted (runner.run with model="sonnet" overrides config.model="opus") and test_model_falls_back_to_config_when_unset (no model kwarg → config.model still drives the flag).
CLI-level forwarding test following the pattern at tests/test_askcc.py:2030-2044: assert --model sonnet and ASKCC_CLAUDE_MODEL=sonnet both reach runner.run(model="sonnet").
Argparse rejection test: invoking main() with --model opuz raises SystemExit (argparse exits 2 on bad choice).
Verification
After implementation, the following must pass:
uv run pytest # all tests pass, including the new TestModelPrecedence class
uv run ruff check # no lint regressions
uv run pyright # no type regressions
Manual smoke (against a real issue, optional but recommended before opening the PR):
askcc --model sonnet plan -g <url># spawns claude with --model sonnet regardless of frontmatter
ASKCC_CLAUDE_MODEL=haiku askcc plan -g <url># spawns claude with --model haiku when no --model flag
ASKCC_CLAUDE_MODEL=opuz askcc plan -g <url># WARNING logged, falls through to frontmatter
askcc --model opuz plan -g <url># argparse exits 2: "invalid choice"
Acceptance Criteria
askcc --model sonnet develop <url> overrides model: opus in DEVELOP_SYSTEM_PROMPT.md frontmatter
ASKCC_CLAUDE_MODEL=sonnet askcc develop <url> does the same when no --model flag is set
Invalid values: --model opuz rejected by argparse; ASKCC_CLAUDE_MODEL=opuz logs a warning and falls through to frontmatter/default
README.md "Override Precedence" section extended to cover model alongside effort and max_thinking_tokens
uv run pytest, uv run ruff check, uv run pyright all pass
Dependencies
None identified — self-contained additive change inside askcc/cli.py, askcc/runners.py, README.md, and tests/test_askcc.py. No new package dependencies. VALID_FRONTMATTER_MODELS is already exported from askcc/definitions.py:699.
Risks and Open Questions
model: inherit at the CLI/env layer — VALID_FRONTMATTER_MODELS includes "inherit". Reusing the same choice set for the CLI flag and env var keeps the surface symmetric, but inherit is meaningful primarily inside subagent frontmatter (it tells claude to use the parent session's model). At the askcc-CLI level, inherit reaching the runner becomes --model inherit against claude, which may or may not be accepted. Plan: pass through verbatim. If a downstream rejection emerges, address as a follow-up rather than special-casing askcc.
Unset-everywhere fallback — when no CLI / env / frontmatter value exists, _resolve_model returns None and _frontmatter_cli_flags emits no --model flag, letting claude pick its own default. Identical to today's behavior for templates without model:, so no behavior change for users who haven't customized.
Non-Goals
Does not add --max-turns / ASKCC_CLAUDE_MAX_TURNS (separate symmetry gap; can be a follow-up if needed)
Does not surface permission_mode — intentionally omitted in ✨ Add subagent frontmatter to agent action definitions #84 because askcc runs claude --dangerously-skip-permissions and a permission prompt would deadlock an unattended run
Problem Statement
PR #84 (merged 2026-04-25) delivered the bulk of #80 — subagent-style YAML frontmatter on
*_SYSTEM_PROMPT.mdtemplates, declarative per-actiontools/model/effort, and runner translation toclaudeCLI flags. One acceptance criterion from #80 was not delivered:Today,
--effort/ASKCC_CLAUDE_EFFORT_LEVELand--max-thinking-tokens/ASKCC_CLAUDE_MAX_THINKING_TOKENSfollow the documented precedence chain (CLI > env > frontmatter > built-in default), butmodelhas no runtime override path. The only way to changemodel: opusinDEVELOP_SYSTEM_PROMPT.mdis to edit the template file in~/.askcc/templates/.Current State
askcc/runners.py:49-60(_frontmatter_cli_flags) emits--modelfromconfig.modelonly — no CLI/env consultation.askcc/cli.pyexposes--effortand--max-thinking-tokensargparse flags but no--model.askcc/settings.pyreadsASKCC_CLAUDE_EFFORT_LEVELandASKCC_CLAUDE_MAX_THINKING_TOKENSbut noASKCC_CLAUDE_MODEL.README.md"Override Precedence" section documents the chain foreffortandmax_thinking_tokens;modelis silently not overridable.VALID_FRONTMATTER_MODELS(askcc/definitions.py:699) already defines the canonical choice set("opus", "sonnet", "haiku", "inherit")— reused for both CLI choices and env validation.Implementation Plan
Mirror the existing
--effort/ASKCC_CLAUDE_EFFORT_LEVELprecedence chain formodel. The runner contract stays symmetric: the CLI resolves precedence, then passes the resolved value torunner.run.Add
--modelargparse flag —askcc/cli.pynear the existing--effortargument (askcc/cli.py:139-146). Usechoices=VALID_FRONTMATTER_MODELS(imported fromaskcc/definitions.py:699) so argparse rejects invalid values pre-run. DefaultNone. Help text documents the precedence: CLI > env (ASKCC_CLAUDE_MODEL) > frontmatter > claude's built-in default.Add
_resolve_modelhelper — new function inaskcc/cli.pyimmediately after_resolve_max_thinking_tokens(aroundaskcc/cli.py:84), mirroring_resolve_effort(askcc/cli.py:59-71). Signature:_resolve_model(cli_value: str | None, frontmatter_value: str | None) -> str | None. Precedence:ASKCC_CLAUDE_MODELif non-empty and inVALID_FRONTMATTER_MODELS(warn-and-ignore on invalid, matching_resolve_effort's warning shape)frontmatter_value(may itself beNone)Nonewhen no source is set — caller suppresses the--modelflag.Wire resolved value into runner call — in
askcc/cli.py main()nearaskcc/cli.py:286-287whereeffort_level/max_thinking_tokensare resolved, addmodel = _resolve_model(args.model, config.model)and passmodel=modelinto therunner.run(...)call ataskcc/cli.py:290-299.Extend
Runner.runsignature —askcc/runners.py:33-46: addmodel: str | None = Nonekeyword-only parameter to the abstract method and toClaudeRunner.run(askcc/runners.py:159-170). InsideClaudeRunner.run, forward it to_frontmatter_cli_flagsso the resolved value (notconfig.model) drives the emitted flag.Update
_frontmatter_cli_flags—askcc/runners.py:49-60: accept an optionalmodel: str | None = Noneparameter. Replace theconfig.modellookup witheffective_model = model if model is not None else config.model. Emit["--model", effective_model]only when truthy — preserves "no flag when unset" behavior for templates withoutmodel:.Document in README —
README.md:effort,max_thinking_tokens, andmodel.--modelrow.ASKCC_CLAUDE_MODELrow.Tests —
tests/test_askcc.py:TestModelPrecedence(new class) mirroringTestEffortPrecedence(tests/test_askcc.py:2598-2648): four tests covering CLI > env > frontmatter > unset (no default;Nonepropagates).TestThinkingSettingsextensions (tests/test_askcc.py:1827):test_invalid_askcc_claude_model_logged_and_ignoredmirroringtest_invalid_effort_level_logged_and_ignored(tests/test_askcc.py:1846-1853), but exercising_resolve_model(env lookup lives incli.py, notsettings.py— adjust import and caplog logger accordingly).TestRunnerFrontmatterFlagsextensions (tests/test_askcc.py:2651-2702):test_model_override_emitted(runner.run withmodel="sonnet"overridesconfig.model="opus") andtest_model_falls_back_to_config_when_unset(nomodelkwarg →config.modelstill drives the flag).tests/test_askcc.py:2030-2044: assert--model sonnetandASKCC_CLAUDE_MODEL=sonnetboth reachrunner.run(model="sonnet").main()with--model opuzraisesSystemExit(argparse exits 2 on bad choice).Verification
After implementation, the following must pass:
Manual smoke (against a real issue, optional but recommended before opening the PR):
Acceptance Criteria
askcc --model sonnet develop <url>overridesmodel: opusinDEVELOP_SYSTEM_PROMPT.mdfrontmatterASKCC_CLAUDE_MODEL=sonnet askcc develop <url>does the same when no--modelflag is set--model opuzrejected by argparse;ASKCC_CLAUDE_MODEL=opuzlogs a warning and falls through to frontmatter/defaultREADME.md"Override Precedence" section extended to covermodelalongsideeffortandmax_thinking_tokensREADME.mdEnvironment Variables table listsASKCC_CLAUDE_MODEL; global options table lists--model--modelcorrectly; argparse rejects invalid CLI choiceuv run pytest,uv run ruff check,uv run pyrightall passDependencies
None identified — self-contained additive change inside
askcc/cli.py,askcc/runners.py,README.md, andtests/test_askcc.py. No new package dependencies.VALID_FRONTMATTER_MODELSis already exported fromaskcc/definitions.py:699.Risks and Open Questions
model: inheritat the CLI/env layer —VALID_FRONTMATTER_MODELSincludes"inherit". Reusing the same choice set for the CLI flag and env var keeps the surface symmetric, butinheritis meaningful primarily inside subagent frontmatter (it tells claude to use the parent session's model). At the askcc-CLI level,inheritreaching the runner becomes--model inheritagainstclaude, which may or may not be accepted. Plan: pass through verbatim. If a downstream rejection emerges, address as a follow-up rather than special-casing askcc._resolve_modelreturnsNoneand_frontmatter_cli_flagsemits no--modelflag, lettingclaudepick its own default. Identical to today's behavior for templates withoutmodel:, so no behavior change for users who haven't customized.Non-Goals
--max-turns/ASKCC_CLAUDE_MAX_TURNS(separate symmetry gap; can be a follow-up if needed)permission_mode— intentionally omitted in ✨ Add subagent frontmatter to agent action definitions #84 because askcc runsclaude --dangerously-skip-permissionsand a permission prompt would deadlock an unattended runReferences
askcc/cli.py:59-83— existing_resolve_effort/_resolve_max_thinking_tokenspattern to mirroraskcc/runners.py:49-60—_frontmatter_cli_flagsemission siteaskcc/definitions.py:699—VALID_FRONTMATTER_MODELSsource of truthtests/test_askcc.py:2598-2648—TestEffortPrecedencepattern to mirror forTestModelPrecedencetests/test_askcc.py:2651-2702—TestRunnerFrontmatterFlagsto extend