feat(setup): make local daemon onboarding configurable#182
Conversation
|
Warning Review limit reached
Next review available in: 15 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAdds daemon-first setup, native daemon mode and defaults, add-mcp-based MCP client setup, and global HTTP serve defaults with matching config, routing, docs, and tests. ChangesDaemon-first setup onboarding
Global HTTP serve defaults
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Filename | Overview |
|---|---|
| packages/opencode/src/index.ts | Empty OpenCode config now falls through to setup-written daemon defaults. |
| packages/core/src/remote/selection.ts | Loopback attach now requires a matching persisted daemon config, running daemon status, and healthy daemon response. |
| packages/pi/src/index.ts | Pi now resolves daemon defaults after explicit options, settings, and runtime environment selectors. |
| packages/core/src/serve/http.ts | Remote credential host identity now handles multiple configured public origins. |
Reviews (5): Last reviewed commit: "fix(remote): trust persisted daemon befo..." | Re-trigger Greptile
Preview DeployedLanding: https://pr-182.preview.caplets.dev Built from commit bd22dbb |
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/core/src/remote/selection.ts (1)
75-107: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winLocal-daemon fallback still fails when a stale self-hosted profile exists.
refreshSelfHostedProfileIfNeeded()runs before the loopback fallback, and the refresh callback can throwremoteLoginRequired()/ refresh errors for expired local profiles. That meanscaplets attach http://127.0.0.1:...can still get blocked by old auth state instead of resolving aslocal_daemon. Please short-circuit loopback HTTP URLs before refresh, or downgrade self-hosted refresh failures for loopback targets into the credential-free daemon path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/remote/selection.ts` around lines 75 - 107, The remote selection flow in selection.ts is refreshing self-hosted credentials before it can apply the loopback/local-daemon fallback, so stale auth can block a 127.0.0.1 target. Update the logic around the localDaemonFallback check and refreshSelfHostedProfileIfNeeded call in the remote selection path to short-circuit loopback HTTP URLs into local_daemon handling before any self-hosted refresh, or catch refreshSelfHostedCredentials/remoteLoginRequired failures for loopback targets and fall back to localDaemonRemoteSelection instead of failing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/docs/src/content/docs/index.mdx`:
- Around line 33-37: The Quick Start text conflicts with the daemon-first setup
flow by telling users to run `npx caplets setup` even though the generated
config expects a locally available `caplets` binary. Update the docs content in
the Quick Start/setup section so the first-run path installs `caplets` before
invoking `setup`, or explicitly scope `npx` usage only to foreground-only
commands. Use the `caplets setup` and `caplets attach` guidance in this page to
align the installation and configuration steps.
In `@apps/docs/src/content/docs/install.mdx`:
- Around line 95-106: The manual config example in install.mdx hard-codes the
default daemon URL, which can mislead users whose daemon runs on a different
host or port. Update the example in the install docs to use a placeholder like
<local-daemon-url> or add a clear note telling readers to replace the attach
target in the caplets MCP server config with their configured daemon URL.
In `@docs/native-integrations.md`:
- Line 21: Update the native integrations docs paragraph to include the
environment-variable layer in the precedence order, alongside explicit
integration config, Pi settings, and native defaults. Clarify in the same
section that runtime selectors such as CAPLETS_MODE and the daemon/remote URL
env vars override setup defaults, so readers know native-defaults.json is not
the final source when those env vars are present.
In `@packages/core/src/cli/completion.ts`:
- Around line 46-47: The completion helper is returning client ids that
`resolveSetupMcpClient()` cannot actually use, so shell completion is suggesting
invalid `--client` values. Update `setupMcpClientIds()` in `completion.ts` to
filter the results from `listSupportedAddMcpClients()` so it only includes
clients accepted by `setup`/`resolveSetupMcpClient()` (for example,
stdio-capable clients only), keeping the completion list aligned with the values
that will succeed.
In `@packages/core/src/cli/setup.ts`:
- Around line 508-511: The fallback in projectConfigPath currently derives the
project config from dirname(userConfigPath(env)), which points at the
user-config directory instead of the working tree. Update projectConfigPath and
its use in defaultEnsureUserConfig() so the fallback resolves the project config
from the current cwd/worktree (using the existing project config resolution
helpers) rather than from userConfigPath, keeping CAPLETS_PROJECT_CONFIG as the
override. Ensure the validation path matches what the CLI/native integrations
will actually load.
- Around line 657-667: The MCP client prompt in resolveSetupMcpClient currently
uses the raw detected list, but parseMcpClientPromptAnswer only accepts
stdio-capable clients. Filter detectClients() results to only entries with
supportsStdio before building the prompt and before using the default selection,
while keeping listSupportedClients().filter((client) => client.supportsStdio) as
the fallback; this ensures formatMcpClientPrompt, isShowAllMcpClientsAnswer, and
parseMcpClientPromptAnswer all operate on valid choices.
In `@packages/core/src/native/options.ts`:
- Around line 59-69: `hasNativeRuntimeSelectionEnv()` is treating
`CAPLETS_DAEMON_URL` as a selection signal, but
`resolveNativeCapletsServiceOptions()` still only switches to daemon behavior
when `CAPLETS_MODE` or `input.mode` is explicitly "daemon". Update the daemon
resolution path in `resolveNativeCapletsServiceOptions()` so `input.daemon?.url`
or `CAPLETS_DAEMON_URL` implies daemon mode whenever no explicit non-daemon mode
was requested, while preserving existing precedence for `input.mode` and
`CAPLETS_MODE`; use the existing `hasNativeRuntimeSelectionEnv` and
daemon-related option handling to locate the relevant branch.
In `@packages/core/src/native/user-settings.ts`:
- Around line 45-73: `readNativeDefaults()` and `isNativeDefaults()` currently
accept any string for `daemon.url`, which lets invalid values reach daemon
startup and fail later in `resolveNativeDaemonOptions()`. Add URL validation in
this reader (or enforce it when writing defaults) so only loopback HTTP URLs are
accepted. If `daemon.url` is missing or invalid, have `readNativeDefaults()`
emit the existing warning via `writeWarning` and return `undefined` instead of
returning the parsed object.
In `@packages/core/test/cli.test.ts`:
- Around line 4032-4034: The CLI test fixture is inheriting the caller’s full
environment through io.env, which can let local CAPLETS_* variables interfere
with daemon-first behavior. Update the runCli fixture setup in cli.test.ts to
pass a minimal environment or explicitly remove all Caplets-specific keys before
calling runCli, keeping only the variables required for the test and the
CAPLETS_CONFIG override.
In `@packages/core/test/setup-runner.test.ts`:
- Around line 273-303: The Codex no-op setup tests only assert that runCommand
is not called, but they also need to verify no MCP config mutation happens. In
the runSetup("codex", …) test cases in setup-runner.test.ts, add a spy/stub for
mcpOperations.upsertServer and assert it is never invoked alongside the existing
daemonCalled and commands checks. Use the runSetup entry point and the
upsertServer symbol so these tests fail if the integration phase regresses and
mutates MCP state.
In `@packages/pi/src/index.ts`:
- Around line 337-340: The daemon polling interval handling in index.ts should
reject values that core will not accept before they reach service creation.
Update the validation around daemon.pollIntervalMs in the parsing logic to
require an integer value of at least 1000 (while still rejecting non-numbers and
non-finite values) and leave invalid settings out of parsedDaemon so they are
ignored with a warning instead of causing core failure.
- Around line 398-404: The status-widget eligibility check in the remote-mode
helper needs to account for daemon mode selected through environment-driven
defaults, since serviceOptions may be empty and the existing options.mode checks
alone won’t enable it. Update the eligibility logic in the function that
evaluates options.mode and remote URL so daemon startup via env also returns
true, using the same daemon-related symbols already present in this branch.
---
Outside diff comments:
In `@packages/core/src/remote/selection.ts`:
- Around line 75-107: The remote selection flow in selection.ts is refreshing
self-hosted credentials before it can apply the loopback/local-daemon fallback,
so stale auth can block a 127.0.0.1 target. Update the logic around the
localDaemonFallback check and refreshSelfHostedProfileIfNeeded call in the
remote selection path to short-circuit loopback HTTP URLs into local_daemon
handling before any self-hosted refresh, or catch
refreshSelfHostedCredentials/remoteLoginRequired failures for loopback targets
and fall back to localDaemonRemoteSelection instead of failing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: ce969d1b-2704-467b-9545-0cf466613e14
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (40)
.changeset/daemon-first-setup.mdCONCEPTS.mdREADME.mdapps/docs/src/content/docs/agent-integrations.mdxapps/docs/src/content/docs/index.mdxapps/docs/src/content/docs/install.mdxapps/docs/src/content/docs/remote-attach.mdxapps/docs/src/content/docs/troubleshooting.mdxapps/landing/src/data/landing.tsdocs/native-integrations.mddocs/plans/2026-06-30-001-feat-daemon-first-setup-onboarding-plan.mdpackages/core/package.jsonpackages/core/src/cli.tspackages/core/src/cli/add-mcp-adapter.tspackages/core/src/cli/completion.tspackages/core/src/cli/setup.tspackages/core/src/daemon/index.tspackages/core/src/native.tspackages/core/src/native/options.tspackages/core/src/native/service.tspackages/core/src/native/user-settings.tspackages/core/src/project-binding/attach.tspackages/core/src/remote/selection.tspackages/core/test/add-mcp-adapter.test.tspackages/core/test/agent-plugins.test.tspackages/core/test/attach-service-wiring.test.tspackages/core/test/cli-completion.test.tspackages/core/test/cli.test.tspackages/core/test/native-options.test.tspackages/core/test/native.test.tspackages/core/test/package-boundaries.test.tspackages/core/test/remote-selection.test.tspackages/core/test/serve-daemon.test.tspackages/core/test/setup-runner.test.tspackages/opencode/README.mdpackages/opencode/src/index.tspackages/opencode/test/opencode.test.tspackages/pi/README.mdpackages/pi/src/index.tspackages/pi/test/pi.test.ts
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/core/src/cli/setup.ts (1)
462-476: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDon't reject unsafe existing daemons before the repair path.
Line 462 throws as soon as a persisted daemon host is non-loopback, so
caplets setupfails instead of reinstalling/restarting the safe local daemon. That breaks the daemon-first local recovery flow described in this PR for users who already have a remote or otherwise unsafe daemon configured.Suggested fix
async function defaultEnsureDaemon(context: SetupPhaseContext): Promise<SetupPhaseResult> { const operation = daemonOperationOptions(context.env); const status = await daemonStatus(operation); - if (status.config) assertCredentialFreeLocalSetupDaemonHost(status.config); if (status.installed && status.running && status.health?.ok && status.config) { - if (!isCredentialFreeLocalSetupDaemon(status.config)) { + if ( + !isLoopbackHost(status.config.serve.host) || + !isCredentialFreeLocalSetupDaemon(status.config) + ) { return await installCredentialFreeLocalSetupDaemon(operation); } return { phase: "daemon", label: "Reuse local Caplets daemon",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/cli/setup.ts` around lines 462 - 476, The setup flow in setup.ts is rejecting persisted daemon hosts too early, which prevents the repair path from reinstalling or restarting a safe local daemon. Update the logic around the status/config handling in the setup operation so the unsafe-host check does not short-circuit before installCredentialFreeLocalSetupDaemon(operation) can run; only reuse the daemon when isCredentialFreeLocalSetupDaemon(status.config) is true and the daemon is healthy. Keep the existing reuse branch in the main success path, but allow non-loopback or otherwise unsafe configs to fall through to the reinstall/recovery path instead of calling assertCredentialFreeLocalSetupDaemonHost(status.config) upfront.packages/core/src/serve/http.ts (1)
157-165: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winHonor
publicOriginsin the trust-proxy preflight check.This guard still rejects any
remote_credentials + trustProxyconfig unlesspublicOriginis set, even though the rest of the file now supportspublicOriginsdirectly. A caller that passes onlypublicOriginswill fail at startup for no functional reason.Suggested fix
if ( options.auth.type === "remote_credentials" && options.trustProxy === true && - options.publicOrigin === undefined + publicOriginsForOptions(options).length === 0 ) { throw new CapletsError( "REQUEST_INVALID", - "Remote credential auth with --trust-proxy requires CAPLETS_SERVER_URL.", + "Remote credential auth with --trust-proxy requires a configured public origin.", ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/serve/http.ts` around lines 157 - 165, The trust-proxy preflight check in http.ts still only looks at options.publicOrigin, so remote_credentials with trustProxy incorrectly fails even when options.publicOrigins is provided. Update the guard around the CapletsError in the HTTP startup path to accept either publicOrigin or publicOrigins as satisfying the requirement, keeping the check aligned with the rest of the file’s public origin handling.
🧹 Nitpick comments (1)
packages/core/test/setup-runner.test.ts (1)
976-980: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake the
daemonClientBaseUrlstub enforce loopback like production.The real helper rejects non-loopback daemon hosts, but this stub always returns a URL. That weakens the regression signal in the non-loopback setup tests if the guard ever moves from
assertCredentialFreeLocalSetupDaemonHost()intodaemonClientBaseUrl().🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/test/setup-runner.test.ts` around lines 976 - 980, The `daemonClientBaseUrl` test stub currently bypasses the production loopback guard by always returning a URL, so update it to mirror the real helper’s behavior by validating the `config.serve.host` before constructing the URL. Use the existing `daemonClientBaseUrl` stub in `setup-runner.test.ts` and make it reject non-loopback hosts the same way the production helper does, so the non-loopback setup tests still fail if the guard moves from `assertCredentialFreeLocalSetupDaemonHost()` into `daemonClientBaseUrl()`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/src/daemon/index.ts`:
- Around line 635-642: The early persisted-config guard in the daemon flow is
too broad and blocks `caplets daemon stop` when the config file is missing.
Update the logic in the daemon entry path so the `readDaemonConfig(paths)`
failure only throws for actions that need config (such as start/restart), while
`stop` can continue and call `DaemonManager.stop` without requiring `persisted`.
Keep the action check near `refreshDaemonServeConfig`/`config` selection so the
behavior for stop remains independent of the config file.
In `@packages/core/src/daemon/process.ts`:
- Around line 32-34: The `resolveServeOptions()` path is dropping the
`preserveUnauthenticatedAuth` sentinel too early, so `defaults` can still
override legacy credential-free daemons. Update the `process.ts` resolver logic
around `serveRaw`/`resolveServeOptions()` to detect
`preserveUnauthenticatedAuth` and force the unauthenticated HTTP auth behavior
before applying global defaults, rather than deleting the flag and continuing
with the normal merge. Make sure the fix preserves existing local daemon auth
state across restarts when `serve.allowUnauthenticatedHttp` is false, and keep
the behavior localized to the `resolveServeOptions()` / `mergeServeOverrides()`
flow.
In `@packages/core/src/serve/options.ts`:
- Around line 92-93: When CAPLETS_SERVER_URL is present, the publicOrigins logic
in options.ts is dropping any configured secondary origins by replacing
defaults.publicOrigins with only serverUrl.origin. Update the publicOrigins
selection so the env-provided origin is included without discarding
defaults.publicOrigins, preserving the existing multi-origin list used by the
attach/auth flow. Use the publicOrigins and publicOrigin handling in the serve
options builder to locate the fix.
In `@schemas/caplets-config.schema.json`:
- Around line 72-79: Tighten the `publicOrigins` schema so it matches
`parseConfig()` runtime validation: update `schemas/caplets-config.schema.json`
to constrain each entry to an origin-only HTTP(S) URL with no path, query,
fragment, or userinfo, and apply the same rule in
`apps/landing/public/config.schema.json` so both published schemas stay aligned
with the config parser’s contract.
---
Outside diff comments:
In `@packages/core/src/cli/setup.ts`:
- Around line 462-476: The setup flow in setup.ts is rejecting persisted daemon
hosts too early, which prevents the repair path from reinstalling or restarting
a safe local daemon. Update the logic around the status/config handling in the
setup operation so the unsafe-host check does not short-circuit before
installCredentialFreeLocalSetupDaemon(operation) can run; only reuse the daemon
when isCredentialFreeLocalSetupDaemon(status.config) is true and the daemon is
healthy. Keep the existing reuse branch in the main success path, but allow
non-loopback or otherwise unsafe configs to fall through to the
reinstall/recovery path instead of calling
assertCredentialFreeLocalSetupDaemonHost(status.config) upfront.
In `@packages/core/src/serve/http.ts`:
- Around line 157-165: The trust-proxy preflight check in http.ts still only
looks at options.publicOrigin, so remote_credentials with trustProxy incorrectly
fails even when options.publicOrigins is provided. Update the guard around the
CapletsError in the HTTP startup path to accept either publicOrigin or
publicOrigins as satisfying the requirement, keeping the check aligned with the
rest of the file’s public origin handling.
---
Nitpick comments:
In `@packages/core/test/setup-runner.test.ts`:
- Around line 976-980: The `daemonClientBaseUrl` test stub currently bypasses
the production loopback guard by always returning a URL, so update it to mirror
the real helper’s behavior by validating the `config.serve.host` before
constructing the URL. Use the existing `daemonClientBaseUrl` stub in
`setup-runner.test.ts` and make it reject non-loopback hosts the same way the
production helper does, so the non-loopback setup tests still fail if the guard
moves from `assertCredentialFreeLocalSetupDaemonHost()` into
`daemonClientBaseUrl()`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 8b08bd78-c60d-41fe-a1c2-93594d11f037
📒 Files selected for processing (27)
.changeset/global-serve-config.mdCONCEPTS.mdREADME.mdapps/docs/src/content/docs/configuration.mdxapps/docs/src/content/docs/install.mdxapps/docs/src/content/docs/reference/config.mdxapps/docs/src/content/docs/troubleshooting.mdxapps/landing/public/config.schema.jsondocs/architecture.mddocs/plans/2026-06-30-002-feat-global-serve-config-defaults-plan.mdpackages/core/src/cli.tspackages/core/src/cli/setup.tspackages/core/src/config.tspackages/core/src/daemon/index.tspackages/core/src/daemon/process.tspackages/core/src/daemon/types.tspackages/core/src/serve/http.tspackages/core/src/serve/options.tspackages/core/test/cli.test.tspackages/core/test/config.test.tspackages/core/test/serve-daemon.test.tspackages/core/test/serve-http.test.tspackages/core/test/serve-options.test.tspackages/core/test/setup-runner.test.tsschemas/caplets-config.schema.jsonscripts/check-public-docs.tsscripts/generate-docs-reference.ts
✅ Files skipped from review due to trivial changes (7)
- .changeset/global-serve-config.md
- docs/architecture.md
- CONCEPTS.md
- apps/docs/src/content/docs/configuration.mdx
- apps/docs/src/content/docs/troubleshooting.mdx
- apps/docs/src/content/docs/reference/config.mdx
- apps/docs/src/content/docs/install.mdx
🚧 Files skipped from review as they are similar to previous changes (2)
- README.md
- packages/core/test/cli.test.ts
Preserve local-only daemon setup without remote auth friction while tightening project config validation, native default precedence, local daemon discovery, and setup/docs contracts from PR review.
Require credential-free local-daemon attach fallback to match the setup-written daemon config and pass native running plus health checks before bypassing remote credentials. Spoofed loopback services now fall back to remote credential requirements.
Summary
Local onboarding now routes agents through a managed Caplets daemon instead of asking every MCP client to run the full Caplets runtime, and the local-only path stays credential-free. The same daemon/HTTP serving surface can now be shaped with safe global
servedefaults, so users can customize host, port, base path, auth, proxy trust, and public origins without letting project configs widen the server.What changed
caplets setupcreates or validates user config, installs or reuses a healthy local daemon, and only then mutates selected MCP/native integrations.add-mcpprogrammatic API, but Caplets owns the picker and writes each client as a thincaplets attach <local-daemon-url>command.servedefaults for the HTTP daemon/serve path, including multiple public origins; projectserveconfig is ignored with warnings for safety.Validation
pnpm verifypnpm verifyduringgit push -u origin HEAD; the full suite reported 137 test files and 1990 tests passing, benchmark docs up to date, and build completed.publicOriginscredential-host gap afterce-code-reviewsubagents timed out; fixed and covered by a regression inpackages/core/test/serve-http.test.ts.Summary by CodeRabbit
New Features
caplets attach-based client wiring.servedefaults, including public origins and restart-friendly settings.Bug Fixes
Documentation