Skip to content

Repo optimization: lifecycle & security fixes, release readiness, roadmap wave 1 - #18

Merged
Liohtml merged 6 commits into
mainfrom
claude/repo-optimization-maintenance-izvium
Jul 30, 2026
Merged

Repo optimization: lifecycle & security fixes, release readiness, roadmap wave 1#18
Liohtml merged 6 commits into
mainfrom
claude/repo-optimization-maintenance-izvium

Conversation

@Liohtml

@Liohtml Liohtml commented Jul 29, 2026

Copy link
Copy Markdown
Owner

What & why

Maintenance batch from a full repo audit (code audit + OSS-maturity audit + ecosystem research), in three commits:

1. Critical fixes

  • Detach no longer destroys the sandbox. The container now runs detached with the entry script as PID 1; the terminal attaches via docker exec. Previously the tmux attach client was PID 1, so Ctrl+b d killed the lead, all teammates and the Observer, and --rm deleted the container — the opposite of the documented behavior.
  • Teammates appear as tmux panes again. The lead launches with --teammate-mode tmux; since Claude Code v2.1.179 the upstream default is in-process, which silently disabled agentbox's signature split-pane layout (verified against the Agent Teams docs).
  • Hardening: Observer stored-XSS escaping + EADDRINUSE handling, entry script moved to a per-user 0700 temp dir (tamper/TOCTOU), fail-fast validation for --port/--memory/--cpus/unknown flags, rebuild refuses while the box runs, down reports real failures, ~/.claude.json symlink/directory edge cases, shell-free launcher (Windows injection), doctor uid warning.

2. Release readiness

  • Package renamed to @liohtml/agentbox — the bare npm name agentbox is an npm security placeholder and can't be published to; npx agentbox outside the repo was a footgun (README fixed).
  • tsx moved to runtime dependencies (the published package was unrunnable for end users), publishConfig with provenance, tag-triggered release workflow.
  • CI: Node 22+24 matrix + npm pack tarball smoke test; SECURITY.md (incl. the ~/.claude settings/hooks container-to-host escape channel), CODE_OF_CONDUCT, PR/issue templates, Dependabot, CHANGELOG, RELEASING (replaces PUBLISH.md, which contained a personal email).

3. Roadmap wave 1 (from ecosystem research, see ROADMAP.md)

  • --yolo (opt-in --dangerously-skip-permissions for the lead), persistent npm/cargo/pip cache volumes, read-only ~/.gitconfig + ~/.config/gh forwarding with gh in the image, --gpus passthrough, doctor image/host arch check.

Scope

  • src/cli/ — arg parsing, help, config, command router
  • src/docker/ — image build, container run/exec, mounts
  • src/session/ — tmux entry script, TTY sizing
  • assets/ — Dockerfile / Observer
  • src/types.ts / CONTRACT.md — contract change (explain below!)
  • Docs / CI / meta

Checklist

  • npm run typecheck passes
  • npm test passes (45 → 76 tests)
  • No build step; tsx intentionally became a runtime dependency so the published package works via npx (CONTRIBUTING's no-build constraint is unchanged)
  • Contract friction described: DockerModule gained execOk/imageArchitecture; runContainer is now detached; the entry script idles as PID 1 instead of exec tmux attach; new config fields yolo/gpus/gitconfigPath/ghConfigDir. CONTRACT.md §5/§6 updated accordingly.

Notes

Touches all modules because it consolidates an audit; the three commits are separable if needed. The weekly "Research digest" routine will file follow-up issues; remaining roadmap items are tracked in ROADMAP.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RfjQjysq5JF7gQCJB3RQtP


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added --gpus passthrough and opt-in --yolo permission-skip mode.
    • Enhanced doctor with architecture, mount-safety, and config checklist reporting.
  • Bug Fixes
    • Stricter CLI/config validation and clearer error messages (including missing value flags).
    • Improved container lifecycle: up cleans stale stopped boxes and detaching doesn’t tear down the sandbox.
  • Documentation
    • Updated usage for @liohtml/agentbox, refreshed help/contract/security info, and expanded release/contribution guidance.

claude added 4 commits July 29, 2026 17:40
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfjQjysq5JF7gQCJB3RQtP
…x + pack smoke test

- Rename package to @liohtml/agentbox (bare 'agentbox' on npm is a security
  placeholder and cannot be claimed); bin command stays 'agentbox'.
- Move tsx to dependencies so the published package runs via npx/global
  install without dev setup; add prepublishOnly guard + publishConfig
  (public access, provenance).
- CI: Node 22+24 matrix, npm pack smoke test that installs the tarball and
  runs the CLI as an end user; narrow push triggers to main.
- Add tag-triggered release workflow (npm publish --provenance).
- Add SECURITY.md (threat model + private reporting), CODE_OF_CONDUCT.md,
  PR template, issue-template config, dependabot config, CHANGELOG.md.
- Replace one-time PUBLISH.md (contained personal email) with generic
  RELEASING.md; README: badges + corrected npx instructions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfjQjysq5JF7gQCJB3RQtP
…launcher hardening

Consolidated fixes from a code audit + upstream research:

- Run the container detached with the entry script as PID 1 and attach via
  'docker exec tmux attach'. Previously the tmux attach client was PID 1, so
  Ctrl+b d (detach) killed the lead, all teammates and the Observer, and
  --rm deleted the container — the opposite of the documented behavior.
- Launch the lead with --teammate-mode tmux: since Claude Code v2.1.179 the
  default teammateMode is 'in-process', which silently disabled the
  split-pane team layout agentbox is built around.
- Fail fast with clear errors on invalid --port/--memory/--cpus, unknown
  flags, stray positionals, ':' in mount paths, and a nonexistent explicitly
  requested workspace (no more silently created empty mounts).
- rebuild refuses while the box is running (used to rebuild then silently
  reattach to the old container); reattach warns when tuning flags are
  ignored; stale stopped containers are removed before docker run.
- down surfaces real 'docker rm -f' failures instead of claiming success.
- ensureHostMounts: clear error when ~/.claude.json is a directory; follow
  symlinked configs (dotfile managers); benign EEXIST race handling.
- Entry script moves to a per-user 0700 temp subdir (was a fixed
  world-shared /tmp path: tamper/TOCTOU + collision); never write through a
  pre-existing symlink.
- Observer: HTML-escape project/session names (stored XSS), handle
  EADDRINUSE with a readable message, remain-on-exit for the observer tmux
  window, port sanity fallback, drop dead tail() code.
- Launcher: invoke local tsx via node directly (no shell) — survives paths
  with spaces, removes cmd.exe metacharacter injection on Windows.
- doctor: warn on host uid != 1000 (bind-mount write failures).
- Dockerfile: CLAUDE_CODE_VERSION/TSX_VERSION build args for reproducible
  builds.
- Contract & README updated; tests: 45 → 65 (new cli router suite, config
  validation, observer + session assertions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfjQjysq5JF7gQCJB3RQtP
…rwarding, --gpus, arch check

- --yolo: launch the lead with --dangerously-skip-permissions (teammates
  inherit it). Strictly opt-in with a visible warning; documented under
  Security & trust.
- Persistent tool-cache named volumes (agentbox-npm-cache,
  agentbox-cargo-registry, agentbox-pip-cache): boxes stay --rm-disposable,
  package downloads survive. Mount points pre-created agent-owned in the
  image so first use isn't root-owned.
- Host identity forwarding: ~/.gitconfig and ~/.config/gh bind-mounted
  READ-ONLY when present; gh CLI installed in the image. Same philosophy as
  Claude auth: borrow host identity, never store it.
- --gpus passthrough to docker run.
- doctor: image-vs-host architecture check (warns about qemu-emulated
  images on e.g. Apple Silicon).
- docker run argv construction extracted as pure buildRunArgs() and covered
  by a new Docker-free test suite (the audit's top missing-coverage item).
- Docs: README flags/mounts/security, CONTRACT mounts + cache volumes,
  ROADMAP wave 1 marked done, CHANGELOG. Tests: 65 → 76, all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfjQjysq5JF7gQCJB3RQtP
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Liohtml, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ce053ba5-4ae8-4dff-a50a-5f8ef3115a70

📥 Commits

Reviewing files that changed from the base of the PR and between 51c1d77 and e343354.

📒 Files selected for processing (1)
  • bin/agentbox.mjs
📝 Walkthrough

Walkthrough

The PR hardens Agentbox configuration, Docker startup, tmux session management, Observer rendering, and CLI validation. It also adds package publishing metadata, CI and release workflows, dependency automation, security documentation, repository templates, and project documentation.

Changes

Agentbox runtime

Layer / File(s) Summary
Session and configuration contracts
src/types.ts, src/cli/config.ts, src/session/index.ts, tests/config.test.ts, tests/session.test.ts, CONTRACT.md, README.md
Adds GPU and yolo configuration, optional identity mounts, per-user entry-script paths, stricter filesystem handling, and a detached PID 1 tmux lifecycle with Agent Teams support.
Docker execution and CLI flow
src/cli/*, src/docker/index.ts, tests/cli.test.ts, tests/docker.test.ts
Adds strict CLI input handling, Docker argument construction, conditional mounts, stale-container cleanup, startup polling, and architecture and UID diagnostics.
Runtime assets and package execution
assets/*, bin/agentbox.mjs, package.json, tests/observer.test.ts
Adds pinned image tools and GitHub CLI, prepares cache directories, requires local tsx execution, hardens Observer behavior, and configures scoped npm publishing.

Repository delivery

Layer / File(s) Summary
Repository automation and release documentation
.github/*, CHANGELOG.md, SECURITY.md, RELEASING.md, ROADMAP.md, CODE_OF_CONDUCT.md
Adds issue and PR templates, Dependabot, Node 22/24 CI coverage, package smoke tests, tag-based provenance publishing, security reporting guidance, release instructions, changelog entries, and roadmap documentation.

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

Possibly related issues

  • Liohtml/agentbox issue 15 — Adds the security policy and private vulnerability-reporting path described by the issue.
  • Liohtml/agentbox issue 16 — Adds Docker argument-building and mount-logic tests described by the issue.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AgentboxCLI
  participant Docker
  participant Tmux
  User->>AgentboxCLI: Run agentbox up with validated options
  AgentboxCLI->>Docker: Start detached container
  Docker->>Tmux: Create or reuse session
  AgentboxCLI->>Tmux: Poll for session readiness
  AgentboxCLI->>Tmux: Attach client to session
  User->>Tmux: Detach client
  Tmux-->>Docker: Keep PID 1 alive while the session exists
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.57% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main themes: lifecycle/security hardening, release readiness, and roadmap updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/repo-optimization-maintenance-izvium

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 18

🤖 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 @.github/workflows/ci.yml:
- Around line 24-25: Disable persisted checkout credentials for every checkout
step: add persist-credentials: false to .github/workflows/ci.yml lines 24-25 and
46-47, and .github/workflows/release.yml lines 15 and 32.

In @.github/workflows/release.yml:
- Around line 11-22: The release gate must validate the packed artifact before
publication. In .github/workflows/release.yml lines 11-22, add the existing
clean-install npm pack smoke-test step after the source checks and before the
publish job or step; in RELEASING.md lines 25-26, retain the “all checks”
statement only after the release workflow’s smoke test is included.
- Around line 3-5: Update the release workflow’s tag-triggered publishing flow
to compare the pushed tag with v${package.json.version} before publishing. Add a
pre-publish assertion that fails on stale, mistyped, or otherwise mismatched
tags, while preserving the existing v* trigger.
- Around line 39-41: Align the release authentication strategy across
.github/workflows/release.yml (lines 39-41) and RELEASING.md (lines 34-35):
configure the npm publish step to use Trusted Publishing exclusively and update
the documentation accordingly, or remove/clarify the Trusted Publishing
documentation as unsupported while retaining NODE_AUTH_TOKEN-based publishing.
- Around line 16-20: Disable npm caching in both setup-node steps at
.github/workflows/release.yml lines 16-20 and 33-38. Remove the cache
configuration entirely, or configure caching without restore/write, while
preserving lockfile-based npm ci installation and the existing typecheck/test
and publish flows.

In `@assets/Dockerfile`:
- Around line 48-50: Update the CLAUDE_CODE_VERSION and TSX_VERSION
build-argument defaults in the Dockerfile to reviewed exact immutable versions
instead of latest, while preserving the arguments so upgrades remain intentional
overrides for the npm install command.

In `@bin/agentbox.mjs`:
- Around line 28-31: Remove the Windows shell-based global fallback from the
spawn logic in bin/agentbox.mjs, so process.argv.slice(2) is never passed with
shell: true or routed through cmd.exe. Use the local tsx runtime dependency
directly and fail clearly when it is unavailable, while preserving inherited
stdio and environment behavior.

In `@CHANGELOG.md`:
- Around line 61-64: Update the “Git identity + gh auth forwarding” changelog
entry to replace “never entering the image” with wording that clarifies
credentials are not baked into the image, while explicitly stating that
runtime-mounted credentials may remain readable inside the running container.

In `@CODE_OF_CONDUCT.md`:
- Around line 30-35: Update the “Enforcement” section of CODE_OF_CONDUCT.md to
name a maintainer contact or dedicated private channel for sensitive conduct
complaints, while retaining the GitHub issue option for non-sensitive matters.
Reserve the GitHub Security Advisories link specifically for security
vulnerability reports.

In `@src/cli/index.ts`:
- Around line 106-117: The reattach warning in the `up`/`rebuild` flow uses
invocation-time `config.yolo` instead of the existing container’s state. Persist
the startup yolo mode as a container label or environment marker when creating
the container, inspect that marker before reattaching, and base the warning on
the persisted state so both `up --yolo` and reattaching without `--yolo` report
accurately; update the related logic around `tuningFlagsGiven` as well.
- Around line 85-104: Restrict the startup validation block around
validateConfig to only the up and rebuild commands, so down remains usable with
invalid runtime settings. Update cmdDoctor to call validateConfig explicitly and
report every returned problem while retaining its checklist behavior. Keep the
explicit workspace existence check within the up/rebuild validation flow.

In `@src/cli/parser.ts`:
- Line 11: Update the argument parsing flow around VALUE_FLAGS to reject any
value-consuming flag, including --gpus, when it has no following value before
resolveConfig runs. Ensure bare flags produce the existing invalid-argument
behavior rather than being recorded as true and converted to undefined, while
preserving valid values for all flags in VALUE_FLAGS.

In `@src/docker/index.ts`:
- Around line 281-291: Update the symbolic-link handling block to inspect the
followed target with statSync and require isFile() before returning. If the
target exists but is not a regular file, reject it using the same corrective
guidance already used for invalid config targets; retain dangling-link creation
through fs.writeFileSync with flag "w".
- Around line 124-127: The Docker run configuration in src/docker/index.ts lines
124-127 must stop using automatic container removal so failed startup containers
and their logs remain available. Update the polling logic around the CLI startup
flow in src/cli/index.ts lines 214-222 to detect an early container exit, report
diagnostics from the retained container, and avoid waiting for the full timeout;
apply these changes using the relevant Docker argument construction and
container startup/polling symbols.

In `@src/session/index.ts`:
- Around line 136-147: The writeEntryScript path must not rely on a pre-existing
predictable temporary directory. Update the directory setup around mkdirSync so
it atomically creates a unique directory, or validates an existing directory is
owned by the current UID and has no group/world permissions before writing
entryScriptPath.

In `@src/types.ts`:
- Around line 75-82: Update the global flag surface in CONTRACT.md to include
the AgentboxConfig options yolo (--yolo) and gpus (--gpus), matching their
existing types and semantics. Keep the prose and machine-readable contract
entries consistent with each other and with the declarations in AgentboxConfig.

In `@tests/docker.test.ts`:
- Around line 59-65: Extend the “mounts workspace, claude dir/json, entry script
and observer” test to assert that the observer asset bind mount is included in
the mounts returned by buildRunArgs. Use the expected observerAsset source and
its corresponding container destination, preserving the existing assertions.

In `@tests/observer.test.ts`:
- Around line 60-62: Strengthen the test in “falls back to the default port when
OBSERVER_PORT is not a valid port” to verify the complete contract: integer
validation, inclusive 1–65535 bounds, and fallback to port 4317. Either match
the full validation expression in src or add cases covering invalid, zero, and
out-of-range values.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 775f86f3-e039-4799-a1c8-57ca7b43ef3c

📥 Commits

Reviewing files that changed from the base of the PR and between 4f9cfc9 and f97c386.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (29)
  • .github/ISSUE_TEMPLATE/config.yml
  • .github/PULL_REQUEST_TEMPLATE.md
  • .github/dependabot.yml
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • CHANGELOG.md
  • CODE_OF_CONDUCT.md
  • CONTRACT.md
  • PUBLISH.md
  • README.md
  • RELEASING.md
  • ROADMAP.md
  • SECURITY.md
  • assets/Dockerfile
  • assets/observer.mjs
  • bin/agentbox.mjs
  • package.json
  • src/cli/config.ts
  • src/cli/help.ts
  • src/cli/index.ts
  • src/cli/parser.ts
  • src/docker/index.ts
  • src/session/index.ts
  • src/types.ts
  • tests/cli.test.ts
  • tests/config.test.ts
  • tests/docker.test.ts
  • tests/observer.test.ts
  • tests/session.test.ts
💤 Files with no reviewable changes (1)
  • PUBLISH.md

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread src/docker/index.ts
Comment thread src/session/index.ts
Comment thread src/types.ts
Comment thread tests/docker.test.ts
Comment thread tests/observer.test.ts Outdated
claude added 2 commits July 30, 2026 06:20
…r lifecycle, remaining edge cases

Release workflow:
- persist-credentials: false on every checkout (ci.yml, release.yml).
- release.yml: verify the pushed tag matches package.json's version before
  publishing; run the npm-pack tarball smoke test as a gate before publish;
  drop dependency caching (a poisoned cache must never influence what gets
  published — CI keeps caching, only the sensitive publish workflow doesn't).
- RELEASING.md: stop implying npm Trusted Publishing is configured — the
  workflow authenticates with NODE_AUTH_TOKEN only.
- Dockerfile: pin CLAUDE_CODE_VERSION/TSX_VERSION defaults to reviewed exact
  versions instead of `latest`, so a plain rebuild is reproducible.
- Launcher: drop the shell-based global-tsx-on-PATH fallback — it forwarded
  raw argv through a shell (cmd.exe metacharacter risk on Windows); tsx is a
  runtime dependency now, so a missing local install is a clear error instead.

Container lifecycle:
- Stop using --rm: a container that exits early (entry-script crash) now
  stays around for `docker logs` to inspect instead of vanishing instantly.
  `up` removes a stale stopped container before starting fresh (already
  implemented); `down` still removes it explicitly; waitForSession also
  gives up early if the container itself exits, instead of polling the full
  timeout.
- Reattaching now reports the RUNNING container's actual --yolo state via a
  container label, instead of echoing the current invocation's flag (which
  is ignored on reattach and could claim the wrong thing in either direction).

Input handling:
- A value flag given with no value (e.g. trailing --gpus) is now a hard
  error instead of silently doing nothing.
- validateConfig (+ workspace-must-exist) now gates only up/rebuild — down/
  shell stay usable with a currently-invalid MEM/CPUS/workspace. doctor
  reports every validateConfig problem explicitly in its checklist instead
  of a narrower, separate ad hoc port check.

Other fixes:
- ~/.claude.json symlinked to a directory is now a clear error (previously
  it passed through silently and Docker mounted a directory).
- The entry-script temp directory's ownership/permissions are verified after
  creation (mkdirSync's mode arg is a no-op on a pre-existing dir), closing
  a race where another local user could pre-create it.
- CODE_OF_CONDUCT: separate conduct-report contact from the security channel.
- Doc wording fixes (CHANGELOG credential-mount phrasing; all --rm mentions
  updated to match the new lifecycle).

Tests: 76 → 87, all green; typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfjQjysq5JF7gQCJB3RQtP
…ded path

CI's npm-pack smoke test failed: when the tarball is installed as a
dependency in another project, npm hoists tsx to the CONSUMER's top-level
node_modules instead of nesting it under agentbox's own node_modules, so
the previous `<here>/../node_modules/tsx/dist/cli.mjs` path didn't exist.

Fix: use createRequire().resolve() to locate tsx the way Node itself would
(walking up node_modules, handling hoisting correctly in every install
shape — repo clone, hoisted consumer install, or nested global install).
Resolves "tsx/package.json" rather than "tsx/dist/cli.mjs" directly: tsx's
package.json declares an `exports` map that only allows a few subpaths, and
"./dist/cli.mjs" isn't one of them, so resolving it by specifier would throw
ERR_PACKAGE_PATH_NOT_EXPORTED even though the file exists on disk. Deriving
the cli.mjs path from the resolved package.json's directory sidesteps that:
it's a plain filesystem join, not a specifier resolution.

Verified locally by reproducing the CI job exactly: npm pack, install the
tarball into a scratch consumer project, run npx agentbox --help/doctor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfjQjysq5JF7gQCJB3RQtP
@Liohtml
Liohtml merged commit e29079b into main Jul 30, 2026
3 of 4 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/docker/index.ts (1)

269-317: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat only ENOENT as a missing config target.

Both lstatSync and the followed statSync catch every filesystem error. EACCES, ELOOP, and similar failures are then treated as missing paths and trigger a write attempt, masking the original failure. Re-throw errors other than ENOENT in both catches.

Proposed fix
-    } catch {
+    } catch (err) {
+      if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
...
-      } catch {
+      } catch (err) {
+        if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
🤖 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 `@src/docker/index.ts` around lines 269 - 317, Update the lstatSync and
followed statSync catch blocks in the Claude config setup to treat only ENOENT
as a missing path. Re-throw any other filesystem error, including EACCES or
ELOOP, before proceeding with directory creation or symlink-target writing;
preserve the existing missing-target behavior.
🧹 Nitpick comments (2)
tests/observer.test.ts (1)

60-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Good improvement over the prior loose regex check; new Function() flag here is a false positive.

The OpenGrep hint about dynamic code execution doesn't apply meaningfully — the evaluated code is extracted from the project's own trusted source file, not external input. That said, the two-line regex extraction is a bit fragile: any reformatting of the RAW_PORT/PORT lines in observer.mjs would break the match rather than the intended bounds logic. Exporting the port-resolution as a small pure function reusable by both the runtime and this test would remove the eval/regex coupling entirely.

🤖 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 `@tests/observer.test.ts` around lines 60 - 73, Replace the regex extraction
and new Function usage in resolvePort with a shared pure port-resolution
function exported from observer.mjs. Update the runtime to use this function and
have the test import and invoke it directly, preserving the existing integer,
range, and 4317 fallback behavior.

Source: Linters/SAST tools

src/session/index.ts (1)

160-167: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Narrow the catch so a failed rmSync isn't silently treated as "doesn't exist".

If lstatSync succeeds (symlink found) but rmSync then throws for a real reason (e.g. permission/immutable flag), the shared catch swallows it and writeFileSync proceeds to write through the still-present symlink — defeating the intent of this guard. Split the lookup from the removal so only the lstat's ENOENT is treated as "fine".

♻️ Proposed fix
-  try {
-    if (lstatSync(config.entryScriptPath).isSymbolicLink()) {
-      rmSync(config.entryScriptPath);
-    }
-  } catch {
-    // does not exist — fine
-  }
+  let existing;
+  try {
+    existing = lstatSync(config.entryScriptPath);
+  } catch {
+    existing = null; // does not exist — fine
+  }
+  if (existing?.isSymbolicLink()) {
+    rmSync(config.entryScriptPath);
+  }
🤖 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 `@src/session/index.ts` around lines 160 - 167, Update the symlink guard around
config.entryScriptPath so lstatSync handles only an ENOENT as the missing-path
case, while rmSync runs outside that catch and propagates removal errors.
Preserve the existing behavior of removing an existing symlink before
writeFileSync proceeds.
🤖 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 `@src/types.ts`:
- Around line 177-183: Preserve an explicit unavailable state for container
labels instead of treating Docker inspect failures as empty labels. In
src/types.ts lines 177-183, update containerLabels to return the chosen
unavailable or tagged result; in src/docker/index.ts lines 355-369, return that
state for inspect and JSON failures, and update reattach handling to report
“startup mode unknown” rather than inferring non-YOLO mode from a missing
LABEL_YOLO.

In `@tests/session.test.ts`:
- Around line 158-176: Update both writeEntryScript tests to wrap their setup,
assertions, and invocation in try/finally blocks, keeping the existing directory
cleanup in finally so it always runs when writeEntryScript or an assertion
fails. Preserve each test’s current assertions and behavior, including the
world-writable directory rejection.

---

Outside diff comments:
In `@src/docker/index.ts`:
- Around line 269-317: Update the lstatSync and followed statSync catch blocks
in the Claude config setup to treat only ENOENT as a missing path. Re-throw any
other filesystem error, including EACCES or ELOOP, before proceeding with
directory creation or symlink-target writing; preserve the existing
missing-target behavior.

---

Nitpick comments:
In `@src/session/index.ts`:
- Around line 160-167: Update the symlink guard around config.entryScriptPath so
lstatSync handles only an ENOENT as the missing-path case, while rmSync runs
outside that catch and propagates removal errors. Preserve the existing behavior
of removing an existing symlink before writeFileSync proceeds.

In `@tests/observer.test.ts`:
- Around line 60-73: Replace the regex extraction and new Function usage in
resolvePort with a shared pure port-resolution function exported from
observer.mjs. Update the runtime to use this function and have the test import
and invoke it directly, preserving the existing integer, range, and 4317
fallback behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a70fa907-b695-48c1-8efc-9e9ce942a1a6

📥 Commits

Reviewing files that changed from the base of the PR and between f97c386 and 51c1d77.

📒 Files selected for processing (21)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • CHANGELOG.md
  • CODE_OF_CONDUCT.md
  • CONTRACT.md
  • README.md
  • RELEASING.md
  • ROADMAP.md
  • SECURITY.md
  • assets/Dockerfile
  • bin/agentbox.mjs
  • src/cli/index.ts
  • src/cli/parser.ts
  • src/docker/index.ts
  • src/session/index.ts
  • src/types.ts
  • tests/cli.test.ts
  • tests/docker.test.ts
  • tests/observer.test.ts
  • tests/parser.test.ts
  • tests/session.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • ROADMAP.md
  • README.md
  • CONTRACT.md

Comment thread src/types.ts
Comment on lines +177 to +183
/**
* Labels of a container (e.g. LABEL_YOLO), or {} if the container is
* missing / docker is unavailable. Used on reattach to report the
* RUNNING container's actual startup mode instead of the current
* invocation's flags, which are ignored on reattach.
*/
containerLabels(name: string): Promise<Record<string, string>>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Preserve an explicit unknown state for container labels.

The interface and implementation collapse Docker inspect failures into {}, while src/cli/index.ts interprets a missing LABEL_YOLO as false. This can tell users that prompts are active when the running box was actually started with --yolo.

  • src/types.ts#L177-L183: change containerLabels to return an explicit unavailable state or tagged result.
  • src/docker/index.ts#L355-L369: return that state for inspect/JSON failures instead of {}, and make reattach output report “startup mode unknown”.
📍 Affects 2 files
  • src/types.ts#L177-L183 (this comment)
  • src/docker/index.ts#L355-L369
🤖 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 `@src/types.ts` around lines 177 - 183, Preserve an explicit unavailable state
for container labels instead of treating Docker inspect failures as empty
labels. In src/types.ts lines 177-183, update containerLabels to return the
chosen unavailable or tagged result; in src/docker/index.ts lines 355-369,
return that state for inspect and JSON failures, and update reattach handling to
report “startup mode unknown” rather than inferring non-YOLO mode from a missing
LABEL_YOLO.

Comment thread tests/session.test.ts
Comment on lines +158 to +176
posixOnly("writeEntryScript succeeds when it creates the temp dir itself", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agentbox-test-"));
fs.rmSync(dir, { recursive: true, force: true }); // let writeEntryScript create it fresh
const scriptPath = path.join(dir, "entry.sh");
const written = session.writeEntryScript(baseConfig({ entryScriptPath: scriptPath }), TTY);
assert.equal(written, scriptPath);
assert.ok(fs.existsSync(scriptPath));
fs.rmSync(dir, { recursive: true, force: true });
});

posixOnly("writeEntryScript refuses a pre-existing world-writable temp dir", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agentbox-test-"));
fs.chmodSync(dir, 0o777); // simulate another local user's permissive pre-created dir
const scriptPath = path.join(dir, "entry.sh");
assert.throws(
() => session.writeEntryScript(baseConfig({ entryScriptPath: scriptPath }), TTY),
/refusing to write the entry script/,
);
fs.rmSync(dir, { recursive: true, force: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Clean up temporary directories in finally blocks.

If writeEntryScript or an assertion fails, fs.rmSync is skipped, leaving test directories behind. Wrap both test bodies in try/finally so repeated or watch-mode runs do not leak directories.

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 169-169: Do not give 777 permissions to a file
Context: fs.chmodSync(dir, 0o777)
Note: [CWE-732] Incorrect Permission Assignment for Critical Resource.

(chmod-permissions-typescript)

🤖 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 `@tests/session.test.ts` around lines 158 - 176, Update both writeEntryScript
tests to wrap their setup, assertions, and invocation in try/finally blocks,
keeping the existing directory cleanup in finally so it always runs when
writeEntryScript or an assertion fails. Preserve each test’s current assertions
and behavior, including the world-writable directory rejection.

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.

2 participants