Setup ends on a question, and can start the client on it (LLP 0195) - #667
Setup ends on a question, and can start the client on it (LLP 0195)#667bgmcmullen wants to merge 3 commits into
Conversation
Setup proved it had captured something (LLP 0135 #first-look) but never
taught what to do with it: ten skills land in ~/.claude/skills and the
finale reports "skills: 10 copied", which names a file operation, not a
capability. The gap was activation, not information - printing example
prompts still leaves the user to open a terminal, start a client, and
retype one.
Both first-party CLI clients take the opening prompt as argv, so setup
can be the launcher. After the first look and the privacy narration, an
attended run now offers a short list of questions and spawns the chosen
client on the pick with stdio: 'inherit'. The narration keeps its place
as the wizard's last words; the ask is the exit door.
- contributes.client.launch ({ bin, args, label }) carries how to start a
client on a question, so a future adapter is launchable with no core
edit. A spec missing the {prompt} placeholder is rejected at catalog
build: launchable-and-mute looks like the feature working.
- Launchability is a $PATH probe, deliberately not the picker's detection.
A settings file does not imply a binary, and Claude Desktop is
detectable, pickable, attachable, and has no prompt argument at all.
- An empty cache suppresses the launch and says why. Every question is
about recorded history, so a fresh install would spend its one first
impression on an empty answer. "slow" from the first look means the
opposite of empty and still launches; unknown never withholds.
- The step can never fail a finished install: missing binary, spawn
error, cancel, or an unforeseen throw all degrade to the printed list.
The child's exit code is not propagated - quitting the client is not a
failed install.
- hyp ask makes the list re-runnable, and hyp ask "<question>" skips the
menu. Every other closing surface names a durable entry point.
The prompt is drawn in a border, which no other wizard prompt is (LLP
0195 #frame). Every other prompt owns its screen; this one lands under
the first look's tables and rules, where an unframed list reads as more
output to skim rather than the one thing awaiting a keypress. The frame
is a shape, not a colour, so it survives NO_COLOR and a colour-blind
reader, and is suppressed rather than wrapped when it would not fit.
Setup's first look now runs ['models', 'daily'] instead of all four
sections (LLP 0195 #wizard-sections), taking up the seam LLP 0135 left
open. Four sections ran ~60 lines and ~5s on a 91k-row cache against a
planner that budgeted ~2s, so setup routinely printed "Stopped here to
keep setup moving - the repos and tools sections did not finish". A
block that regularly announces its own truncation is worse than a
shorter block that finishes. hyp query overview still renders all four.
missingSections now takes the requested set: without it the trim would
make setup claim repos and tools "did not finish" on every run, a claim
about work nobody started.
The planner's own miscalibration is filed as #665, not fixed here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…his branch was open master merged llp/0193-openclaw-backfill-cli-denylist.decision.md and llp/0194-per-message-provider.decision.md during this work, so both numbers were claimed twice. LLP numbers are globally unique and never reused, so this branch moves rather than the merged docs. 0195 is taken by PR #667, hence 0196/0197. Renaming an LLP is a mechanical edit (LLP 0156), and it has to be exhaustive or the @refs in shipped code point at someone else's decision. Every rewrite here is anchored to my slug or to an anchor only my documents define, so the openclaw refs (LLP 0193#decision, LLP 0194#decision) are untouched, as are LLP 0161/0172/ 0173 and docs/ACCEPTANCE.md. Updated: both filenames and titles, the forward-refs on LLP 0142 and LLP 0155, all 9 @ref annotations across src/ and test/, prose references in the skills, src/core/reports/README.md, and the CI workflow comment. Verified every renumbered anchor resolves, and test/core/llp-ref-hygiene.test.js (which arrived from master in the same merge) passes. Also merges origin/master, which brings the openclaw backfill work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Neutral review round 1 of 2 (adopted PR,
|
| Check | Result |
|---|---|
node scripts/run-tests.js |
3616 tests / 3615 pass / 0 fail / 1 skipped |
npx tsc -p tsconfig.json --noEmit |
clean, exit 0 |
smokes walkthrough_picker_to_first_query, cli_bundled_plugins_activated, package_bin_boot, status_diagnostics |
all ok |
No em dashes anywhere in the diff. No statement-terminating semicolons. All 28
@ref LLP 0195#... anchors resolve and the glosses were spot-checked as accurate.
One correction: the PR body claims usage-policy-fold.test.js:387 fails at master. It did
not fail here; the suite was fully green at this head.
BLOCKING 1 - LLP 0195 is claimed twice
origin/fix/issue-653 llp/0195-upstream-less-gateway-idles.decision.md
origin/wizard-first-ask llp/0195-setup-ends-on-a-question.decision.md
origin/master (no llp/0195)
The filenames differ, so git will not conflict - both would land silently as duplicate
LLP 0195s. The llp-check duplicate-numbers job exists precisely for this and has not
run on this head. PR #658 is neutral:approved and held for merge, so it lands first,
which makes this PR the one to renumber.
The next free number is 0198, not 0196: PR #668 just renumbered into 0196/0197.
Computing the next number from master alone (which is only at 0194) is exactly how this
happened - it has to be computed across master and every branch.
Neutral is pushing the renumber to 0198, including all 28 @ref sites, the
Extended-by: block in LLP 0135, and the doc's own title.
BLOCKING 2 - hyp ask launches an unattached client, contradicting its own help text
src/core/commands/ask.js:122-141. if (attached.length > 0) return attached conflates
"probe succeeded, zero clients attached" with "probe threw", so a successful report
showing nothing attached falls through to launching every client on $PATH.
Reproduced on a fresh HYP_HOME with nothing attached and a stub claude on $PATH:
hyp ask "hello world" launched it, exit 0.
That falsifies three statements shipped in this same PR:
core_commands.js:~272help: "Only clients that are attached (hyp status) and whose CLI
is on your PATH can be started."ask.js:110-118docblock: "The clientshyp askmay start: those HypAware is actually
recording."- the new LLP
#path-probe: "an unpicked client is one HypAware is not recording, so
opening it would produce a session the user did not consent to capture."
The docblock's own reasoning is right - a probe that cannot read is not evidence of
detachment - but a probe that succeeds and reports zero is. Neutral is pushing the
restructure so only a thrown probe falls through. An unattached machine then correctly
reaches the existing no-launcher message and exit 1.
Also being fixed by neutral
- N1 -
src/core/commands/ask.jshas zero test coverage. 143 new lines behind a new
top-level verb, andgrep -rln "runAsk\|commands/ask" test/returns nothing. A single
test overaskableClientswould have caught Blocking 2. Addingtest/core/commands/ask.test.js. - N2 - two surviving mutants in
firstLookHadRows(wizard/index.js:~556): treating a
slowfirst look as empty, and treating an absent one as empty, both leave the suite
green. The new LLP's#empty-cachesays outright that "collapsing any two of them would
be the bug", andslow-means-yes is the least intuitive of the three. - N6 - the
#no-preauthdecision has no test. It is the most security-relevant
decision in the change and nothing pins it. Adding an assertion that the bundled launch
args carry no permission-widening flag. - N7 -
hyp ask --listalways claims launchability (ask.js:39hardcodes
launchable: true), so a machine with nothing on$PATHis told to run a command that
exits 1. - N8 -
ask.js:28says "the same five questions";SUGGESTED_PROMPTShas four.
Left for you, deliberately
- N3 -
hyp ask "<question>"never checks for a TTY (ask.js:46-63); it spawns with
stdio: 'inherit'unconditionally, so in cron/CI/a git hook it can block until the
client exits. The wizard path is correctly gated (mutation-confirmed) and the menu path
is gated onisTty; only the named-question path is open. Defensible ("the user asked to
launch"), but it is the one headless-hang vector the change introduces. Worth either
refusing on non-TTY or documenting it. - N4 -
hyp ask "q"silently pickslaunchers[0]when several clients are attached,
while the menu path asks which should answer.report.clientsorder is not user-meaningful.
A--client <name>flag would settle it. - N5 - "exactly one
{prompt}" is documented but not enforced. The LLP and the
PluginClientLaunchManifestJSDoc both say exactly one, butplugin_catalog.js:93
validates with.some()andlaunchClientreplaceAlls in every element - and
first-ask.test.js:151deliberately asserts multi-slot substitution works. The code's
behaviour is the better one; the docs should soften to "at least one". Your Draft LLP,
so your call. - N9 -
resolveOnPathplatform seam is host-coupled (first_ask.js:109-111):
bin.includes(path.sep)andpath.joinuse the host separator whileplatformis a
parameter. Harmless in production. Separately, on win32extsnever includes'', so an
extensionless shim onPATHwill not resolve. - N10 - stray duplicated docblock at
test/core/cli/wizard/index.test.js:~683.
Mutation testing: 9 of 11 killed
Killed: requested-set filtering, empty-cache suppression (2), launch spec without
{prompt}, oversized box suppression, stdio mode, unpicked clients offered (7),
first-look section count (3), the non-interactive gate, {prompt} substitution.
Survivors: both in firstLookHadRows (N2 above).
On the specific risks checked
- Declined path: correct and well covered. "Not now",
PromptCancelledError, and the
back error all collapse todeclined, print the list, launch nothing, exit 0. - Idempotent/reversible: yes. The launch writes nothing durable; it is a
spawnafter
the last durable write, and the child's exit code is deliberately dropped so quitting the
client cannot fail a finished install. - Headless hang: wizard path safe, menu path gated, one open vector (N3).
- Renumber llp/0195-setup-ends-on-a-question -> 0198: 0195 collides with PR #658's fix/issue-653 doc, 0196/0197 are taken by PR #668. Updates all 28 @ref sites plus the LLP 0135 Extended-by block. - ask.js askableClients: a successful status probe reporting zero attached clients is evidence of detachment, not grounds to fall back to every launchable client on PATH. Only a thrown probe now falls through. - ask.js --list: launchable claim now reflects actual resolvability instead of being hardcoded true. - ask.js docblock/comment: four suggested prompts, not five. - Add test/core/commands/ask.test.js covering askableClients' probe success/throw branches and the no-launcher/--list exit-code contract. - Add two firstLookHadRows cases (slow, absent/error) to wizard/index.test.js pinning the two mutants the review found. - Add a no-preauth assertion to client-launch-manifest.test.js: bundled launch args never carry --allowedTools or --dangerously-skip-permissions. Co-Authored-By: Claude <noreply@anthropic.com>
The 0195 -> 0198 renumber swept .js and .md but not .d.ts, leaving seven prose citations behind. They are not @ref form, so neither ref-check nor the duplicate-numbers job sees them, and once fix/issue-653 lands they would resolve to a real but unrelated document. One of the three files ships to plugin authors as the kernel contract.
Neutral review round 2 of 2 (adopted PR) -
|
| Check | Result |
|---|---|
node scripts/run-tests.js |
3624 pass / 0 fail / 1 skipped, exit 0 |
npx tsc -p tsconfig.json --noEmit |
clean, exit 0 |
| Mutation probes (3) | all 3 mutants killed |
CI ran green on b87f6a7 - 9 check runs, all success, including duplicate-numbers.
That is a change from round 1, when no checks had run at all. The 84354cc fix is
docs/types-only and re-verified locally (3624 pass, tsc exit 0); CI will re-run on it.
philcunliffe
left a comment
There was a problem hiding this comment.
Verdict: changes requested - five items that are yours to decide
Two review rounds are used, so the residual findings go back to you rather than being
deferred to a follow-up issue. None of them is a defect neutral found and left broken -
both blockers are fixed and on the branch:
- LLP 0195 → 0198 renumber (neutral pushed): 0195 was claimed by
fix/issue-653,
0196/0197 byllp/0193-vendor-report-renderer, so 0198 was the next free number across
every branch. ZeroLLP 0195references remain. hyp asklaunching an unattached client (neutral pushed): a successful probe reporting
zero attached now returns[]instead of falling through to every launchable client. Only
a thrown probe falls through. Traced end to end: the zero-attached case now reaches
no-launcherand exit 1, so the help text's "only clients that are attached ... can be
started" is true.- Neutral's own renumber missed seven
.d.tscitations; that was neutral's defect and is
fixed in84354cc.
CI is green and the suite is 3624 pass / 0 fail, tsc clean.
What is left, all requiring your judgment
hyp ask "<question>"has no TTY check (ask.js:46-63) - it spawns with
stdio: 'inherit'unconditionally, so in cron, CI, or a git hook it can block until the
client exits. The wizard path is correctly gated (mutation-confirmed) and thehyp ask
menu is gated onisTty; the named-question path is the one open vector. Defensible as
"the user asked to launch" - but it should be a decision, either refusing on non-TTY or
documenting it.hyp ask "q"silently pickslaunchers[0]when several clients are attached, while
the menu path asks which should answer.report.clientsorder is not user-meaningful. A
--client <name>flag would settle it.- "Exactly one
{prompt}" is documented but not enforced. Your LLP and the
PluginClientLaunchManifestJSDoc both say exactly one, butplugin_catalog.js:93
validates with.some()andlaunchClientreplaceAlls every element - and
first-ask.test.js:151deliberately asserts multi-slot substitution works. The code's
behaviour is the better one; the docs should soften to "at least one". Your Draft doc,
your call. - The no-preauth assertion is real but thin. It is not vacuous -
assert.ok(launch)
first pins that both clients are launchable - butArray.includesis exact-element over
two entries. It would not catch--permission-mode acceptEdits,
--dangerously-bypass-approvals-and-sandbox,--full-auto,--yolo,
--sandbox danger-full-access,-a never, or an=truesuffixed form. Since this pins
the most security-relevant decision in the change, widening it to a regex over
(-{1,2})(dangerously|allowedTools|permission-mode|full-auto|yolo|sandbox|ask-for-approval)
is worth the two minutes. resolveOnPath's platform seam is host-coupled (first_ask.js:109-111):
bin.includes(path.sep)andpath.joinuse the host separator whileplatformis a
parameter, soplatform: 'win32'on POSIX builds POSIX paths. Harmless in production.
Separately, on win32extsnever includes'', so an extensionless shim onPATHwill
not resolve.
Also worth a look, non-blocking: runAsk now calls askableClients (a full
collectHypAwareStatus - config load, layered merge, plugin catalog build) before the
--list early return, so hyp ask --list got measurably slower for a command documented as
"print the suggested questions and exit". It is read-only and network-free, so latency only;
computing launchers lazily inside the --list branch avoids it. And there is a stray
duplicated docblock at test/core/cli/wizard/index.test.js:~683.
Push any commit and neutral re-engages on its next tick with a fresh review at the new head.
|
Two things from #665 that affect this PR, separate from the review verdict above. 1. Your LLP is now 0198, not 0195. Neutral renumbered it on this branch because This matters beyond the file: your prose in #665 cites 2. This PR and #666 have a merge-order dependency. #666 should merge before or together with this PR. It is Neutral's earlier comment on #665 claimed the wizard does not request a subset. That was |
Setup proved it had captured something (LLP 0135 #first-look) but never taught what to do with it. Ten skills land in
~/.claude/skillsand the finale reportsskills: 10 copied- a file operation, not a capability. The gap was activation, not information: printing example prompts still leaves the user to open a terminal, start a client, and retype one, and the drop-off across those three steps is the whole distance between "installed" and "used".Both first-party CLI clients take the opening prompt as argv (
claude [options] [prompt],codex [OPTIONS] [PROMPT]), so setup can be the launcher.What it looks like
After the first look and the privacy narration:
Enter, and Claude comes up already working on that question. No copy-paste step.
Design notes
Full rationale in LLP 0195. The decisions worth reviewing:
The manifest owns the launch; core owns the questions.
contributes.client.launch({ bin, args, label }) says how to start a client on a question, so a future adapter becomes launchable with no core edit - the same rule LLP 0180 set for the finale's client list. A spec missing the{prompt}placeholder is rejected at catalog build: launchable-and-mute looks like the feature working.Launchability is a
$PATHprobe, not the picker's detection. A~/.claude/settings.jsondoes not imply aclaudeon$PATH, and Claude Desktop is detectable, pickable, attachable, and has no prompt argument at all. Only clients both picked and resolvable are offered - starting an unattached client would open a session nothing captures.An empty cache suppresses the launch and says why. Every question is about recorded history, so a fresh install with nothing backfilled would spend its one first impression on an empty answer. The first look's result already distinguishes the cases:
slowmeans the opposite of empty and still launches;error/unknown never withholds the offer.Nothing here can fail a finished install. Missing binary, spawn error, cancel, unforeseen throw - all degrade to the printed list. The child's exit code is deliberately not propagated: quitting the client with ctrl+c is not a failed install.
The launched session is not pre-authorized. No
--allowedTools. The client asks its own permission the first time the skill runshyp query, and that prompt stands. A tool whose pitch is "see what your AI clients are doing" should not open by quietly widening what one of them may do.hyp askmakes the list re-runnable, andhyp ask "<question>"skips the menu. Every other closing surface names a durable entry point; a menu reachable only by re-runninghyp initwould be the exception.Two changes to existing behaviour
The prompt is framed (#frame), which no other wizard prompt is. Position is what makes it necessary: every other prompt owns its screen, while this one lands under the first look's tables, rules and dim footers, where an unframed list of sentences reads as more output to skim rather than the one thing still awaiting a keypress. The frame is a shape rather than a colour, so it survives
NO_COLORand a colour-blind reader, and is suppressed rather than soft-wrapped when it would not fit - a broken rectangle is worse than no rectangle.Setup's first look runs
['models', 'daily'], not all four sections (#wizard-sections). This takes up the seam LLP 0135 explicitly left open ("the seam for a shorter variant remains if setup output ever needs trimming") and reverses its judgment that all four was right, recorded as a new decision with a forward-ref rather than an edit to what 0135 settled. Both halves of the cost were real: ~60 lines, and ~5s on a 91k-row cache against a planner that budgeted ~2s - so setup routinely printed "Stopped here to keep setup moving - the repos and tools sections did not finish". A block that regularly announces its own truncation is worse than a shorter block that finishes.hyp query overviewis unchanged and still renders all four.That trim needed one fix in the shared module:
missingSectionsnow takes the requested set. Without it the wizard would claim repos and tools "did not finish" on every run - a claim about work nobody started, and the exact thing that sentence exists to prevent.Known issue, not fixed here
The overview planner's
SECTION_COST_VS_PROBEis calibrated at 1.9x and measures ~3.4x on a real cache, so it picks windows it cannot finish. Filed as #665 with measurements. The trim reduces how often the wizard trips over it but does not fix it, and a real run after this change still hit the deadline - the issue carries that evidence and argues against the obvious fix (re-measuring the constant), since the probe reads a prunable column and structurally cannot predict what the sections cost.Testing
npm test: 3613 pass, 1 fail -usage-policy-fold.test.js:387, confirmed failing identically atmasterin a clean worktree. Unrelated to this branch.npm run typecheck: clean.walkthrough_picker_to_first_query,cli_bundled_plugins_activated,package_bin_boot,status_diagnostics.test/core/cli/wizard/first-ask.test.js(16),test/core/client-launch-manifest.test.js(4), plus the framed-render and wizard-orchestration cases. Launch tests stub the spawn seam - no test starts a real client.claudeandcodexresolve from the real manifests;hyp ask,hyp ask --list, and the no-launcher path (empty$PATH) behave as described.🤖 Generated with Claude Code