Skip to content

Fix sandbox config mount path and repeated first-time setup (Fixes #3081) - #3091

Merged
acoliver merged 9 commits into
mainfrom
issue3081
Aug 6, 2026
Merged

Fix sandbox config mount path and repeated first-time setup (Fixes #3081)#3091
acoliver merged 9 commits into
mainfrom
issue3081

Conversation

@acoliver

@acoliver acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

TLDR

Fixes the container sandbox path mismatch that made every sandboxed LLxprt launch look like a fresh install. The host global config directory was mounted at the legacy dot-llxprt destination, while the in-container CLI resolves config through the OS-standard path. The sandbox now mounts the canonical config directory at path parity, pins LLXPRT_CONFIG_HOME to the mount, and keeps data/cache/log roots container-local via entrypoint exports derived from the real container HOME.

Also corrects fresh legacy migrations so welcomeConfig.json, trustedFolders.json, and user skills/ are routed to the config directory where the application reads them.

Dive Deeper

Container sandbox

  • Resolves the host config directory dynamically through Storage.getGlobalConfigDir(), so runtime migration fallback and explicit host overrides are honored.
  • Mounts that directory at the translated path-parity destination and sets LLXPRT_CONFIG_HOME to it.
  • Removes the obsolete container-home dot-llxprt destination that triggered a phantom legacy migration on every start.
  • Exports LLXPRT_DATA_HOME, LLXPRT_CACHE_HOME, and LLXPRT_LOG_HOME from the real container $HOME inside the trusted entrypoint. This is required because those roots otherwise fall back to LLXPRT_CONFIG_HOME, which would redirect ephemeral state into the host config mount. Resolving them container-side also supports custom images whose default home is not /home/node.
  • Rejects SANDBOX_ENV attempts to override any of the four canonical roots before image/network/SSH side effects.
  • Uses the Podman :z shared SELinux label for the writable config bind mount.
  • Keeps the security boundary unchanged: only the same global config directory crosses. The data directory is deliberately not mounted because it contains OAuth/provider credentials; credential access remains through the proxy.

Legacy migration

Fresh/unmarked migrations now route these entries to config rather than data:

  • welcomeConfig.json
  • trustedFolders.json
  • skills/

The migration marker remains version 1. A full marker bump was rejected because re-running the entire copy pass could resurrect deliberately deleted credentials, hooks, commands, profiles, and .env files. Recovery of entries already misplaced by a completed v1 migration is tracked separately in #3085 as a bounded data-to-config reconciliation.

Top-level skills/ deterministically wins over the historical tmp/skills/ source on same-name collisions.

Review and verification

  • Deep architecture review and Open Code Review completed; all actionable findings were remediated.
  • Added Bun-registered behavioral coverage for container argv composition, resolver/mount agreement, custom/current-user HOME handling, Windows path translation, reserved-root validation ordering, Podman labeling, migration categorization, marker safety, and skills precedence.
  • Updated sandbox documentation, including Windows path translation and non-legacy SSH-agent socket examples.

Reviewer Test Plan

  1. Run the complete validation suite:

    npm run test
    npm run lint
    npm run typecheck
    npm run format
    npm run build
    bun scripts/start.ts --profile-load stepfun-37 "write me a haiku and nothing else"
    
  2. Inspect generated container args via the focused suite:

    bun test --preload ./test-setup/augment-bun-vi.ts packages/cli/src/utils/sandbox-containers.test.ts
    bun test packages/cli/test-bun/sandbox-env.bun.ts
    bun test packages/cli/test-bun/pathMigration.issue3081.bun.ts
    
  3. Launch a Docker or Podman sandbox twice from a configured host install. Confirm:

    • the active profile/settings are visible on both launches;
    • the welcome wizard does not reappear;
    • the config root maps to the host canonical directory;
    • data/cache/log roots are under the container HOME, not under the mounted config root.
  4. Set SANDBOX_ENV=LLXPRT_CONFIG_HOME=/tmp/override and confirm startup fails before image/network/SSH setup with the reserved-key error.

Testing Matrix

🍏 🪟 🐧
npm run
npx
Docker
Podman - -
Seatbelt - -

macOS Docker and Podman bind-mount checks passed. Windows and Linux behavior is covered by the platform-focused Bun tests; no physical Windows/Linux host was used for this PR. Seatbelt is unaffected by this container-only mount change and already resolves canonical roots directly through Storage.

Linked issues / bugs

Fixes #3081

Follow-up recovery for already-completed v1 migrations: #3085

Related security boundary: #2957

Summary by CodeRabbit

  • New Features

    • Sandbox containers now preserve global configuration while keeping data, cache, and logs container-local.
    • Sandbox environment settings are validated before startup, with clearer handling of invalid or reserved variables.
    • Configuration migration now includes welcome settings, trusted folders, and skills.
  • Bug Fixes

    • Migration ordering prevents newer skills from being overwritten by legacy files.
    • SSH-agent setup guidance now uses stable socket locations.
  • Documentation

    • Updated sandbox and Podman macOS setup instructions to reflect current storage and SSH-agent behavior.

The container sandbox bind-mounted the host global config directory at the
legacy /home/node/.llxprt destination, but the CLI inside the container
resolves its config directory through Storage to /home/node/.config/llxprt-code.
Nothing was mounted where the CLI looks, so every sandboxed launch saw an
empty configuration and re-ran the first-time setup wizard, re-prompted folder
trust, and could not see profiles, subagents, prompts, commands, policies,
hooks or global memory.

Mount the host config directory at path parity and pin all four canonical
roots with LLXPRT_CONFIG_HOME / LLXPRT_DATA_HOME / LLXPRT_CACHE_HOME /
LLXPRT_LOG_HOME so in-container resolution no longer depends on the container
HOME. All four must be pinned: data, cache and log fall back to
LLXPRT_CONFIG_HOME, so setting config alone would redirect container-local
state into the mounted host directory. Data, cache and log stay
container-local; mounting the data directory would push raw OAuth and provider
credentials across the sandbox boundary, which is what #2946 removed.

The legacy destination also made the in-container startup migration treat the
mount as a legacy directory and copy the whole config into throwaway paths on
every start. That exposed a second defect: categorizeEntry routed
welcomeConfig.json, trustedFolders.json and skills/ to the data directory even
though the application reads all three from the config directory. That is a
host bug too - migrating from a legacy ~/.llxprt silently dropped the welcome
marker, trusted-folder decisions and user skills. Add them to CONFIG_ENTRIES
and bump the migration marker so already-migrated users re-place them.
- Revert MIGRATION_MARKER_VERSION 2→1: the bump re-ran the whole legacy copy
  pass and recreated every entry the user deleted, including oauth_creds.json
  (deleted on logout), which would silently re-authenticate a logged-out user.
  Replaced the stale-marker test with one pinning the actual safety property:
  a current marker means a deleted canonical entry is never recreated.
- Resolve the config dir dynamically via Storage.getGlobalConfigDir() so the
  runtime legacy-fallback (cli.tsx sets LLXPRT_CONFIG_HOME at runtime) is
  honoured by the mount, not the module-load-time constant.
- Pin the ephemeral data/cache/log roots from the real container HOME inside
  the entrypoint (not as host --env), so custom sandbox images whose default
  user home is not /home/node are not forced to mkdir under it. Filter those
  roots from SANDBOX_ENV so they cannot shadow the entrypoint export.
- Collapse the async shouldUseCurrentUserInSandbox wrapper (identical sync
  semantics) and update both call sites.
- Pass containerHome to mountGitConfigFiles instead of the hard-coded value;
  drop the defensive existsSync/mkdir (the startup lifecycle guarantees it);
  add the :z SELinux shared label to the config mount under podman.
- Define explicit top-level skills/ precedence over tmp/skills/ via a
  deterministic sort (no-overwrite copy semantics make first-writer win).
- Rewrite the inert/tautological tests and add the invariants the change
  relies on (HOME agreement, current-user branch, dynamic config dir, single
  CONFIG_HOME under SANDBOX_ENV, Windows path translation, entrypoint roots).
@github-actions github-actions Bot added the maintainer:e2e:ok Trusted contributor; maintainer-approved E2E run label Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4985c3af-094c-43f6-98f3-80429ee1722e

📥 Commits

Reviewing files that changed from the base of the PR and between 2f377bc and 027f199.

📒 Files selected for processing (4)
  • packages/cli/src/utils/sandbox-containers.test.ts
  • packages/cli/src/utils/sandbox-env.ts
  • packages/cli/test-bun/pathMigration.issue3081.bun.ts
  • packages/cli/test-bun/sandbox-env.bun.ts
📝 Walkthrough

Walkthrough

The PR updates legacy path migration and aligns container sandbox mounts, environment variables, home resolution, entrypoint paths, SSH-agent setup, tests, and documentation with canonical platform directories.

Changes

Sandbox paths and legacy migration

Layer / File(s) Summary
Legacy migration routing
packages/cli/src/config/pathMigration.ts, packages/cli/src/config/*.test.ts, packages/cli/test-bun/pathMigration.issue3081.bun.ts, scripts/bun-test-manifest.ts
Configuration entries now include welcomeConfig.json, trustedFolders.json, and skills. Legacy entries are processed in sorted order. Migration marker fixtures use the exported version constant.
Sandbox environment and home resolution
packages/cli/src/utils/sandbox-env.ts, packages/cli/src/utils/sandbox-ssh.ts, packages/cli/src/utils/sandbox-ssh.test.ts, packages/cli/test-bun/sandbox-env.bun.ts
Current-user detection uses synchronous filesystem access. Container-home resolution is shared by sandbox setup and SSH-agent handling.
Canonical container mounts and environment pinning
packages/cli/src/utils/sandbox-containers.ts, packages/cli/src/utils/sandbox-entrypoint.ts, packages/cli/src/utils/sandbox-exec.ts, packages/cli/src/utils/sandbox-containers.test.ts
Container setup mounts the resolved global configuration directory, pins canonical home variables, validates SANDBOX_ENV before side effects, and exports container-local data, cache, and log paths.
Sandbox path and SSH documentation
docs/sandbox.md, docs/tutorials/sandbox-setup.md
Documentation describes canonical configuration mounts, ephemeral container-local directories, and stable SSH-agent socket paths.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary sandbox configuration mount fix and repeated first-time setup issue.
Description check ✅ Passed The description covers the required change summary, implementation details, testing plan, matrix, and linked issues.
Linked Issues check ✅ Passed The changes address issue #3081 by fixing canonical mounts, migration routing, environment pinning, side-effect validation, and behavioral coverage.
Out of Scope Changes check ✅ Passed The code, tests, and documentation changes are directly related to the sandbox mount and migration objectives.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue3081

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR changes 16 file(s).

  • packages/cli/test-bun/sandbox-env.bun.ts: (per-file summary unavailable)
  • packages/cli/src/utils/sandbox-exec.ts: (per-file summary unavailable)
  • packages/cli/src/config/pathMigration.ts: (per-file summary unavailable)
  • packages/cli/src/utils/sandbox-containers.test.ts: (per-file summary unavailable)
  • project-plans/issue-3081-sandbox-canonical-config-mount.md: (per-file summary unavailable)
  • packages/cli/src/utils/sandbox-ssh.ts: (per-file summary unavailable)
  • packages/cli/src/utils/sandbox-entrypoint.ts: (per-file summary unavailable)
  • docs/tutorials/sandbox-setup.md: (per-file summary unavailable)
  • packages/cli/test-bun/pathMigration.issue3081.bun.ts: (per-file summary unavailable)
  • scripts/bun-test-manifest.ts: (per-file summary unavailable)
  • packages/cli/src/config/pathMigration.test.ts: (per-file summary unavailable)
  • docs/sandbox.md: (per-file summary unavailable)
  • packages/cli/src/config/pathMigration.profileRepair.test.ts: (per-file summary unavailable)
  • packages/cli/src/utils/sandbox-containers.ts: (per-file summary unavailable)
  • packages/cli/src/utils/sandbox-ssh.test.ts: (per-file summary unavailable)
  • packages/cli/src/utils/sandbox-env.ts: (per-file summary unavailable)

Changes

Layer File(s) Summary
packages/cli/test-bun packages/cli/test-bun/sandbox-env.bun.ts, packages/cli/test-bun/pathMigration.issue3081.bun.ts Changes in packages/cli/test-bun
packages/cli/src/utils packages/cli/src/utils/sandbox-exec.ts, packages/cli/src/utils/sandbox-containers.test.ts, packages/cli/src/utils/sandbox-ssh.ts, packages/cli/src/utils/sandbox-entrypoint.ts, packages/cli/src/utils/sandbox-containers.ts, packages/cli/src/utils/sandbox-ssh.test.ts, packages/cli/src/utils/sandbox-env.ts Changes in packages/cli/src/utils
packages/cli/src/config packages/cli/src/config/pathMigration.ts, packages/cli/src/config/pathMigration.test.ts, packages/cli/src/config/pathMigration.profileRepair.test.ts Changes in packages/cli/src/config
project-plans project-plans/issue-3081-sandbox-canonical-config-mount.md Changes in project-plans
docs/tutorials docs/tutorials/sandbox-setup.md Changes in docs/tutorials
scripts scripts/bun-test-manifest.ts Changes in scripts
docs docs/sandbox.md Changes in docs

Magnitude

🎯 2 (M)
1093 additions, 77 deletions, 16 changed files across 1 package, 2 acceptance criteria

Related

No related items found.


Walkthrough generated by LLxprt PR Review. Planner issue: #2256

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/cli/src/utils/sandbox-containers.test.ts`:
- Around line 549-556: Update volumeDestinations to parse Windows host paths
before extracting the container destination: handle a drive-letter prefix such
as C:\ so the destination is taken from the separator after the host path, while
preserving existing Unix volume and options parsing behavior.
🪄 Autofix

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: 3e14cb50-47d9-40b2-ab25-2e9e945bdf74

📥 Commits

Reviewing files that changed from the base of the PR and between 9adcdbf and 2f377bc.

⛔ Files ignored due to path filters (1)
  • project-plans/issue-3081-sandbox-canonical-config-mount.md is excluded by !project-plans/**
📒 Files selected for processing (15)
  • docs/sandbox.md
  • docs/tutorials/sandbox-setup.md
  • packages/cli/src/config/pathMigration.profileRepair.test.ts
  • packages/cli/src/config/pathMigration.test.ts
  • packages/cli/src/config/pathMigration.ts
  • packages/cli/src/utils/sandbox-containers.test.ts
  • packages/cli/src/utils/sandbox-containers.ts
  • packages/cli/src/utils/sandbox-entrypoint.ts
  • packages/cli/src/utils/sandbox-env.ts
  • packages/cli/src/utils/sandbox-exec.ts
  • packages/cli/src/utils/sandbox-ssh.test.ts
  • packages/cli/src/utils/sandbox-ssh.ts
  • packages/cli/test-bun/pathMigration.issue3081.bun.ts
  • packages/cli/test-bun/sandbox-env.bun.ts
  • scripts/bun-test-manifest.ts

Comment thread packages/cli/src/utils/sandbox-containers.test.ts
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

OpenCodeReview — automatic reviews suspended

Automatic OCR reviews are suspended for this PR after 2 of 2 automatic reviews.

To get more reviews you can:

  • Check the box below to re-enable automatic reviews (resets the counter), or

  • Comment /review, /ocr, or /open-code-review to request a single review on demand.

  • Re-enable automatic reviews

@acoliver

acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the Windows volume-source parsing finding in commit 5460833. The test helper now skips the drive-letter colon before extracting the container destination, preserves Unix and optional volume-label parsing, and has a focused Windows-source regression test. The sandbox container suite passes 41/41.

Comment thread packages/cli/test-bun/sandbox-env.bun.ts Outdated
Comment thread packages/cli/src/utils/sandbox-ssh.test.ts
Comment thread packages/cli/test-bun/pathMigration.issue3081.bun.ts Outdated
Comment thread packages/cli/test-bun/pathMigration.issue3081.bun.ts Outdated
@acoliver

acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the quoted os-release finding in commit ee3ce84. Sandbox user auto-detection now parses ID and ID_LIKE values, strips optional single or double quotes, and matches complete whitespace-delimited ID_LIKE tokens. Added direct Bun coverage for quoted/unquoted Debian, Ubuntu, and Debian-derived values; 20/20 sandbox-env tests pass.

@acoliver

acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Evaluated this filesystem-stub finding against the source and did not change the tests. shouldUseCurrentUserInSandbox reads only process.env, os.platform(), and fs.readFileSync('/etc/os-release'); it does not call os.homedir(), resolve XDG paths, or read any other file. Both stubs are intentionally path-conditional and delegate every non-os-release read to the real readFileSync, which is narrower and more deterministic than a broad fake HOME setup. The suggested broader stub would reduce coverage and would not exercise any actual current path.

@acoliver

acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed both duplicate migration-safety findings in commit 8ef3f49. The test now invokes the real runStartupMigrationWithPath orchestrator after asserting the marker gate, then verifies the deleted oauth_creds.json remains absent. performMigration itself intentionally does not read the marker; runStartupMigrationWithPath is the production authority that composes shouldMigrate with the copy pass. The focused migration suite passes 5/5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer:e2e:ok Trusted contributor; maintainer-approved E2E run

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Container sandbox mounts the global config at the legacy ~/.llxprt path, so first-time setup runs on every sandboxed start

1 participant