fix(cli): skip the foreground-start prompt after the service starts (#12153)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The CLI onboarding wizard can install Paperclip as a background service, and it offers a foreground start when nothing else will serve > - After #12148, an interactive onboard installs and starts the service, then still asks "Start Paperclip now?" > - Answering yes runs the foreground start into the already-running instance guard, so a fully successful onboard ends with an error message > - This pull request excludes the just-installed-service case from the foreground-start prompt > - The benefit is that an interactive onboard that installs the service ends cleanly instead of steering the user into a guard refusal ## Linked Issues or Issue Description Refs #12148 — found while verifying that fix interactively. The `shouldRunNow` flag already accounts for `serviceInstalled`, but the interactive TTY fallback prompt did not, so only real interactive runs hit it: `--yes` runs, CI, and container smokes all skip the prompt branch. **What happened?** Interactive `onboard`, accept the background-service prompt. Output ends with: service installed and started, then "Start Paperclip now?" → yes → "Paperclip instance 'default' is already running as ing.paperclip.paperclipai. Use 'paperclipai service status --instance default' or pass --force to bypass this safety check." **What did you expect to happen?** Onboarding ends cleanly after "Installed and started …" — there is nothing left to start, so no prompt. **Steps to reproduce** Run `npx paperclipai@2026.825.0-nightly.1 onboard --data-dir "$(mktemp -d)"` in a terminal, accept the service prompt, then accept "Start Paperclip now?". ## What Changed - New `shouldOfferForegroundStart` predicate in `cli/src/onboard-service.ts`: the foreground-start prompt is offered only when the start was not already decided by flags, the service was not just installed, onboarding was not invoked by `run`, and the terminal is interactive. - Both onboarding call sites in `cli/src/commands/onboard.ts` use the predicate instead of the inline condition that ignored `serviceInstalled`. - Unit tests cover the predicate matrix in `cli/src/__tests__/onboard-service.test.ts`. ## Verification - `npx vitest run src/__tests__/onboard-service.test.ts` in `cli/`: 12 passed (5 new). - `tsc --noEmit` reports no errors in the changed files (remaining errors are pre-existing in `server/`). - Manual reproduction of the defect on macOS with `2026.825.0-nightly.1` before the fix: service installed, started, and healthy, then the prompt steered into the guard refusal. ## Risks - Low risk. The prompt still appears in every case it did before except when the service was just installed and is already serving. - No behavior change for `--yes`, `--run`, `--install-service` in non-interactive runs: those paths never reached the prompt. ## Model Used - Claude Fable 5 (Anthropic, model ID `claude-fable-5`), extended thinking, agentic tool use via Claude Code. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
This commit is contained in:
parent
0a01444514
commit
8d714c2d84
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { handleOnboardService, isInstallableReleaseVersion } from "../onboard-service.js";
|
||||
import { handleOnboardService, isInstallableReleaseVersion, shouldOfferForegroundStart } from "../onboard-service.js";
|
||||
|
||||
function supportedDetection() {
|
||||
return {
|
||||
|
|
@ -138,3 +138,27 @@ describe("isInstallableReleaseVersion", () => {
|
|||
expect(isInstallableReleaseVersion("not-a-version")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldOfferForegroundStart", () => {
|
||||
const base = { serviceInstalled: false, startAlreadyDecided: false, invokedByRun: false, interactive: true };
|
||||
|
||||
it("offers a foreground start on a plain interactive onboard", () => {
|
||||
expect(shouldOfferForegroundStart(base)).toBe(true);
|
||||
});
|
||||
|
||||
it("never prompts after the service was installed and started", () => {
|
||||
expect(shouldOfferForegroundStart({ ...base, serviceInstalled: true })).toBe(false);
|
||||
});
|
||||
|
||||
it("never prompts when the start decision was already made by flags", () => {
|
||||
expect(shouldOfferForegroundStart({ ...base, startAlreadyDecided: true })).toBe(false);
|
||||
});
|
||||
|
||||
it("never prompts when run itself invoked onboarding", () => {
|
||||
expect(shouldOfferForegroundStart({ ...base, invokedByRun: true })).toBe(false);
|
||||
});
|
||||
|
||||
it("never prompts without an interactive terminal", () => {
|
||||
expect(shouldOfferForegroundStart({ ...base, interactive: false })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ import {
|
|||
trackInstallStarted,
|
||||
trackInstallCompleted,
|
||||
} from "../telemetry.js";
|
||||
import { handleOnboardService } from "../onboard-service.js";
|
||||
import { handleOnboardService, shouldOfferForegroundStart } from "../onboard-service.js";
|
||||
import { readInstallManifest, isManagedExecutable } from "../install-store.js";
|
||||
|
||||
type SetupMode = "quickstart" | "advanced";
|
||||
|
|
@ -459,7 +459,7 @@ export async function onboard(opts: OnboardOptions): Promise<void> {
|
|||
const serviceInstalled = await handleOnboardService(opts);
|
||||
|
||||
let shouldRunNow = !serviceInstalled && (opts.run === true || opts.yes === true);
|
||||
if (!shouldRunNow && !opts.invokedByRun && process.stdin.isTTY && process.stdout.isTTY) {
|
||||
if (shouldOfferForegroundStart({ serviceInstalled, startAlreadyDecided: shouldRunNow, invokedByRun: opts.invokedByRun === true, interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY) })) {
|
||||
const answer = await p.confirm({
|
||||
message: "Start Paperclip now?",
|
||||
initialValue: true,
|
||||
|
|
@ -725,7 +725,7 @@ export async function onboard(opts: OnboardOptions): Promise<void> {
|
|||
const serviceInstalled = await handleOnboardService(opts);
|
||||
|
||||
let shouldRunNow = !serviceInstalled && (opts.run === true || opts.yes === true);
|
||||
if (!shouldRunNow && !opts.invokedByRun && process.stdin.isTTY && process.stdout.isTTY) {
|
||||
if (shouldOfferForegroundStart({ serviceInstalled, startAlreadyDecided: shouldRunNow, invokedByRun: opts.invokedByRun === true, interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY) })) {
|
||||
const answer = await p.confirm({
|
||||
message: "Start Paperclip now?",
|
||||
initialValue: true,
|
||||
|
|
|
|||
|
|
@ -165,3 +165,20 @@ export async function handleOnboardService(
|
|||
deps.success(`Installed and started ${detection.manager.serviceName}.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Onboarding falls back to offering a foreground start when nothing else
|
||||
// will serve. A just-installed service is already serving, so offering the
|
||||
// start would only run the user into the already-running instance guard.
|
||||
export function shouldOfferForegroundStart(options: {
|
||||
serviceInstalled: boolean;
|
||||
startAlreadyDecided: boolean;
|
||||
invokedByRun: boolean;
|
||||
interactive: boolean;
|
||||
}): boolean {
|
||||
return (
|
||||
!options.startAlreadyDecided &&
|
||||
!options.serviceInstalled &&
|
||||
!options.invokedByRun &&
|
||||
options.interactive
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue