fix(agent): offer no Claude effort levels when the CLI predates --effort

claudeEffortSuperset treated a --help with no --effort line as 'help
format drifted' and fell back to the full level superset. On a host
whose claude binary predates the flag (e.g. 2.1.2), that let
ValidateThinkingLevel approve the agent's persisted thinking_level, so
the daemon injected --effort and the CLI exited 1 with
"error: unknown option '--effort'" — hard-failing every task instead
of degrading to a plain run. Field case: a stale root-owned
/usr/local/bin/claude shadowing a current install for daemons whose
PATH orders /usr/local/bin first (Multica desktop app), while a shell-
launched daemon on the same machine resolved a current claude — the
same agent then failed or succeeded depending on which daemon won the
task claim race.

Distinguish the two cases: --effort present but value list unparsed →
keep the full-superset drift fallback; --effort absent entirely →
return no levels, so the daemon's existing per-model guard drops the
level with a warning and the task runs without the flag.

Co-authored-by: Fable Chief Strategist <noreply@anthropic.com>
This commit is contained in:
Andrew Noble
2026-07-02 20:58:43 -07:00
committed by GitHub
parent 65269ef922
commit c7e10ffa85
2 changed files with 113 additions and 8 deletions

View File

@@ -166,8 +166,9 @@ func loadClaudeThinkingByModel(ctx context.Context, executablePath string) map[s
}
// claudeEffortSuperset returns the parsed `--effort` value list. When
// parsing fails it returns the static fallback rather than nothing so
// callers can still render a usable picker.
// the help output can't be captured at all it returns the static
// fallback rather than nothing so callers can still render a usable
// picker.
func claudeEffortSuperset(ctx context.Context, executablePath string) []string {
cmd := exec.CommandContext(ctx, executablePath, "--help")
hideAgentWindow(cmd)
@@ -175,14 +176,30 @@ func claudeEffortSuperset(ctx context.Context, executablePath string) []string {
if err != nil {
return append([]string(nil), claudeStaticEffortFallback...)
}
parsed := parseClaudeEffortHelp(string(out))
if len(parsed) == 0 {
// Help format drifted — fall back to the last known good
// superset rather than the conservative subset, so newer
// levels are still offered until we hand-edit the fallback.
return claudeEffortLevelsFromHelp(string(out))
}
// claudeEffortLevelsFromHelp decides the effort superset from a
// successfully captured `claude --help`. Three cases:
// - the value list parsed → use it verbatim;
// - `--effort` is advertised but the value list didn't parse → help
// format drifted; fall back to the last known good superset so
// newer levels are still offered until we hand-edit the fallback;
// - `--effort` is absent entirely → the installed CLI predates the
// flag. Return no levels: offering any would let the daemon pass
// ValidateThinkingLevel and inject --effort, which such a binary
// rejects with `error: unknown option '--effort'` — hard-failing
// every task for an agent with a persisted thinking_level instead
// of degrading to a plain run.
func claudeEffortLevelsFromHelp(helpText string) []string {
parsed := parseClaudeEffortHelp(helpText)
if len(parsed) > 0 {
return parsed
}
if strings.Contains(helpText, "--effort") {
return append([]string(nil), claudeStaticEffortFullSuperset...)
}
return parsed
return nil
}
// parseClaudeEffortHelp extracts the comma-separated value list from a

View File

@@ -58,6 +58,39 @@ Options:
}
}
func TestClaudeEffortLevelsFromHelp_DriftedFormatFallsBackToFullSuperset(t *testing.T) {
t.Parallel()
// The flag is advertised but the parenthesised value list is gone —
// genuine help drift, so keep offering the last known good superset.
help := `Usage: claude [options]
Options:
--effort <level> Choose how hard the model thinks
`
got := claudeEffortLevelsFromHelp(help)
if !reflect.DeepEqual(got, claudeStaticEffortFullSuperset) {
t.Fatalf("claudeEffortLevelsFromHelp: got %v, want full superset %v", got, claudeStaticEffortFullSuperset)
}
}
func TestClaudeEffortLevelsFromHelp_PreEffortCLIReturnsNoLevels(t *testing.T) {
t.Parallel()
// A CLI released before --effort existed (e.g. claude 2.1.2) has no
// mention of the flag anywhere in --help. This must yield NO levels —
// the old fallback-to-full-superset here made the daemon inject
// --effort, which the binary rejects with "unknown option", failing
// the task outright.
help := `Usage: claude [options]
Options:
--model <model> Model to use
--verbose
`
if got := claudeEffortLevelsFromHelp(help); got != nil {
t.Fatalf("claudeEffortLevelsFromHelp: expected nil for pre-effort CLI, got %v", got)
}
}
func TestProjectClaudeLevels_PerModelSubset(t *testing.T) {
t.Parallel()
superset := []string{"low", "medium", "high", "xhigh", "max"}
@@ -376,6 +409,42 @@ func TestValidateThinkingLevel_ExplicitModel(t *testing.T) {
}
}
func TestValidateThinkingLevel_PreEffortCLIRejectsAllLevels(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("shell-script fake binary requires a POSIX shell")
}
t.Parallel()
// End-to-end guard for the daemon's pre-execution check against a CLI
// that predates --effort: the catalog must offer no levels, so any
// persisted thinking_level is dropped (with a warning) instead of being
// injected as a flag the binary rejects with "unknown option".
fakeClaude := writeFakeClaudePreEffortHelpBinary(t)
resetThinkingCacheForTests()
defer resetThinkingCacheForTests()
ctx := context.Background()
for _, level := range []string{"low", "medium", "high", "xhigh", "max"} {
ok, err := ValidateThinkingLevel(ctx, "claude", fakeClaude, "claude-fable-5", level)
if err != nil {
t.Fatalf("unexpected err for %q: %v", level, err)
}
if ok {
t.Errorf("level %q must be invalid on a pre-effort CLI; got true", level)
}
}
// Empty value still means "use runtime default" and must stay valid.
ok, err := ValidateThinkingLevel(ctx, "claude", fakeClaude, "claude-fable-5", "")
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if !ok {
t.Errorf("empty value must always be valid")
}
}
func TestValidateThinkingLevel_OpenCodeEmptyModelUsesAdvertisedVariants(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("shell-script fake binary requires a POSIX shell")
@@ -451,6 +520,25 @@ func writeFakeClaudeHelpBinary(t *testing.T) string {
return path
}
// writeFakeClaudePreEffortHelpBinary mimics a Claude Code release from
// before the --effort flag existed (e.g. 2.1.2): --help succeeds but has
// no --effort line at all.
func writeFakeClaudePreEffortHelpBinary(t *testing.T) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "claude")
script := "#!/bin/sh\n" +
"cat <<'EOF'\n" +
"Usage: claude [options]\n" +
"\n" +
"Options:\n" +
" --model <model> Model to use\n" +
" --verbose\n" +
"EOF\n"
writeTestExecutable(t, path, []byte(script))
return path
}
// ── Cache key invalidation ───────────────────────────────────────────
func TestThinkingCacheKeyDistinct(t *testing.T) {