Skip to content

Worktrees#12

Merged
eenlars merged 18 commits intomainfrom
worktrees
Feb 5, 2026
Merged

Worktrees#12
eenlars merged 18 commits intomainfrom
worktrees

Conversation

@eenlars
Copy link
Owner

@eenlars eenlars commented Feb 4, 2026

Summary by CodeRabbit

  • New Features

    • Worktrees: create, list, and delete parallel-branch workspaces; integrated into uploads, images, chat streams, session/tab keys, and workspace resolution. New Worktree switcher UI with per-workspace persisted selection and wt URL param sync.
  • Documentation

    • Full docs and architecture guidance added for worktrees: rules, containment, locking, error codes, API shapes, and client UI behavior.
  • Tests

    • Extensive API, service, workspace-resolution, and UI tests covering success and error scenarios.

@coderabbitai
Copy link

coderabbitai bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

Adds a Git worktrees feature across server, service, client, types, stores, tests, and docs: new worktree service (list/resolve/create/remove), unified API route (GET/POST/DELETE), Zod schemas, ErrorCodes, UI WorktreeSwitcher and chat integration, session/workspace key changes to include worktree, broad plumbing to thread worktree through file/image/stream flows, and many tests/docs updates.

Changes

Cohort / File(s) Summary
Worktree service & tests
apps/web/features/worktrees/lib/worktrees.ts, apps/web/features/worktrees/lib/__tests__/worktrees.test.ts
New worktree library: list/resolve/create/remove, path containment, slug/branch/from validation, locking (.git/bridge-worktree.lock), WorktreeError codes, and comprehensive Git-backed tests.
Worktrees API & tests
apps/web/app/api/worktrees/route.ts, apps/web/app/api/worktrees/__tests__/route.test.ts
New route implementing GET/POST/DELETE with session + verifyWorkspaceAccess + getWorkspace flow, requestId propagation, mapWorktreeError → HTTP mapping, and exhaustive handler tests with mocks.
Schemas & runtime validation
apps/web/lib/api/schemas.ts, apps/web/types/guards/worktree-schemas.ts, apps/web/lib/api/__tests__/schemas.type.test.ts
Add typed API schemas for worktrees endpoints and Zod validators (WorktreeSlugSchema, OptionalWorktreeSchema) used in request validation and tests.
Error codes & messages
apps/web/lib/error-codes.ts
Introduce many WORKTREE_* error codes and user/help messages, plus FORBIDDEN mapping used by new flows.
Workspace resolution & retriever
apps/web/features/chat/lib/workspaceRetriever.ts, apps/web/features/workspace/lib/workspace-utils.ts
Make workspace resolution async, add WorkspaceRequestBody (workspace+worktree), centralize resolveWorktreeIfRequested, map worktree errors to public ErrorCodes/status, and reject worktrees for superadmin workspace.
Session / keys / store
apps/web/features/auth/lib/sessionStore.ts, apps/web/lib/stores/workspaceStore.ts, packages/shared/src/constants.ts, apps/web/features/workspace/lib/worktree-utils.ts
Add wt/ segment to tab/workspace keys (tabKey/parseKey), per-workspace currentWorktreeByWorkspace persistence, workspaceKey utilities, storage version bump, and validation helpers.
UI: Chat page & switcher
apps/web/app/chat/page.tsx, apps/web/app/chat/components/WorkspaceInfoBar.tsx, apps/web/components/workspace/WorktreeSwitcher.tsx
Thread worktree through chat page (wt URL param, workspaceKey), expose worktree props, add WorktreeSwitcher modal (list/create/remove), and wire selection persistence/UI hooks.
File/Image APIs & stores
apps/web/app/api/files/*, apps/web/app/api/images/*, apps/web/lib/stores/imageStore.ts
Propagate optional worktree through file/image endpoints and client stores; convert several getWorkspace/resolveWorkspace calls to awaited async.
Sandbox / file browser & caching
apps/web/features/chat/components/sandbox/**, apps/web/features/chat/components/sandbox/lib/file-api.ts, apps/web/features/chat/components/sandbox/hooks/useFileContent.ts
Thread worktree through FileTree, CodeViewer, list/read file APIs and hooks; include worktree in cache keys and invalidate logic.
Streams & session cancellation
apps/web/app/api/claude/stream/{route,cancel,reconnect}.ts, apps/web/app/api/claude/stream/cancel/__tests__/route.test.ts
Include worktree in stream/reconnect/cancel payloads and tabKey construction; normalize/validate in cancel endpoint; propagate worktree through reconnection/cancel logic and tests.
Chat hooks & uploads
apps/web/features/chat/hooks/*, apps/web/features/chat/components/ChatInput/*, apps/web/features/chat/utils/upload-handler.ts
Extend many hooks (messaging, reconnect, cancellation, image/upload, browser cleanup, attachments) to accept and propagate optional worktree parameter and include in upload multipart when present.
Workspace hook & utils tests
apps/web/features/workspace/hooks/useWorkspace.ts, apps/web/features/workspace/lib/__tests__/worktree-utils.test.ts
Expose worktree, workspaceKey, setWorktree from useWorkspace; add tests for normalize/validate/build/parse workspace keys.
API typing & Body schema
apps/web/types/guards/api.ts, apps/web/lib/api/schemas.ts
Re-export worktree schemas, add OptionalWorktreeSchema to BodySchema, and register typed worktrees endpoints.
Tests & mocks
multiple __tests__ (files, claude cancel, sessionStore, api schemas, etc.)
Add/adjust tests to cover worktree flows, update mocks to async getWorkspace, add worktree normalization/validation tests, and extend sessionStore tests for wt segment.
Worker-pool rename
packages/worker-pool/src/types.ts, packages/worker-pool/__tests__/*.test.ts, apps/web/lib/automation/executor.ts
Rename AgentConfig.streamTypes → bridgeStreamTypes across types, tests, and executor usage.
Docs & design
docs/features/worktrees.md, docs/architecture/workspace-isolation.md, docs/prs/worktrees/*.md
Add comprehensive docs/stage notes covering slug/branch rules, path model, containment checks, lock semantics, API docs, UI flows, and testing guidance.
Project scripts / config / namespace
apps/web/next.config.js, apps/web/tsconfig.json, ops/scripts, deployment scripts, package namespace updates
Namespace/path updates (@alive-brug@webalive), project root path changes (/root/... → /root/alive), standalone packaging/validation scripts, and minor config bumps.
Removed route
apps/web/app/api/priorities/route.ts
Deleted the priorities route and its DB-backed endpoints.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Browser
    participant API as /api/worktrees
    participant Auth as Auth Service
    participant Service as Worktree Service
    participant Git as Git Repo

    Client->>API: POST /api/worktrees (workspace, slug, branch, from)
    API->>Auth: getSessionUser()
    Auth-->>API: session | 401
    API->>Auth: verifyWorkspaceAccess(workspace)
    Auth-->>API: allowed | 401
    API->>Service: createWorktree(baseWorkspacePath, slug, branch, from, requestId)
    Service->>Service: validate & normalize slug/branch, assert base repo
    Service->>Service: acquire lock (.git/bridge-worktree.lock) -> 409 on contention
    Service->>Git: git worktree add / git ops
    Git-->>Service: success (path, head)
    Service->>Service: release lock
    Service-->>API: CreateWorktreeResult (slug, branch, worktreePath)
    API-->>Client: 201 Created { slug, branch, worktreePath }
Loading
sequenceDiagram
    participant Chat as Chat Page
    participant Hook as useWorkspace
    participant Store as WorkspaceStore
    participant URL as Browser URL

    Chat->>Hook: init (read wt param)
    Hook->>Store: get currentWorktree for workspace
    Hook->>Store: buildWorkspaceKey(workspace, worktree)
    Hook-->>Chat: { workspace, worktree, workspaceKey, setWorktree }
    Chat->>URL: sync wt param on change
    Chat->>Hook: setWorktree(slug)
    Hook->>Store: setCurrentWorktree(workspace, slug) -> persist
    Store-->>Hook: persisted
    Hook-->>Chat: updated worktree applied to UI and API calls
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I dug a tidy tunnel in code so neat,
slugs trimmed small and branches meet.
Locks hold firm while worktrees grow,
switch, create, remove — then off I go.
A hop, a test, a cozy seat.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Worktrees' is vague and generic—it names the feature but doesn't clarify what's being done (implemented, added, or refactored). A teammate scanning history wouldn't understand the scope from this title alone. Consider a more descriptive title like 'Add Git worktrees feature with API and UI support' or 'Implement worktrees with server routes and client components' to convey the primary change at a glance.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch worktrees

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🤖 Fix all issues with AI agents
In `@apps/web/app/api/worktrees/__tests__/route.test.ts`:
- Around line 6-29: Remove the vi.mock calls that stub
"@/features/auth/lib/auth" (getSessionUser, verifyWorkspaceAccess),
"@/features/chat/lib/workspaceRetriever" (getWorkspace), and
"@/features/worktrees/lib/worktrees" (listWorktrees, createWorktree,
removeWorktree) and convert the tests to integration-style: import and use real
test fixtures/helpers to create a real workspace/user and real worktree records,
call the route handlers (or HTTP endpoints) to exercise database state, assert
on real DB changes, include descriptive test names, ensure each test sets up and
tears down its data via provided test helpers (or beforeEach/afterEach) and add
tests for error paths (auth failure, create/remove failures).

In `@apps/web/app/api/worktrees/route.ts`:
- Around line 45-59: Replace direct session and workspace checks that use
getSessionUser() and verifyWorkspaceAccess() with the mandated API helpers: call
getCookieUserId() to obtain and validate the user id (return
createErrorResponse(ErrorCodes.NO_SESSION, 401, { requestId }) if
missing/invalid), then call isWorkspaceAuthenticated(userId, workspace) to
authorize the workspace (return
createErrorResponse(ErrorCodes.WORKSPACE_NOT_AUTHENTICATED, 401, { requestId,
workspace }) if it fails). Ensure you still validate workspace from new
URL(req.url).searchParams and return WORKSPACE_MISSING when empty, and keep the
same ErrorCodes and requestId usage so downstream logic remains unchanged.
- Around line 103-110: The POST handler currently calls verifyWorkspaceAccess
and returns a 401 for empty/whitespace workspaces; before calling
verifyWorkspaceAccess (after body = parsed), validate body.workspace by checking
if typeof body.workspace !== 'string' or body.workspace.trim() === '' and if so
return createErrorResponse(ErrorCodes.WORKSPACE_MISSING, 400, { requestId,
workspace: body.workspace }); keep the rest of the flow (isHandleBodyError,
verifyWorkspaceAccess) unchanged so genuine auth failures still return 401.

In `@apps/web/features/worktrees/lib/worktrees.ts`:
- Around line 365-371: The while loop that checks gitRefExists using a suffix
from formatTimestampUTC() can spin if the timestamp granularity doesn't change;
update the branch-name collision logic in the block that references
gitRefExists, baseWorkspacePath and branchName so the suffix always makes
progress — e.g., append a high-resolution value (Date.now()) or a monotonic
counter to the suffix (increment a local counter each iteration) instead of
relying solely on formatTimestampUTC(); ensure the final branchName is set to
`${branchName}${suffix}` after the loop exits.
- Around line 109-183: The code performs fs operations on baseWorkspacePath,
worktreeRoot and lockPath without validating they are inside the allowed
workspace; add calls to isPathWithinWorkspace() to guard against path traversal
by validating paths before any fs.* use. Specifically: in assertBaseRepo(),
ensureFromRef(), ensureWorktreeRoot(), and withWorktreeLock() call
isPathWithinWorkspace(baseWorkspacePath) at the start and throw a WorktreeError
(e.g., "WORKTREE_INVALID_PATH") if it fails; in ensureWorktreeRoot() also
validate the computed worktreeRoot before mkdirSync/chownSync/statSync; in
withWorktreeLock() validate the lockPath before openSync/unlinkSync and the
baseWorkspacePath before statSync/fchownSync/writeSync to ensure every
filesystem call is preceded by the path guard.
- Around line 351-388: The pre-lock existence check using listWorktrees(...)
must be moved inside the withWorktreeLock(...) callback to avoid a TOCTOU race:
remove the top-level existing = await listWorktrees(...) block and instead, at
the start of the async function passed to withWorktreeLock(baseWorkspacePath,
async () => { ... }), call const existing = await
listWorktrees(baseWorkspacePath) and if (existing.some(item => item.slug ===
normalizedSlug)) throw new WorktreeError("WORKTREE_EXISTS", `Worktree already
exists: ${normalizedSlug}`); keep the rest of the logic (baseRef, branchName
handling, gitRefExists, runGit, return object) unchanged so the existence check
is performed while the worktree lock is held.
- Around line 411-423: The call to remove the worktree uses
runGit(baseWorkspacePath, ["worktree", "remove", targetPath]) but does not pass
--force when allowDirty is true, so allowDirty has no effect; update the
worktree removal call in worktrees.ts (the runGit invocation that uses
baseWorkspacePath and targetPath) to include the "--force" flag when allowDirty
is true (e.g., choose ["worktree","remove","--force",targetPath] vs
["worktree","remove",targetPath]) so dirty worktrees are removed as intended;
keep the existing behavior when allowDirty is false.

In `@docs/architecture/workspace-isolation.md`:
- Around line 25-27: The `.env` entry is misindented and currently appears
outside the example.com tree; move the `.env` line so it is a sibling of
`worktrees/` and `user/` under `example.com/` (i.e., align its
tree-prefix/indentation to match `worktrees/` and `user/` in the block
containing `example.com/`, `worktrees/`, and `feature-x/`) so the visual tree
shows `.env` as part of the example.com directory.

In `@docs/features/worktrees.md`:
- Around line 12-17: The fenced code block that displays the path layout is
missing a language specifier; update the block delimiter from ``` to ```text so
the snippet becomes a language-specified code block (i.e., change the opening
fence for the path layout block to ```text) to satisfy markdown linting and
enable proper syntax highlighting for the path listing.

In `@docs/prs/worktrees/stage-2-server-api.md`:
- Around line 1-75: The markdown titled "Stage 2 - Server API" is placed outside
the mandated docs tree; relocate this document into the project's docs hierarchy
under an appropriate category (e.g., features or archive) so it follows the docs
organization, and update any docs index/sidebar references; ensure the file
keeps the same content and headers (e.g., "Stage 2 - Server API", "POST
/api/worktrees", "GET /api/worktrees", "DELETE /api/worktrees") so links and
references remain valid.

In `@docs/prs/worktrees/stage-3-workspace-resolution.md`:
- Around line 13-17: The documentation incorrectly references
ensurePathWithinWorkspace; update the text to reference and require
isPathWithinWorkspace() instead, and explicitly state that callers must call
isPathWithinWorkspace() (the path-validation helper) before any file operation
and use realpath and git worktree list --porcelain as described; replace
“ensurePathWithinWorkspace” with “isPathWithinWorkspace()” and add the
coding-guideline note to use isPathWithinWorkspace() in all .ts files to prevent
path traversal.
- Around line 1-27: The markdown file "stage-3-workspace-resolution.md" titled
"Stage 3 - Workspace Resolution and Tooling" is in the wrong docs location; move
this document into an allowed docs area (for example under the features or
archive sections) so it follows the required docs organization; update any
internal links or TOC entries that reference the old location and ensure the new
path lives under the docs directory hierarchy (e.g., docs/features/worktrees or
docs/archive/prs) so it complies with the docs/**/*.md structure and
README/GETTING_STARTED/architecture/etc. organization rules.

In `@docs/prs/worktrees/stage-4-client-ui.md`:
- Around line 1-41: The "Stage 4 - Client UI and Session Scoping" markdown
(stage-4-client-ui.md) is placed outside the required docs structure; move the
file into the organized docs tree under an allowed area (for example
features/worktrees or archive/prs within the docs directory), update any docs
index/README links that reference it, and ensure the file follows docs naming
conventions (top-level README/GETTING_STARTED/architecture/features structure)
so it resides under the proper docs hierarchy.
🧹 Nitpick comments (4)
docs/prs/worktrees/stage-1-foundations.md (1)

63-67: Minor inconsistency: force vs allowDirty naming.

Line 65 mentions force for allowing dirty worktree removal, but the implementation in worktrees.ts uses allowDirty as the parameter name. Consider updating the design doc to match the implementation for consistency.

docs/prs/worktrees/stage-5-testing.md (1)

3-9: Consider adding explicit path traversal test to API tests.

The Workspace Resolution Tests section covers traversal rejection, but based on learnings, API route tests should also explicitly verify path traversal protection at the HTTP layer. Consider adding a test case like "400 on traversal attempt in slug" to the API Tests section for completeness.

apps/web/features/worktrees/lib/__tests__/worktrees.test.ts (1)

45-205: Consider adding tests for WORKTREE_EXISTS and WORKTREE_NOT_FOUND error codes.

The test suite covers most error codes but appears to be missing explicit tests for:

  • WORKTREE_EXISTS: Creating a worktree with a slug that already exists
  • WORKTREE_NOT_FOUND: Resolving or removing a worktree that doesn't exist

These are documented in docs/features/worktrees.md and implemented in the service layer.

docs/features/worktrees.md (1)

8-8: Minor grammar: consider hyphenating "checked-out".

When "checked out" is used as a compound adjective before a noun ("checked-out branches"), it's typically hyphenated. This is a minor style suggestion.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (4)
apps/web/features/chat/lib/__tests__/workspace-naming-regression.test.ts (1)

23-38: ⚠️ Potential issue | 🟡 Minor

Add isPathWithinWorkspace() validation before file access.

existsSync is used on dotsPath/hyphensPath without path validation. Please guard file operations with isPathWithinWorkspace() to satisfy the workspace traversal protection requirement. As per coding guidelines: Always use path validation with isPathWithinWorkspace() before any file operation to prevent path traversal attacks.

apps/web/features/chat/components/sandbox/FileTree.tsx (1)

125-138: ⚠️ Potential issue | 🟠 Major

Missing worktree prop when rendering TreeNode.

TreeNode accepts a worktree prop (line 145), but it's not passed when rendered from TreeLevel (lines 126-137). This will cause nested tree levels to lose the worktree context, resulting in incorrect cache keys and API calls.

🐛 Proposed fix
       {files.map(item => (
         <TreeNode
           key={item.path}
           workspace={workspace}
+          worktree={worktree}
           item={item}
           depth={depth}
           isActive={activeFile === item.path}
apps/web/app/api/images/delete/route.ts (1)

9-77: ⚠️ Potential issue | 🟠 Major

Write mandatory tests for this new route covering authentication (401), happy path, and error cases.

This new route requires test coverage for:

  • Authentication: missing/invalid session returns 401
  • Happy path: successful image deletion
  • Error cases: invalid key (400), unauthorized tenant (403), missing image (404), deletion failures (500)

Since resolveWorkspace() supports optional worktree parameter (like list and upload routes), also test that the route correctly handles worktree in the request body if workspace isolation is intended for image operations.

apps/web/features/chat/hooks/useStreamReconnect.ts (1)

147-148: ⚠️ Potential issue | 🟡 Minor

pollForRemainingMessages captures worktree from closure but isn't passed as parameter.

The function is called with (tabId, workspace) but uses worktree from the outer scope. If worktree changes while polling is active, the poll will use the new value mid-poll. Consider passing worktree as a parameter for consistency with workspace:

Suggested fix
-      } else if (data.state === "streaming") {
-        // Stream is still active - mark it and set up polling
-        streamingActions.startStream(tabId)
-        pollForRemainingMessages(tabId, workspace)
+      } else if (data.state === "streaming") {
+        // Stream is still active - mark it and set up polling
+        streamingActions.startStream(tabId)
+        pollForRemainingMessages(tabId, workspace, worktree || undefined)
       }

And update the function signature:

   const pollForRemainingMessages = useCallback(
-    async (pollTabId: string, ws: string) => {
+    async (pollTabId: string, ws: string, wt: string | undefined) => {
🤖 Fix all issues with AI agents
In `@apps/web/app/api/claude/stream/cancel/route.ts`:
- Around line 133-135: The custom message passed in
apps/web/app/api/claude/stream/cancel/route.ts is being dropped by
createErrorResponse, which removes the message field before calling
getErrorMessage; modify createErrorResponse (in features/auth/lib/auth.ts) to
preserve and forward an explicit message field (or merge it into the fields
passed to getErrorMessage) so that when route.ts calls
createErrorResponse(ErrorCodes.INVALID_REQUEST, 400, { message: "Invalid
worktree slug..." }) the resulting payload includes that exact message; update
createErrorResponse and its use of getErrorMessage (and any tests relying on
data.message) so the provided message is propagated instead of being stripped.

In `@apps/web/components/workspace/WorktreeSwitcher.tsx`:
- Around line 234-254: The three text inputs in WorktreeSwitcher (the inputs
bound to slug/setSlug, branch/setBranch, and from/setFrom) lack accessible
labels; add either visible <label> elements linked via id/htmlFor or add
descriptive aria-label attributes for each input (e.g., "Slug", "Branch", "From
branch") and ensure ids match when using labels, and keep placeholders as
supplemental hints rather than the sole accessible name.

In `@apps/web/lib/error-codes.ts`:
- Around line 13-26: Add a new WORKTREE_GIT_FAILED entry alongside the other
WORKTREE_* keys (e.g., next to WORKTREE_NOT_GIT, WORKTREE_NOT_FOUND) in the
error-codes list, and add corresponding user-facing text in the error message
and help lookup tables referenced elsewhere in this file (the blocks around the
other WORKTREE messages at the ranges you noted). Specifically, insert
WORKTREE_GIT_FAILED into the error code enum/object and add a clear
ERROR_MESSAGES["WORKTREE_GIT_FAILED"] and ERROR_HELP["WORKTREE_GIT_FAILED"] (or
whatever message/help maps exist at the other two blocks) so API/UI surfaces a
dedicated message and help text instead of falling back to the default.
🧹 Nitpick comments (13)
apps/web/app/api/images/upload/route.ts (1)

29-36: Consider logging the worktree parameter for debugging consistency.

The worktreeParam is extracted but not included in the log statement on Line 36, which could make debugging worktree-related issues harder.

Also, the nested ternary for body construction is somewhat hard to follow. Consider a more explicit approach:

♻️ Proposed refactor for readability
     const workspaceParam = formData.get("workspace") as string | null
     const worktreeParam = formData.get("worktree") as string | null
-    const body =
-      workspaceParam && worktreeParam
-        ? { workspace: workspaceParam, worktree: worktreeParam }
-        : workspaceParam
-          ? { workspace: workspaceParam }
-          : {}
-    console.log(`[Image Upload ${requestId}] Workspace param: ${workspaceParam || "(none)"}`)
+    const body: { workspace?: string; worktree?: string } = {}
+    if (workspaceParam) {
+      body.workspace = workspaceParam
+      if (worktreeParam) {
+        body.worktree = worktreeParam
+      }
+    }
+    console.log(`[Image Upload ${requestId}] Workspace param: ${workspaceParam || "(none)"}, worktree: ${worktreeParam || "(none)"}`)
apps/web/features/auth/lib/sessionStore.ts (1)

55-61: Consider validating the worktree segment format.

When parsing a 5-segment key, if the third segment doesn't start with wt/, the code returns it as-is without validation or warning. This could silently accept malformed keys.

🛡️ Proposed defensive validation
   return {
     userId,
     workspaceDomain: workspaceDomain || "default",
-    worktree: maybeWorktreeOrTabGroup?.startsWith("wt/") ? maybeWorktreeOrTabGroup.slice(3) : maybeWorktreeOrTabGroup,
+    worktree: maybeWorktreeOrTabGroup?.startsWith("wt/")
+      ? maybeWorktreeOrTabGroup.slice(3)
+      : (() => {
+          console.warn(`[SessionStore] Expected wt/<slug> segment, got: "${maybeWorktreeOrTabGroup}"`)
+          return maybeWorktreeOrTabGroup
+        })(),
     tabGroupId: maybeTabGroupOrTabId,
     tabId: maybeTabId ?? "",
   }
apps/web/features/chat/components/sandbox/hooks/useFileContent.ts (1)

95-109: Cache invalidation may not clear all worktree entries for a workspace.

When calling invalidateFileContentCache(workspace) without specifying worktree, the function only clears entries where scope === "base" (line 101-103). This means worktree-specific cache entries (wt/{slug}) remain cached.

If the intent is to invalidate all cache entries for a workspace regardless of worktree, the logic should iterate over all keys starting with ${workspace}:: instead of including the scope in the prefix match.

Is this the intended behavior? If you want to clear all entries for a workspace:

♻️ Suggested fix to invalidate all scopes for a workspace
   if (workspace) {
-    const scope = worktree ? `wt/${worktree}` : "base"
+    // If worktree is explicitly provided, only clear that scope
+    // If worktree is undefined, clear all entries for the workspace
+    const prefix = worktree !== undefined 
+      ? `${workspace}::${worktree ? `wt/${worktree}` : "base"}::`
+      : `${workspace}::`
     for (const key of fileContentCache.keys()) {
-      if (key.startsWith(`${workspace}::${scope}::`)) {
+      if (key.startsWith(prefix)) {
         fileContentCache.delete(key)
       }
     }
     return
   }
apps/web/features/chat/components/sandbox/FileTree.tsx (1)

231-245: Same cache invalidation scope behavior as useFileContent.ts.

The invalidation logic here mirrors useFileContent.ts - when only workspace is provided, it only clears entries for the specific scope (base or worktree). Consider whether both caches should invalidate all scopes when worktree is undefined, or document this as intentional behavior.

apps/web/app/api/images/list/route.ts (1)

33-38: Consider simplifying the conditional body construction.

The nested ternary is functionally correct but could be more readable.

♻️ Optional: Simplified body construction
-    const body =
-      workspaceParam && worktreeParam
-        ? { workspace: workspaceParam, worktree: worktreeParam }
-        : workspaceParam
-          ? { workspace: workspaceParam }
-          : {}
+    const body: { workspace?: string; worktree?: string } = {}
+    if (workspaceParam) body.workspace = workspaceParam
+    if (worktreeParam) body.worktree = worktreeParam
apps/web/features/workspace/lib/__tests__/worktree-utils.test.ts (1)

1-59: Good test coverage for worktree utilities.

The tests cover the essential paths including normalization, validation of valid/invalid slugs, reserved word rejection, and workspace key building/parsing. The test for foo::bar rejection (Line 34) is particularly important for preventing session key corruption.

Consider adding edge case tests for completeness:

🧪 Optional: Additional edge case tests
   it("rejects invalid or reserved worktree slugs", () => {
     // ... existing tests ...
+
+    // Rejects slugs exceeding max length (49 chars)
+    const tooLong = validateWorktreeSlug("a".repeat(50))
+    expect(tooLong.valid).toBe(false)
+
+    // Accepts max length slug
+    const maxLength = validateWorktreeSlug("a".repeat(49))
+    expect(maxLength.valid).toBe(true)
   })
+
+  it("handles null workspace in buildWorkspaceKey", () => {
+    expect(buildWorkspaceKey(null, "feature")).toBe(null)
+    expect(buildWorkspaceKey(null, null)).toBe(null)
+  })
apps/web/lib/stores/workspaceStore.ts (1)

296-297: Consider selector stability for useCurrentWorktree.

The selector creates a closure over workspace which is fine, but note that this hook will cause re-renders whenever any property in currentWorktreeByWorkspace changes, not just the specific workspace entry. For high-frequency updates, consider using useShallow or a more targeted selector.

This is acceptable for the current use case but worth noting for future optimization if needed.

apps/web/components/workspace/WorktreeSwitcher.tsx (2)

16-21: Consider importing the type from the worktrees module.

This local WorktreeListItem interface duplicates the server-side type (minus the path field). If the server shape changes, this could drift. Consider importing and using Pick<WorktreeListItem, ...> from the shared module or defining a shared DTO type.


68-72: Replace confirm() with an accessible modal confirmation.

The native confirm() dialog blocks the main thread and isn't accessible or styleable. Consider using a custom confirmation modal component that follows the same patterns as the existing Modal component.

apps/web/features/workspace/lib/worktree-utils.ts (1)

33-38: parseWorkspaceKey may misbehave if workspace contains the delimiter.

Using split("::wt/") without a limit could produce unexpected results if the workspace string itself contains ::wt/. While unlikely in practice, consider using a more robust approach:

Suggested fix
 export function parseWorkspaceKey(key: string): { workspace: string; worktree: string | null } {
-  const [workspace, worktreePart] = key.split("::wt/")
-  if (!worktreePart) {
-    return { workspace: key, worktree: null }
-  }
-  return { workspace, worktree: worktreePart || null }
+  const delimiterIndex = key.indexOf("::wt/")
+  if (delimiterIndex === -1) {
+    return { workspace: key, worktree: null }
+  }
+  return {
+    workspace: key.slice(0, delimiterIndex),
+    worktree: key.slice(delimiterIndex + 5) || null,
+  }
 }
apps/web/lib/api/schemas.ts (1)

490-498: Consider using OptionalWorktreeSchema for the slug field.

The worktrees/create request schema uses z.string().optional() for slug, but the OptionalWorktreeSchema (already imported) provides validation and normalization. This would ensure client-side validation matches server-side expectations:

Suggested fix
   "worktrees/create": {
     req: z
       .object({
         workspace: z.string(),
-        slug: z.string().optional(),
+        slug: OptionalWorktreeSchema,
         branch: z.string().optional(),
         from: z.string().optional(),
       })
       .brand<"WorktreesCreateRequest">(),
apps/web/features/worktrees/lib/worktrees.ts (1)

6-8: Rename SLUG_REGEX / RESERVED_SLUGS to camelCase.

Please switch to slugRegex / reservedSlugs (and update references) to align with the naming rule. As per coding guidelines, Use camelCase for variable and function names.

apps/web/app/chat/page.tsx (1)

805-807: Refactor ChatInput usage to the compound pattern.

Guidelines require the compound pattern (<ChatInput><ChatInput.InputArea ... /></ChatInput>), but both desktop and mobile usages still use the single‑component form. As per coding guidelines, Always use compound pattern for ChatInput: <ChatInput><ChatInput.InputArea />...</ChatInput>.

Also applies to: 861-863

eenlars and others added 2 commits February 4, 2026 22:55
Split worktree Zod schemas into separate file to prevent bcrypt/fs
from leaking into client bundles via import chain.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web/components/workspace/WorktreeSwitcher.tsx`:
- Around line 125-137: The new worktree object in the setWorktrees update is
assigning pathRelative from response.worktreePath which may be an absolute path;
change this to avoid exposing absolute paths by assigning a safe value (e.g.,
use response.slug or a server-provided relative path field) instead of
response.worktreePath when building the object in the setWorktrees callback
(where slug, pathRelative, branch, head are created after
postty("worktrees/create", ...)). Ensure the UI-rendered pathRelative uses the
sanitized/relative value the server provides or the slug fallback.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/web/tsconfig.json (1)

35-45: ⚠️ Potential issue | 🟡 Minor

Remove the redundant .next-test/dev/dev/types/**/*.ts include path.

Line 44 duplicates the "dev" segment and appears to be a typo. The correct pattern follows the structure of .next.next/dev and .next-test.next-test/dev. Remove this line, as it is already covered by line 40 (.next-test/dev/types/**/*.ts) and no references to this doubled path exist in the codebase.

🤖 Fix all issues with AI agents
In `@apps/web/types/guards/worktree-schemas.ts`:
- Around line 29-37: OptionalWorktreeSchema currently allows empty strings and
only accepts undefined (not null), contradicting validateWorktreeSlug; change
the schema to accept nullish values and reject empty strings by adding
nullable() (or equivalent null support) and updating the transform and
refinements so normalization only runs for non-empty strings (e.g., treat "" the
same as a validation failure) and the refine predicates use val == null to allow
null/undefined but explicitly reject "" before testing WORKTREE_SLUG_REGEX;
reference OptionalWorktreeSchema, normalizeWorktreeSlug, WORKTREE_SLUG_REGEX and
align behavior with validateWorktreeSlug.
🧹 Nitpick comments (1)
apps/web/lib/api/schemas.ts (1)

490-498: Reuse OptionalWorktreeSchema for slug validation.

slug: z.string().optional() lets invalid/empty values pass validateRequest. Prefer the shared worktree schema so client-side validation matches server expectations.

Proposed change
   "worktrees/create": {
     req: z
       .object({
         workspace: z.string(),
-        slug: z.string().optional(),
+        slug: OptionalWorktreeSchema,
         branch: z.string().optional(),
         from: z.string().optional(),
       })
       .brand<"WorktreesCreateRequest">(),

Includes:
- PR #11: Automation scheduler fixes
- bridge -> stream renaming
- Cron scheduler components
- Various test and doc updates

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/web/app/chat/page.tsx (1)

806-828: ⚠️ Potential issue | 🟠 Major

Switch ChatInput usages to the compound pattern.

Both desktop and mobile inputs still use <ChatInput ... />. Please refactor to the compound API (e.g., <ChatInput><ChatInput.InputArea />...</ChatInput>), which is required for extension via composition. As per coding guidelines: "Always use compound pattern for ChatInput: <ChatInput><ChatInput.InputArea />...</ChatInput>."

Also applies to: 862-881

🤖 Fix all issues with AI agents
In `@apps/web/components/workspace/WorktreeSwitcher.tsx`:
- Around line 45-56: The setter setModalOpen currently short‑circuits whenever
onOpenChange exists, preventing internalOpen from updating when isOpen is not
provided; change the logic in setModalOpen so it only avoids updating
internalOpen when the component is in controlled mode (isControlled), i.e. call
onOpenChange if present but only skip setInternalOpen when isControlled is true;
refer to isControlled, modalOpen, setModalOpen, internalOpen, onOpenChange and
isOpen to locate and update the handler accordingly.
🧹 Nitpick comments (1)
apps/web/features/chat/lib/__tests__/workspace-naming-regression.test.ts (1)

23-38: Consider a small test helper to de-duplicate the “missing site” skip logic.

The same skip/warn pattern appears in multiple tests; a helper keeps maintenance simpler and avoids drift.

♻️ Suggested refactor
+function skipIfMissing(path: string, extraMessage?: string) {
+  if (existsSync(path)) return false
+  console.warn(`⚠️  Test site missing: ${path}`)
+  if (extraMessage) console.warn(extraMessage)
+  return true
+}
+
   it("CRITICAL: finds evermore.alive.best with DOTS in directory name", async () => {
     // Verify the actual directory exists with DOTS
     const dotsPath = "/srv/webalive/sites/evermore.alive.best/user"
     const hyphensPath = "/srv/webalive/sites/evermore-alive-best/user"
 
-    if (!existsSync(dotsPath)) {
-      console.warn(`⚠️  Test site missing: ${dotsPath}`)
-      console.warn("This test requires evermore.alive.best to exist")
-      return // Skip if test site doesn't exist
-    }
+    if (skipIfMissing(dotsPath, "This test requires evermore.alive.best to exist")) return
 
     const result = await getWorkspace({
       host: DOMAINS.STREAM_DEV_HOST,
       body: { workspace: "evermore.alive.best" },
       requestId: "regression-test",
     })

As per coding guidelines, Use test helper functions and fixtures to reduce duplication and improve test maintainability.

Copy link

@coderabbitai coderabbitai bot left a comment

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 (8)
scripts/maintenance/setup-auto-cleanup.sh (1)

30-32: ⚠️ Potential issue | 🟡 Minor

Duplicate OnCalendar directives will trigger the timer twice daily.

The comment says "Run daily at 3 AM", but there are two OnCalendar entries:

  • OnCalendar=daily triggers at midnight (00:00:00)
  • OnCalendar=*-*-* 03:00:00 triggers at 3 AM

Systemd fires the timer at both times. Remove line 31 to run only at 3 AM as intended.

🐛 Proposed fix
 [Timer]
 # Run daily at 3 AM
-OnCalendar=daily
 OnCalendar=*-*-* 03:00:00
 Persistent=true
packages/site-controller/scripts/99-teardown.sh (1)

159-159: ⚠️ Potential issue | 🟡 Minor

Inconsistency confirmed: SITES_ROOT uses outdated path scheme inconsistent with refactoring efforts.

Within the same script, three different path schemes coexist: /root/alive (BRIDGE_ROOT, line 39), /var/lib/alive (SERVER_CONFIG, line 40), and /srv/webalive/sites (SITES_ROOT, line 159). The /srv/webalive/sites pattern also appears in 02-setup-fs.sh and 03-build-site.sh. If this is part of a broader migration to the new path structure, SITES_ROOT should be updated to align with the newer /root/alive or /var/lib/alive conventions. If these directories serve intentionally separate purposes (sites storage vs. bridge configuration), document the architectural decision to justify the inconsistency.

packages/worker-pool/scripts/check-import-order.mjs (1)

199-204: ⚠️ Potential issue | 🔴 Critical

Add @webalive/tools to packages/worker-pool/package.json.

The import is present in worker-entry.mjs (line 33), but the dependency is missing from package.json. Since the check-import-order.mjs script now enforces @webalive/tools as a required top-level import, CI will fail without the dependency declaration.

ops/scripts/pre-deployment-check.sh (1)

89-101: ⚠️ Potential issue | 🟡 Minor

Guard disk-usage check against missing build dir.

With set -e, df "$BUILD_DIR" aborts the script if the new path doesn’t exist yet, so you won’t reach the summary or the explicit “build directory not found” check later. Consider guarding the disk-usage check with a directory existence test.

🔧 Proposed fix
 echo "[4/7] Checking disk space..."
 BUILD_DIR="/root/alive/.builds/$ENVIRONMENT"
-DISK_USAGE=$(df "$BUILD_DIR" | awk 'NR==2 {print $5}' | sed 's/%//')
-
-if [ "$DISK_USAGE" -gt 80 ]; then
-  echo "  ❌ CRITICAL: Disk usage is ${DISK_USAGE}% (> 80%)"
-  echo "  Free up space before deployment"
-  ISSUES=$((ISSUES + 1))
-elif [ "$DISK_USAGE" -gt 70 ]; then
-  echo "  ⚠️  WARNING: Disk usage is ${DISK_USAGE}% (> 70%)"
-else
-  echo "  ✅ Disk usage OK (${DISK_USAGE}%)"
-fi
+if [ -d "$BUILD_DIR" ]; then
+  DISK_USAGE=$(df "$BUILD_DIR" | awk 'NR==2 {print $5}' | sed 's/%//')
+  if [ "$DISK_USAGE" -gt 80 ]; then
+    echo "  ❌ CRITICAL: Disk usage is ${DISK_USAGE}% (> 80%)"
+    echo "  Free up space before deployment"
+    ISSUES=$((ISSUES + 1))
+  elif [ "$DISK_USAGE" -gt 70 ]; then
+    echo "  ⚠️  WARNING: Disk usage is ${DISK_USAGE}% (> 70%)"
+  else
+    echo "  ✅ Disk usage OK (${DISK_USAGE}%)"
+  fi
+else
+  echo "  ❌ Build directory not found for disk check: $BUILD_DIR"
+  ISSUES=$((ISSUES + 1))
+fi
scripts/maintenance/update-cors-domains.sh (1)

4-15: ⚠️ Potential issue | 🟠 Major

Fix SITES_DIR path to current deployment location.

SITES_DIR points to /root/webalive/sites, but other scripts in the codebase (migrate-vite-preview-nav.ts, update-alive-tagger.sh) use /srv/webalive/sites. The script will fail silently if the sites directory doesn't exist at the hardcoded path. Update to /srv/webalive/sites to match the current deployment structure, or follow the pattern in scripts/sites/add-verification-files.sh which allows environment variable override with a sensible default:

-SITES_DIR="/root/webalive/sites"
+SITES_DIR="${SITES_DIR:-/srv/webalive/sites}"

Also update the header comment to reflect the actual directory scanned.

scripts/validation/detect-workspace-issues.sh (3)

43-49: ⚠️ Potential issue | 🟡 Minor

Success message prints even when deprecated packages are found.

Line 49 unconditionally prints "✓ No deprecated packages found" after the loop, even if deprecated packages were detected and ISSUES_FOUND was incremented. This creates misleading output.

🐛 Proposed fix
 DEPRECATED_PACKAGES=("deploy-scripts")
+DEPRECATED_FOUND=0
 for pkg in "${DEPRECATED_PACKAGES[@]}"; do
     if [ -d "packages/$pkg" ]; then
         echo "  ❌ Deprecated package exists: packages/$pkg"
         ISSUES_FOUND=$((ISSUES_FOUND + 1))
+        DEPRECATED_FOUND=1
     fi
 done
-echo "✓ No deprecated packages found"
+if [ $DEPRECATED_FOUND -eq 0 ]; then
+    echo "✓ No deprecated packages found"
+fi

17-17: ⚠️ Potential issue | 🟡 Minor

Inconsistent check counters.

The check counters are inconsistent throughout the script:

  • Lines 17, 41, 53: [1/4], [2/4], [3/4]
  • Lines 72, 84: [4/5], [5/5]

All counters should reflect the total of 5 checks.

🔧 Proposed fix for lines 17, 41, and 53
-echo "[1/4] Checking for circular symlinks in packages..."
+echo "[1/5] Checking for circular symlinks in packages..."
-echo "[2/4] Checking for deprecated packages..."
+echo "[2/5] Checking for deprecated packages..."
-echo "[3/4] Checking for broken symlinks in node_modules..."
+echo "[3/5] Checking for broken symlinks in node_modules..."

85-92: ⚠️ Potential issue | 🟡 Minor

Success message prints even when empty directories are found.

Similar to check 2, line 92 unconditionally prints "✓ All packages have package.json" even if packages without package.json were detected.

🐛 Proposed fix
+EMPTY_FOUND=0
 for dir in packages/*/; do
     PKG_NAME=$(basename "$dir")
     if [ ! -f "$dir/package.json" ]; then
         echo "  ❌ No package.json in: packages/$PKG_NAME"
         ISSUES_FOUND=$((ISSUES_FOUND + 1))
+        EMPTY_FOUND=1
     fi
 done
-echo "✓ All packages have package.json"
+if [ $EMPTY_FOUND -eq 0 ]; then
+    echo "✓ All packages have package.json"
+fi
🤖 Fix all issues with AI agents
In `@scripts/sites/add-verification-files.sh`:
- Line 10: Update the script scripts/sites/add-verification-files.sh to avoid
failing on older deployments by either adding a backward-compatible lookup or
documenting a migration: option A — change how CONFIG_FILE is resolved (try
"/var/lib/alive/server-config.json" first, then fallback to
"/var/lib/claude-bridge/server-config.json" and emit a clear deprecation warning
when the legacy path is used) or option B — leave CONFIG_FILE fixed but add a
short, prominent migration note in the repository setup/README explaining that
the config must be moved to "/var/lib/alive/server-config.json" before running
add-verification-files.sh; reference the CONFIG_FILE variable in the script for
the code change or the README section for the documentation change.

In `@scripts/validation/detect-workspace-issues.sh`:
- Line 55: The for-loop over find output using "for link in $(find
node_modules/@webalive node_modules/@webalive node_modules/@alive-game -maxdepth
1 -type l 2>/dev/null); do" is fragile and contains a duplicated path; replace
the duplicate second "node_modules/@webalive" with the intended
"node_modules/@alive-brug" (or remove if not intended) and convert the loop to a
robust null-delimited pattern: call find with -print0 and iterate with "while
IFS= read -r -d '' link; do ... done" so the variable link (used in the existing
loop) safely handles filenames with spaces/special characters.
🧹 Nitpick comments (2)
packages/site-controller/scripts/05-caddy-inject.sh (1)

24-28: Add sanity checks / overrides for BRIDGE_ROOT and SERVER_CONFIG.
Fail fast with a clearer message if the default path is missing, and allow overrides for non-standard installs or tests.

♻️ Suggested tweak
-BRIDGE_ROOT="${BRIDGE_ROOT:-/root/alive}"
+BRIDGE_ROOT="${BRIDGE_ROOT:-/root/alive}"
+if [[ ! -d "$BRIDGE_ROOT" ]]; then
+    log_error "BRIDGE_ROOT not found: $BRIDGE_ROOT"
+    exit 15
+fi

-SERVER_CONFIG="/var/lib/alive/server-config.json"
+SERVER_CONFIG="${SERVER_CONFIG:-/var/lib/alive/server-config.json}"
+if [[ -f "$SERVER_CONFIG" && ! -r "$SERVER_CONFIG" ]]; then
+    log_error "SERVER_CONFIG not readable: $SERVER_CONFIG"
+    exit 15
+fi
scripts/sites/add-verification-files.sh (1)

10-10: Update CLAUDE.md if this path change affects workflow or structure.

This change alters deployment/config location conventions. If that’s part of the documented workflow, the template’s CLAUDE.md should be updated accordingly. Based on learnings: Update CLAUDE.md file whenever creating a new site from this template, making significant architectural changes, adding new frameworks/libraries/major features, or changing file structure/development workflow.

eenlars and others added 2 commits February 5, 2026 11:20
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Delete memory database file
- Remove /api/priorities route that used it
- Remove "Agent Memory" sections from CLAUDE.md and AGENTS.md
- Update rule 7 from "USE THE BRAIN" to "BE METHODICAL"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@scripts/deployment/build-atomic.sh`:
- Around line 186-189: The copy assumes the destination
"$STANDALONE_NODE_MODULES/@webalive/worker-pool/dist/" exists; update the script
around the worker-entry.mjs copy so that before cp is run you ensure the
destination directory exists (use a safe create like mkdir -p on
"$STANDALONE_NODE_MODULES/@webalive/worker-pool/dist/"), then perform the copy
from "packages/worker-pool/src/worker-entry.mjs" only if the source exists;
reference the variables/paths STANDALONE_NODE_MODULES,
"packages/worker-pool/src/worker-entry.mjs" and the destination
"@webalive/worker-pool/dist/" when making the change.
- Around line 163-166: The rm -rf that deletes the template directory should
guard against an empty STANDALONE_PACKAGES to avoid accidental deletion; update
the removal call that targets "$STANDALONE_PACKAGES/template" (the rm -rf line)
to first validate STANDALONE_PACKAGES is non-empty (e.g. check [ -n
"$STANDALONE_PACKAGES" ] or use a parameter expansion guard) before running rm
-rf and preserve the existing 2>/dev/null || true fallback and subsequent cp
logic.
- Around line 155-161: The rm -rf call can expand to a dangerous path if
STANDALONE_PACKAGES or pkg are empty; update the loop that iterates over pkg to
use Bash parameter expansion checks (e.g., ${STANDALONE_PACKAGES:?} and
${pkg:?}) when constructing the destination path used in rm -rf and the cp
commands so the script fails fast on unset/empty variables; specifically adjust
the lines referencing STANDALONE_PACKAGES and pkg in the for-loop (the rm -rf
"$STANDALONE_PACKAGES/$pkg" and subsequent cp -rL/cp -r invocations) to use the
:? expansion to guard against empty values.

eenlars and others added 2 commits February 5, 2026 11:42
- Create lib/standalone-packages.sh as single source of truth for packages
- Add validate-standalone.sh to check:
  - All packages in config exist
  - Packages in next.config.js are in standalone config
  - Subprocess packages (worker-pool) have their dependencies (claude-agent-sdk)
- Update build-atomic.sh to use config and run validation before build

This prevents the runtime error where worker-pool couldn't find
@anthropic-ai/claude-agent-sdk because it wasn't copied to standalone.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

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)
apps/web/features/chat/lib/workspaceRetriever.ts (1)

136-152: ⚠️ Potential issue | 🟠 Major

Add isPathWithinWorkspace() guard before filesystem checks.
existsSync on Line 152 runs without an isPathWithinWorkspace() validation. Please validate the absolute candidate path before the filesystem access.

🔒 Suggested fix
     const candidateFullPath = path.join("/srv", normalized)
+    if (!isPathWithinWorkspace(candidateFullPath)) {
+      console.warn(`[Workspace ${requestId}] Skipping path outside workspace root: ${candidateFullPath}`)
+      continue
+    }
     if (existsSync(candidateFullPath)) {

As per coding guidelines, Always use path validation with isPathWithinWorkspace() before any file operation to prevent path traversal attacks.

🤖 Fix all issues with AI agents
In `@apps/web/app/api/claude/stream/route.ts`:
- Around line 276-282: The session key creation now includes worktree which
changes scoping from workspace-level to worktree-level; either remove worktree
from the tabKey call so the key is built as tabKey({ userId: user.id, workspace:
resolvedWorkspaceName, tabGroupId, tabId }) to restore the documented
userId::workspace::tabId pattern (adjust any code that relies on worktree-scoped
keys), or if the per-worktree scope is intentional, update the session
management docs and any naming/comments to reflect the new
userId::workspace::worktree::tabGroupId::tabId pattern and ensure all consumers
of sessionKey (symbol: sessionKey) and the function tabKey accept and expect the
worktree parameter consistently.

In `@scripts/deployment/validate-standalone.sh`:
- Around line 49-53: The script fails under set -euo pipefail when grep finds no
matches; modify the extraction of NEXT_PACKAGES so the grep command cannot cause
a non-zero exit (e.g., append a safe fallback like using "|| true" or an
if-check around grep) and ensure the variable NEXT_PACKAGES is set to an empty
string rather than causing the script to exit; adjust the assignment that
references NEXT_CONFIG and the subsequent for next_pkg in $NEXT_PACKAGES loop so
the loop simply skips when no packages are found.
🧹 Nitpick comments (1)
scripts/deployment/lib/standalone-packages.sh (1)

18-33: Optional: mark arrays as intentional exports and silence SC2034.

Since this file is sourced, Shellcheck flags these as unused locally. Consider making intent explicit to reduce noise.

♻️ Proposed tweak
-# Packages to copy to standalone build
-# Format: space-separated list of package names (from packages/ directory)
-STANDALONE_PACKAGES=(
+# shellcheck disable=SC2034
+# Packages to copy to standalone build
+# Format: space-separated list of package names (from packages/ directory)
+readonly -a STANDALONE_PACKAGES=(
     tools
     images
     shared
     worker-pool
     site-controller
     database
 )
 
 # Packages that have external dependencies needed in subprocess
 # These get their node_modules copied too (via cp -rL)
-SUBPROCESS_PACKAGES=(
+readonly -a SUBPROCESS_PACKAGES=(
     worker-pool
 )

eenlars and others added 3 commits February 5, 2026 12:07
When a workspace stored in localStorage doesn't exist on the current
server's filesystem, it was still shown as selected, causing
WORKSPACE_NOT_AUTHENTICATED errors when trying to use it.

Now validates stored workspace against available workspaces (from API)
when organizations load, clearing any that aren't accessible.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add documentation comment to route tests explaining HTTP layer separation
- Add POST workspace validation with tests for empty/whitespace values
- Fix branch collision loop to use counter instead of timestamp
- Add --force flag to git worktree remove when allowDirty is true
- Add WORKTREE_GIT_FAILED error code and mapping
- Fix Zod schema to use .nullish() instead of .optional()
- Add aria-labels for accessibility in WorktreeSwitcher
- Fix modal state bug when onOpenChange provided without isOpen
- Fix absolute path display in worktree list (use slug instead)
- Move PR docs to docs/archive/prs/
- Fix tree indentation in workspace-isolation.md
- Add language specifier to code blocks in worktrees.md
- Add FORBIDDEN error code to cancel route
- Guard rm -rf commands in build scripts
- Fix grep false failure in validate-standalone.sh
- Fix duplicate directory in detect-workspace-issues.sh
- Update tabKey examples in CLAUDE.md
- Add security documentation to worktrees.ts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Track nitpicks and outside-diff-range comments from PR #12:
- 5 nitpicks (minor improvements)
- 10 outside diff range issues (including 1 critical)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@eenlars eenlars merged commit c5af9f9 into main Feb 5, 2026
5 of 6 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@scripts/deployment/build-atomic.sh`:
- Around line 178-193: The copy loop can nest when destination already exists;
before copying each package (in the for loop over "${STANDALONE_PACKAGES[@]}"
template) remove the destination path under STANDALONE_NODE_MODULES/@webalive
for that package (e.g., rm -rf "$STANDALONE_NODE_MODULES/@webalive/$pkg") then
perform the cp -rL ... || cp -r ... operations as written; use the same
destination cleanup logic that Phase 8 uses and reference
STANDALONE_NODE_MODULES, STANDALONE_PACKAGES_DIR and STANDALONE_PACKAGES in the
loop to locate the code to change.
🧹 Nitpick comments (5)
apps/web/lib/hooks/useSettingsQueries.ts (1)

63-75: Prefer async/await over .then() chains.

As per coding guidelines, async/await should be used for promise handling instead of .then() chains. Consider refactoring to an async IIFE:

♻️ Proposed refactor
       authStore.setAuthenticated()
       validateAndCleanup(query.data.organizations as Organization[])
 
-      // Also validate workspace availability on this server
-      // This clears currentWorkspace if the site doesn't exist on the filesystem
-      getty("auth/all-workspaces")
-        .then(response => {
-          const allWorkspaces = Object.values(response.workspaces).flat()
-          validateWorkspaceAvailability(allWorkspaces)
-        })
-        .catch(err => {
-          console.warn("[useOrganizationsQuery] Failed to validate workspace availability:", err)
-        })
+      // Also validate workspace availability on this server
+      // This clears currentWorkspace if the site doesn't exist on the filesystem
+      ;(async () => {
+        try {
+          const response = await getty("auth/all-workspaces")
+          const allWorkspaces = Object.values(response.workspaces).flat()
+          validateWorkspaceAvailability(allWorkspaces)
+        } catch (err) {
+          console.warn("[useOrganizationsQuery] Failed to validate workspace availability:", err)
+        }
+      })()
docs/todos/tofix.md (1)

63-76: Good tracking of deferred issues - the FileTree worktree prop bug is notable.

This documentation approach provides good visibility into technical debt. The flagged issue about missing worktree prop in TreeNode (lines 65-75) appears to be a real bug that could cause nested tree levels to lose worktree context. Consider prioritizing this fix.

Would you like me to generate a fix for the missing worktree prop in apps/web/features/chat/components/sandbox/FileTree.tsx?

apps/web/app/api/claude/stream/cancel/route.ts (1)

126-137: Consider using shared validation instead of inline duplication.

The worktree validation logic here duplicates what's in OptionalWorktreeSchema and validateWorktreeSlug(). Consider using the shared schema for consistency:

import { OptionalWorktreeSchema } from "@/types/guards/worktree-schemas"

// Then in the handler:
const worktreeResult = OptionalWorktreeSchema.safeParse(body.worktree)
if (!worktreeResult.success) {
  return createErrorResponse(ErrorCodes.INVALID_REQUEST, 400, {
    message: worktreeResult.error.issues[0]?.message,
  })
}
const worktree = worktreeResult.data

This ensures validation stays in sync across all worktree-handling code paths.

apps/web/components/workspace/WorktreeSwitcher.tsx (1)

90-118: Consider replacing confirm() with a custom confirmation dialog.

The native confirm() dialog (line 92) blocks the main thread and doesn't follow the app's design system. While functional, it creates an inconsistent UX.

This is a non-blocking suggestion for future improvement - the current implementation works correctly.

apps/web/features/worktrees/lib/worktrees.ts (1)

197-202: Consider using unknown instead of any for the caught error.

The explicit any type works, but unknown with a type guard is more type-safe and aligns better with strict TypeScript practices.

♻️ Optional refactor for stricter typing
-  } catch (error: any) {
-    if (error?.code === "EEXIST") {
+  } catch (error: unknown) {
+    if (error instanceof Error && (error as NodeJS.ErrnoException).code === "EEXIST") {
       throw new WorktreeError("WORKTREE_LOCKED", "Worktree operation already in progress")
     }
     throw error

Comment on lines +178 to +193
# Phase 9: Copy Packages to node_modules (NO SYMLINKS)
# =============================================================================
log_step "Creating package symlinks..."
log_step "Copying packages to node_modules..."
STANDALONE_NODE_MODULES="$STANDALONE_DIR/node_modules"
mkdir -p "$STANDALONE_NODE_MODULES/@alive-brug" "$STANDALONE_NODE_MODULES/@webalive"

for pkg in tools images template; do
ln -sf "../../../../packages/$pkg" "$STANDALONE_NODE_MODULES/@alive-brug/$pkg"
mkdir -p "$STANDALONE_NODE_MODULES/@webalive"

# Copy workspace packages to node_modules/@webalive (actual copies, not symlinks)
# Include template which was copied separately
for pkg in "${STANDALONE_PACKAGES[@]}" template; do
if [ -d "$STANDALONE_PACKAGES_DIR/$pkg" ]; then
cp -rL "$STANDALONE_PACKAGES_DIR/$pkg" "$STANDALONE_NODE_MODULES/@webalive/$pkg" 2>/dev/null || \
cp -r "$STANDALONE_PACKAGES_DIR/$pkg" "$STANDALONE_NODE_MODULES/@webalive/$pkg"
elif [ -d "packages/$pkg" ]; then
cp -rL "packages/$pkg" "$STANDALONE_NODE_MODULES/@webalive/$pkg" 2>/dev/null || \
cp -r "packages/$pkg" "$STANDALONE_NODE_MODULES/@webalive/$pkg"
fi
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the file exists and understand the build context
cd scripts/deployment || exit 1
ls -la build-atomic.sh
wc -l build-atomic.sh

Repository: eenlars/alive

Length of output: 141


🏁 Script executed:

# Check the full context of Phase 9 and earlier phases to understand when node_modules/@webalive is created
sed -n '1,193p' scripts/deployment/build-atomic.sh | tail -80

Repository: eenlars/alive

Length of output: 3785


🏁 Script executed:

# Search for when STANDALONE_DIR and node_modules are first created
grep -n "STANDALONE_DIR\|STANDALONE_NODE_MODULES\|mkdir.*node_modules" scripts/deployment/build-atomic.sh | head -20

Repository: eenlars/alive

Length of output: 1112


🏁 Script executed:

# Check all phases to see what happens before Phase 9
grep -n "Phase" scripts/deployment/build-atomic.sh

Repository: eenlars/alive

Length of output: 399


🏁 Script executed:

# Check how TEMP_BUILD_DIR is initialized and if it could contain residual `@webalive`
head -80 scripts/deployment/build-atomic.sh | grep -A 5 "TEMP_BUILD_DIR"

Repository: eenlars/alive

Length of output: 471


🏁 Script executed:

# Check Phase 1-3 for any cleanup that might affect STANDALONE_DIR
sed -n '64,156p' scripts/deployment/build-atomic.sh

Repository: eenlars/alive

Length of output: 3683


🏁 Script executed:

# Look for any other references to `@webalive` or node_modules in the script
grep -n "@webalive\|node_modules" scripts/deployment/build-atomic.sh

Repository: eenlars/alive

Length of output: 1544


Remove destination packages before copying to avoid nesting on retries.

The cp -r command will nest directories if the destination already exists. While the fresh build output won't include @webalive packages initially, if the script reruns or retries (e.g., during debugging or partial CI failures), existing packages or symlinks could cause cp -r to nest or copy into symlink targets. Phase 8 already follows this defensive pattern with rm -rf before copying; apply the same approach here for consistency.

Suggested fix
 for pkg in "${STANDALONE_PACKAGES[@]}" template; do
+    DEST="$STANDALONE_NODE_MODULES/@webalive/$pkg"
+    rm -rf "${DEST:?}" 2>/dev/null || true
     if [ -d "$STANDALONE_PACKAGES_DIR/$pkg" ]; then
-        cp -rL "$STANDALONE_PACKAGES_DIR/$pkg" "$STANDALONE_NODE_MODULES/@webalive/$pkg" 2>/dev/null || \
-        cp -r "$STANDALONE_PACKAGES_DIR/$pkg" "$STANDALONE_NODE_MODULES/@webalive/$pkg"
-    elif [ -d "packages/$pkg" ]; then
-        cp -rL "packages/$pkg" "$STANDALONE_NODE_MODULES/@webalive/$pkg" 2>/dev/null || \
-        cp -r "packages/$pkg" "$STANDALONE_NODE_MODULES/@webalive/$pkg"
+        cp -rL "$STANDALONE_PACKAGES_DIR/$pkg" "$DEST" 2>/dev/null || \
+        cp -r "$STANDALONE_PACKAGES_DIR/$pkg" "$DEST"
+    elif [ -d "packages/$pkg" ]; then
+        cp -rL "packages/$pkg" "$DEST" 2>/dev/null || \
+        cp -r "packages/$pkg" "$DEST"
     fi
 done
🤖 Prompt for AI Agents
In `@scripts/deployment/build-atomic.sh` around lines 178 - 193, The copy loop can
nest when destination already exists; before copying each package (in the for
loop over "${STANDALONE_PACKAGES[@]}" template) remove the destination path
under STANDALONE_NODE_MODULES/@webalive for that package (e.g., rm -rf
"$STANDALONE_NODE_MODULES/@webalive/$pkg") then perform the cp -rL ... || cp -r
... operations as written; use the same destination cleanup logic that Phase 8
uses and reference STANDALONE_NODE_MODULES, STANDALONE_PACKAGES_DIR and
STANDALONE_PACKAGES in the loop to locate the code to change.

@eenlars eenlars deleted the worktrees branch February 5, 2026 12:07
eenlars added a commit that referenced this pull request Feb 5, 2026
lars-deploy-bot pushed a commit that referenced this pull request Feb 8, 2026
- Delete alive-agents.md + alive-agents-hard-problems.md → #36
- Delete agent-supervisor.md → #37
- Delete subdomain-deployment.md → #38
- Delete docs/archive/prs/worktrees/stage-{2,3,4}.md (implemented in PR #10/#12)
- Fix referral-system.md header: was "not implemented" but is fully implemented

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant