Skip to content

fix(install): gate Claude-only wizard prompts on agent_backend#381

Merged
dcellison merged 5 commits intomainfrom
feature/gate-claude-only-wizard-prompts
Apr 24, 2026
Merged

fix(install): gate Claude-only wizard prompts on agent_backend#381
dcellison merged 5 commits intomainfrom
feature/gate-claude-only-wizard-prompts

Conversation

@dcellison
Copy link
Copy Markdown
Owner

Summary

  • Three wizard prompts in _cmd_config() previously fired regardless of the operator's selected agent backend, even though the captured values are consumed only by ClaudeCodeBackend. Goose-backend operators were asked to set values that would be silently ignored at runtime. This PR adds backend-conditional gating for CLAUDE_AUTOCOMPACT_PCT, CLAUDE_EFFORT_LEVEL, and the legacy CLAUDE_USER prompt at install.py:730.
  • Each gate uses the "initialize-before-gate" pattern: the captured variable is set to its dataclass default before the conditional, and the existing emission check (if value != default) correctly skips writing the env key under goose without needing a parallel emission gate. As a free side effect, this implicitly auto-prunes previously-set values from install.conf on a claude→goose switch — pinned by a regression test.
  • The fourth planned gate from the spec (the admin_os_user prompt at install.py:382) is intentionally NOT included. That prompt fires inside if advanced:, which runs before agent_backend is defined at line 440. Honoring the gate would require structural reordering of the wizard. Deferred to the broader multi-backend rework per operator direction.

Fixes #380 (partial — three of four planned gates; admin_os_user gate deferred per above).

Test plan

  • make check (ruff check + format) — local: passes
  • make test (full suite, 2384 tests) — local: passes
  • test_goose_backend_skips_claude_only_prompts (new): asserts CLAUDE_AUTOCOMPACT_PCT, CLAUDE_EFFORT_LEVEL, CLAUDE_USER all absent from install.conf when wizard ran under goose
  • test_goose_backend_prunes_existing_claude_only_keys (new): pre-seeds install.conf with all three keys at non-default values, runs wizard under goose, asserts all three disappear (pins the implicit auto-prune)
  • Three existing goose fixtures trimmed to remove the three surplus entries each (autocompact, effort, claude_user)
  • Three pre-existing claude tests had been passing on borrowed time without explicit autocompact / effort / claude_user entries (relying on the retry-on-empty default fallback to absorb downstream entries by mistake; pytest output capture hid the validation prints). Updated to provide the entries explicitly so the tests do not depend on absorption side-effects.
  • Verify make config walk-through under goose backend skips the autocompact, effort, and CLAUDE_USER prompts but otherwise behaves as before
  • Verify make config walk-through under claude backend prompts for all three knobs as before (no behavior regression)

Out of scope

  • The admin_os_user prompt at install.py:382 (described above; deferred to broader multi-backend rework).
  • Adding wizard prompts for CLAUDE_MAX_SESSION_HOURS / CLAUDE_IDLE_TIMEOUT (Claude-only at the consumer but no wizard prompt today).
  • Explicit prune-on-switch logic for env vars not gated by this PR. The implicit auto-prune that this PR produces for the three gated keys is acknowledged and tested; broader prune-on-switch is a separate concern.
  • Shared-with-Goose CLAUDE_* prompts (CLAUDE_TIMEOUT_SECONDS, CLAUDE_MAX_CONTEXT_WINDOW, CLAUDE_MAX_BUDGET_USDBUDGET_CEILING, CLAUDE_MODELDEFAULT_MODEL). All four are consumed by both backends and are correctly NOT gated today.

The `make config` wizard prompted for `CLAUDE_AUTOCOMPACT_PCT`,
`CLAUDE_EFFORT_LEVEL`, and the legacy `CLAUDE_USER` env var
unconditionally, even when the operator selected `goose` as the agent
backend. None of these are consumed by GooseBackend at runtime, so
the prompts asked the operator to set values that would be silently
ignored.

Three changes in install.py:
- Wrap the autocompact prompt in `if agent_backend == "claude":` and
  initialize `autocompact_pct = "0"` (dataclass default) before the
  gate so the existing emission check skips correctly for goose.
- Same pattern for the effort prompt: initialize to `"high"` and gate.
- Extend the existing `if/elif/else` cascade for `claude_user` with
  a third short-circuit branch `elif agent_backend != "claude":`
  matching the structure of the two existing short-circuits.

The `admin_os_user` prompt at install.py:382 is intentionally NOT
gated in this PR. It lives inside `if advanced:`, which fires before
`agent_backend` is defined at line 440. Honoring the gate would
require either reordering the wizard (move agent_backend prompt
earlier) or moving the os_user prompt later. Both touch the wizard's
structural UX; deferred until the broader multi-backend rework
revisits the wizard end-to-end.

Tests:
- Extend `_base_inputs` with `agent_backend` (default "claude"),
  `llm_provider`, `llm_api_key` parameters. When agent_backend is not
  claude, the helper omits autocompact, effort, and claude_user
  entries (gated to claude only) and inserts provider + API key
  entries (the wizard prompts for those on the goose path).
- New test `test_goose_backend_skips_claude_only_prompts` asserts the
  three CLAUDE_* env keys are absent in install.conf when the wizard
  ran under goose.
- New test `test_goose_backend_prunes_existing_claude_only_keys`
  pre-seeds an install.conf with all three keys set, runs the wizard
  under goose, and asserts they all disappear. Pins the implicit
  auto-prune that falls out of the "initialize-before-gate" pattern.
- Trim three existing goose fixtures (`test_goose_backend_writes_env`,
  `test_goose_ollama_no_api_key`,
  `test_non_claude_backend_drops_stale_extraction_keys`) to remove the
  three surplus entries each (autocompact, effort, claude_user) that
  the wizard no longer consumes under goose.
- Three pre-existing claude tests (`test_writes_install_conf`,
  `test_advanced_user_options`,
  `test_wizard_defaults_do_not_reintroduce_shared_home`) had been
  passing on borrowed time: they did not provide explicit autocompact /
  effort / claude_user entries and were relying on the wizard's
  retry-on-empty-input default fallback to consume downstream entries
  by mistake. Pytest output capture hid the validation prints. Add
  the missing entries explicitly so the tests do not depend on
  absorption side-effects.

Tests: 2384 pass (was 2382; +2 new tests).
@dcellison
Copy link
Copy Markdown
Owner Author

Review by Kai

The PR is well-structured. Here are my findings:

Warning

tests/test_install.py - _base_inputs() docstring says "set llm_api_key=None for ollama (which skips the API key prompt)" but the parameter is typed llm_api_key: str = "sk-ant-test-key". Passing None is a Pyright strict violation. Since the project requires Pyright strict, this should be str | None (with None as the default when ollama is selected, or kept as str and the docstring corrected to say "just leave the default; it won't be consumed"). Not a runtime bug since the ollama branch never appends llm_api_key, but it is a type error the linter would catch in a non-test context.

Suggestion

tests/test_install.py:1333 - The test docstring ends with "tracked in memory as project_multi_backend_revisit_pending.md". This is a reference to the Claude Code AI assistant's internal memory file system, not a real project artifact. A future developer reading this will have no idea what it means. Replace with a plain English reference like "tracked separately" or a GitHub issue number.

Suggestion

_base_inputs() silently ignores the effort parameter when agent_backend != "claude" (it falls into the empty claude_only_pre_webhook list). A caller passing effort="xhigh" for a goose backend gets no error and no effect. Adding a guard (if agent_backend != "claude" and effort: raise ValueError(...)) would prevent confusing silent no-ops in future tests.


Everything else looks correct. The "initialize-before-gate" pattern is sound - all three variables are set to their dataclass defaults before the conditional, so the downstream emission checks (!= 0, != "high", != "") correctly suppress writes for goose without a parallel gate at the emission site. The elif agent_backend != "claude": placement in the CLAUDE_USER chain is safe regardless of ordering because both prior branches also assign "". The two new regression tests pin both the skip and prune behaviors. Trimmed fixtures match the new wizard flow.

- Drop the "set llm_api_key=None for ollama" suggestion from the
  _base_inputs docstring. The parameter is typed `str` (no None
  variant), and the wizard skips the API key prompt for ollama
  anyway, so the default value is harmless and unused. Pyright
  strict would have flagged the suggested usage.
- Replace the memory-file path reference in the
  test_goose_backend_skips_claude_only_prompts docstring with a
  plain-language deferral note. A future developer reading the
  test should not need to know about an internal AI memory file.
- Add a guard in _base_inputs that raises ValueError when a caller
  passes a non-empty effort value with a non-claude backend. The
  effort prompt does not fire for non-claude backends (gated by
  this PR), so silently ignoring the parameter would be a
  confusing failure mode for a future test author.
@dcellison
Copy link
Copy Markdown
Owner Author

Review by Kai

The implementation is clean. Here's what I found after reviewing the diff against the prior thread:

Prior review items - status

All three issues from the prior review are resolved:

  • llm_api_key: str type annotation: the docstring no longer says to pass None for ollama; it now says the default is "harmless and unused". No type violation.
  • AI memory reference at line 1333: that test was not modified in this PR; not re-raised.
  • effort guard for non-claude backends: ValueError is now raised at lines ~1215-1222.

New findings

Suggestion - install.py, the new claude_user comment block (~line 741):

# GooseBackend in pool.py:186-198 takes no claude_user kwarg

Line-number references in cross-file comments are fragile - pool.py can be edited independently and this will silently drift. Drop the :186-198 qualifier or replace with a function/class name reference that's stable across refactors.

Suggestion - tests/test_install.py, the claude_user_entry comment in _base_inputs:

# Legacy CLAUDE_USER prompt at install.py:730 is gated on
# agent_backend == "claude" by issue #380 (Change 4).

The "(Change 4)" label will be opaque to anyone who doesn't have access to the internal spec. Since the PR description gives the full context, "Change 4 of the spec" should be replaced with just "issue #380" or left out entirely. Minor, but the CLAUDE.md commenting guide implies comments should explain the WHY, not reference private documents.


Everything else is correct. The "initialize-before-gate" pattern is applied consistently across all three variables. The elif agent_backend != "claude": placement in the CLAUDE_USER chain is safe since both preceding branches already assign "". The two new regression tests correctly pin skip-and-prune behavior. The _base_inputs refactor produces an identical input list for agent_backend="claude" (the default), so existing call sites are unaffected.

- Drop the `pool.py:186-198` line-number qualifier from the
  claude_user comment in install.py. Cross-file line-number
  references are fragile - pool.py can be edited independently
  and the comment will silently drift. Reference the function
  name (GooseBackend) instead, which is stable across refactors.
- Drop the "(Change 4)" spec reference from the test comment in
  _base_inputs. Spec labels are opaque without access to the
  internal spec doc and violate the project's no-spec-refs rule
  for public artifacts. The "issue #380" reference is sufficient.
@dcellison
Copy link
Copy Markdown
Owner Author

Review by Kai

The diff is clean. One new finding:

Suggestion - tests/test_install.py, _base_inputs comment at the claude_user_entry block:

# Legacy CLAUDE_USER prompt at install.py:730 is gated on
# agent_backend == "claude" by issue #380.

install.py:730 is a fragile line-number reference - the same pattern that was fixed in the prior round for pool.py:186-198. This comment is new code in this iteration (the _base_inputs function was substantially rewritten), so this is not a re-raise. Drop the :730 qualifier; the issue number and variable name are sufficient to locate the gate.


Everything else is correct:

  • "Initialize-before-gate" is applied consistently for all three variables: autocompact_pct = "0", claude_effort_level = "high", and the elif agent_backend != "claude": claude_user = "" placement. In each case the downstream emission check (!= 0, != "high", != "") correctly suppresses the write for goose without a parallel gate at the emission site.
  • The elif agent_backend != "claude": ordering in the claude_user chain is safe - all three non-prompt branches assign "", so their order is irrelevant to correctness.
  • The ValueError guard in _base_inputs for effort with non-claude backends is a sound defensive choice.
  • Both new regression tests correctly pin skip-and-prune behavior. The prune test's pre-seeded conf cannot contaminate output because the gated block never runs and the initialized default ("0", "high", "") fails the emission check.
  • Trimmed goose fixtures match the new wizard flow.
  • Prior review items (pool.py line-number reference, "(Change 4)" label, llm_api_key docstring, effort guard) are all resolved.

Drop the `:730` line-number qualifier from the legacy CLAUDE_USER
comment in tests/test_install.py. Same pattern as the round 2 fix
for `pool.py:186-198`: cross-file line-number references drift on
every edit and produce silently-stale comments. The issue number
and variable name are sufficient to locate the gate.

Saved memory feedback_no_cross_file_line_numbers_in_comments.md
to capture the rule (this PR hit it twice in adjacent rounds).
@dcellison
Copy link
Copy Markdown
Owner Author

Review by Kai

Looking at this diff against the prior review thread - all three previously-raised issues (pool.py line-number reference, "(Change 4)" label, install.py:730 reference in test comment) are resolved. One new finding:

Suggestion - tests/test_install.py, test_goose_backend_skips_claude_only_prompts docstring:

Note: the admin_os_user prompt at install.py:382 is NOT gated
in this PR.

This is a new instance of the exact pattern caught in the last two review rounds - a fragile line-number reference embedded in a comment. Drop the :382 qualifier; "the admin_os_user prompt" is already sufficient to locate it, and the surrounding sentence explains why it is deferred.


Everything else is correct:

  • "Initialize-before-gate" is applied consistently: autocompact_pct = "0", claude_effort_level = "high", and the elif agent_backend != "claude": claude_user = "" placement. In each case the downstream emission check (!= 0, != "high", != "") correctly suppresses the write for goose without a parallel gate at the emission site.
  • The elif agent_backend != "claude": insertion in the claude_user chain is safe - all three prior branches also assign "", so order is irrelevant to correctness.
  • The _base_inputs refactor is sound: ValueError guard for effort on non-claude backends, conditional backend_block/claude_only_pre_webhook/claude_user_entry lists all match the wizard's runtime conditionals.
  • Both new regression tests correctly pin skip-and-prune behavior. The prune test's pre-seeded values cannot survive because the initialized defaults ("0", "high", "") fail the emission check.
  • Trimmed goose fixtures match the new wizard flow.
  • No fragile line numbers in install.py comments; the pool.py reference now correctly omits the line range.

Drop the `:382` line-number qualifier from the admin_os_user note
in test_goose_backend_skips_claude_only_prompts. Same pattern as
the round 2 (pool.py:186-198) and round 3 (install.py:730) fixes.

Audited the full diff for any other instances of the file:NNN
pattern in added comments before pushing - this was the only
remaining one.
@dcellison
Copy link
Copy Markdown
Owner Author

Review by Kai

Looking at the diff against the prior review thread - the one open finding from the last round (:382 line-number reference in test_goose_backend_skips_claude_only_prompts) is resolved. The docstring now reads "lives inside the if advanced: block in install.py" with no line number.

No new issues found. Specifically:

  • install.py - "initialize-before-gate" is applied consistently: autocompact_pct = "0", claude_effort_level = "high", and elif agent_backend != "claude": claude_user = "". All three downstream emission checks (!= 0, != "high", != "") correctly suppress the write for goose without a parallel gate at the emission site.
  • install.py comments - No fragile line-number cross-references. The pool.py reference correctly omits the line range (addressed in a prior round).
  • _base_inputs refactor - The ValueError guard for effort on non-claude backends is sound. The three conditional lists (backend_block, claude_only_pre_webhook, claude_user_entry) correctly mirror the wizard's runtime conditionals. Default agent_backend="claude" means all existing callers are unaffected.
  • New regression tests - Both tests are correct. test_goose_backend_skips_claude_only_prompts pins that the prompts do not fire; test_goose_backend_prunes_existing_claude_only_keys pins that the implicit auto-prune works because pre-seeded non-default values cannot survive when the initialized defaults fail the emission check.
  • Trimmed goose fixtures - The three removed input entries (autocompact, effort, claude_user) match the new wizard flow exactly.

This PR is clean.

@dcellison dcellison merged commit 6bcfb8b into main Apr 24, 2026
1 check passed
@dcellison dcellison deleted the feature/gate-claude-only-wizard-prompts branch April 24, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gate Claude-only wizard prompts on agent_backend=claude

2 participants