-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/standalone local mode #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Enables running Claude Bridge locally without Supabase/Redis: - Add BRIDGE_ENV=standalone mode - Auto-login with any credentials (no database) - Local workspace management (~/.claude-bridge/workspaces/) - In-memory session store (no Redis) - Supabase clients throw in standalone mode - Setup script: bun run setup:standalone Limitations: single user, no persistence, no OAuth integrations
- Add security comments for weak session value (intentional for local dev) - Add memory leak warning for in-memory session store - Document why process.env.BRIDGE_ENV is used at module scope - Document why require() is used instead of await import() - Handle null return from getRedisUrl() in standalone mode - Add null checks in stream-buffer.ts for Redis operations - Skip Redis/Supabase health checks in standalone mode - Update GETTING_STARTED.md with standalone mode documentation - Add tests for /api/workspaces/local endpoint (15 tests) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove unused @ts-expect-error in alive-tagger jsx-dev-runtime - Fix circular import in Sandbox.tsx (macOS case-insensitive ./sandbox vs ./Sandbox) - Fix redis nullable type in stream-buffer test - Remove unused imports (fetcher, useQueryClient) - Fix import type style (React, UseQueryResult) - Remove unused variables in workspace local test - Fix hardcoded bun paths in hooks/scripts for macOS compatibility Co-Authored-By: Lars <lars@eenlars.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📝 WalkthroughWalkthroughThis pull request introduces standalone mode for Claude Bridge, allowing the application to run as a self-contained environment without requiring external services like Supabase or Redis. The changes add environment-based branching throughout the codebase to detect and handle standalone mode, while maintaining backward compatibility with existing deployments. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant LoginAPI as /api/login
participant StandaloneAuth as Standalone Auth
participant SessionStore as Session Store
participant Response
Client->>LoginAPI: POST /api/login
LoginAPI->>LoginAPI: Check BRIDGE_ENV
alt Standalone Mode
LoginAPI->>StandaloneAuth: Load standalone utilities
StandaloneAuth->>StandaloneAuth: Ensure default workspace exists
StandaloneAuth->>SessionStore: Set session with TEST_USER.ID
SessionStore->>SessionStore: Store in-memory
LoginAPI->>Response: Return userId + workspaces
else Production/Local Mode
LoginAPI->>LoginAPI: Standard authentication flow
LoginAPI->>Response: Return user session
end
Response->>Client: HTTP 200 with session
sequenceDiagram
participant Client
participant WorkspaceAPI as /api/workspaces/local
participant SessionValidation as Session Check
participant WorkspaceUtils as Workspace Utilities
participant FileSystem as File System
Client->>WorkspaceAPI: GET/POST with session cookie
WorkspaceAPI->>WorkspaceAPI: Check BRIDGE_ENV standalone
WorkspaceAPI->>SessionValidation: Verify session cookie
SessionValidation->>SessionValidation: Compare against SESSION_VALUE
alt Valid Session
alt GET - List Workspaces
WorkspaceAPI->>WorkspaceUtils: getStandaloneWorkspaces()
WorkspaceUtils->>FileSystem: Read workspace directory
FileSystem->>WorkspaceUtils: Return workspace names
WorkspaceUtils->>WorkspaceAPI: Return list with basePath
else POST - Create Workspace
WorkspaceAPI->>WorkspaceAPI: Validate input (Zod schema)
WorkspaceAPI->>WorkspaceUtils: Check workspace exists
alt Not Exists
WorkspaceUtils->>FileSystem: Create workspace directory
FileSystem->>WorkspaceUtils: Success
WorkspaceUtils->>WorkspaceAPI: Return workspace info
else Already Exists
WorkspaceAPI->>WorkspaceAPI: Return WORKSPACE_EXISTS error (409)
end
end
else Invalid/Missing Session
WorkspaceAPI->>WorkspaceAPI: Return 401 Unauthorized
end
WorkspaceAPI->>Client: HTTP response with requestId
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
❌ Type Check Failed Please run |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
docs/GETTING_STARTED.md (2)
119-125:⚠️ Potential issue | 🟡 MinorAlign the local API key variable name.
The Local Mode config uses
ANTH_API_SECRET, but the later table saysANTHROPIC_API_KEY. Pick one name and use it consistently to avoid misconfiguration.✅ Proposed fix (use ANTHROPIC_API_KEY consistently)
-ANTH_API_SECRET=sk-ant-your-key-here +ANTHROPIC_API_KEY=sk-ant-your-key-hereAlso applies to: 169-174
132-136:⚠️ Potential issue | 🟡 MinorLocal dev command conflicts with documented setup.
Local Mode says to start with
bun run web, but the documented local setup usesbun run dev. Please align these steps so onboarding matches the actual workflow. Based on learnings: Local development setup: install dependencies withbun install, runbun run setup, add.env.localwith required keys, then runbun run dev.🔧 Suggested change
-```bash -bun run web -``` +```bash +bun run dev +```apps/web/lib/stream/stream-buffer.ts (1)
421-439:⚠️ Potential issue | 🔴 CriticalMissing null check for Redis in
ackStreamCursorwill cause runtime error.All other buffer operations have early returns when
redisisnull, butackStreamCursorattempts to callredis.eval()without checking. This will throw a TypeError in standalone mode when Redis is unavailable.🐛 Proposed fix
export async function ackStreamCursor( requestId: string, userId: string, lastSeenSeq: number, ): Promise<{ lastReadSeq: number } | null> { const redis = getRedis() + if (!redis) return null // Standalone mode + const key = `${BUFFER_KEY_PREFIX}${requestId}` const result = await redis.eval(ACK_SCRIPT, 1, key, userId, lastSeenSeq.toString())apps/web/app/api/health/route.ts (1)
99-101:⚠️ Potential issue | 🟡 MinorReset function doesn't reset the initialization flag.
_resetHealthCheckRedis()setshealthCheckRedisto null but doesn't resethealthCheckRedisInitialized. After calling this function in tests, subsequent calls togetHealthCheckRedis()will return null without attempting to create a new client because the flag remains true.🐛 Proposed fix
// For testing: reset singleton export function _resetHealthCheckRedis() { healthCheckRedis = null + healthCheckRedisInitialized = false }
🤖 Fix all issues with AI agents
In `@apps/web/e2e-tests/site-limits.spec.ts`:
- Around line 133-190: The test "deployment succeeds when quota is increased"
currently skips whenever setQuotaResponse.ok() or increaseResponse.ok() are
false, which hides server errors; change the logic in the handlers for
setQuotaResponse and increaseResponse so you only call test.skip() when
response.status() === 404, and otherwise assert the call succeeded (e.g.,
expect(setQuotaResponse.ok()).toBe(true) /
expect(increaseResponse.ok()).toBe(true)) to fail the test on 500/401 errors;
update the checks around setQuotaResponse and increaseResponse in this test
function to reflect this behavior.
- Around line 192-231: The quota setup may silently fail and leave stale state;
after calling authenticatedPage.request.post("/api/test/set-quota") and handling
the 404 skip branch, add an explicit assertion that setQuotaResponse is
successful (e.g. expect(setQuotaResponse.ok()).toBe(true) or check status ===
200) before proceeding to deploy; reference setQuotaResponse and the
authenticatedPage.request.post("/api/test/set-quota") call in the test "error
message includes currentCount from user's actual site count" so the test fails
fast when quota setup didn't actually apply.
- Line 27: The test suite is permanently disabled by test.describe.skip; change
the suite header so it only skips when appropriate—either replace
test.describe.skip("Site Limits", ...) with test.describe("Site Limits", ...) to
enable the suite (the individual tests already handle 404 skips) or wrap the
suite in a conditional that checks an explicit env flag (e.g.,
process.env.RUN_SITE_LIMITS or a helper like shouldRunSiteLimits()) before
calling test.describe, ensuring the suite runs only when intended.
- Around line 89-131: The test currently skips only on a 404 but does not assert
that the /api/test/set-quota call actually succeeded, allowing false positives;
after the existing 404 skip guard add an explicit assertion that
setQuotaResponse.ok() is true (e.g. expect(setQuotaResponse.ok()).toBeTruthy())
so the test fails when the quota setup returns any non‑success response;
reference the setQuotaResponse variable and the /api/test/set-quota POST call
and keep the existing test.skip behavior for 404s.
In `@apps/web/e2e-tests/utilities-validation.spec.ts`:
- Around line 45-47: Replace presence checks that use toBeVisible with
toBeAttached for DOM-only assertions: change await
expect(authenticatedPage.locator(TEST_SELECTORS.workspaceReady)).toBeVisible({
timeout: 2000 }) and the similar lines for TEST_SELECTORS.messageInput and
TEST_SELECTORS.sendButton to use toBeAttached with the same timeouts; ensure the
longest timeout (workspaceReady) is awaited first, followed by the shorter
presence checks (messageInput, sendButton). Also apply the same change for the
analogous assertions referenced at lines 82-83 (use toBeAttached there as well).
In `@apps/web/features/chat/lib/workspaceRetriever.ts`:
- Around line 115-149: The suggestion message is hardcoded to
"~/.claude-bridge/workspaces/" which can be wrong when WORKSPACE_BASE is
overridden; update the error details returned in the standalone block (where
standaloneWorkspaceExists and getStandaloneWorkspacePath are used) to compute
the actual base path (use getStandaloneWorkspacePath or
process.env.WORKSPACE_BASE) and include that dynamic path in the suggestion
instead of the hardcoded string so NextResponse.json
(ErrorCodes.WORKSPACE_NOT_FOUND) returns the real create-directory hint for the
current configuration.
In `@apps/web/features/workspace/lib/standalone-workspace.ts`:
- Around line 39-42: getStandaloneWorkspacePath currently builds a path from an
unvalidated name which can allow path traversal; update
getStandaloneWorkspacePath to validate the resolved path by calling
getStandaloneWorkspaceBase() then joining with name and "user", then use
isPathWithinWorkspace(resolvedPath, base) (or a similar helper) and throw a
"Path traversal attack detected" error if validation fails; this ensures any
callers (e.g., workspaceRetriever.ts) can't pass malicious names and mirrors the
checks performed in createStandaloneWorkspace.
In `@docs/GETTING_STARTED.md`:
- Around line 156-175: Remove the leftover git merge conflict markers (<<<<<<<,
=======, >>>>>>>) and retain the intended content by keeping both the
"Standalone Mode" and "Local Mode" sections with their tables; ensure the
duplicated separators are gone and the tables are formatted correctly so the
document renders cleanly (look for the conflict block surrounding the
"Standalone Mode" and "Local Mode" headings and delete the marker lines only).
In `@docs/proposals/conversation-tree-architecture.md`:
- Line 401: The recommendation line contains a project name typo: replace the
incorrect "alive-brug" string in the paragraph starting "Recommendation: Adopt
this as the core conversation model for alive-brug." with the correct
project/repo name used in this PR (e.g., the canonical repo name), ensuring the
updated sentence reads "Recommendation: Adopt this as the core conversation
model for <correct-project-name>." Search for the exact "alive-brug" token in
conversation-tree-architecture.md and update it wherever it appears to the
correct project name to avoid confusion.
- Line 4: The document currently embeds a local absolute path
"/Users/here/CODE_FOLDER/mini-projects/zukofrontend/src/lib/conversation/tree.ts"
which leaks developer environment details; replace that string in
docs/proposals/conversation-tree-architecture.md with a repo-relative path like
"src/lib/conversation/tree.ts" or a generic reference such as
"src/lib/conversation/tree.ts (repository path)" so the doc remains portable and
non-identifying.
🧹 Nitpick comments (5)
docs/GETTING_STARTED.md (1)
29-175: Update CLAUDE.md to reflect the new mode-based workflow.This PR introduces a new standalone/local onboarding flow and environment variables; the template guideline requires updating
CLAUDE.mdfor significant workflow/architecture changes. 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.scripts/setup-standalone.sh (1)
10-10: Unused variableREDcan be removed.The
REDcolor variable is defined but never used in the script. Consider removing it or using it for error messages.🧹 Remove unused variable
# Colors for output -RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Colorapps/web/app/api/login/route.ts (1)
44-62: Inconsistent environment variable access pattern.Line 45 uses
process.env.BRIDGE_ENVdirectly while line 65 usesenv.STREAM_ENVfrom the imported@webalive/env/server. For consistency and to benefit from any validation/typing provided by the env module, consider using the env object consistently.Additionally,
ensureDefaultWorkspace()is called without error handling. If workspace creation fails, the login will proceed with potentially no workspaces available.♻️ Suggested improvements
- // Standalone mode - auto-login with any credentials (for local development) - if (process.env.BRIDGE_ENV === "standalone") { + // Standalone mode - auto-login with any credentials (for local development) + if (env.BRIDGE_ENV === "standalone") { // Import workspace utilities const { getStandaloneWorkspaces, ensureDefaultWorkspace } = await import( "@/features/workspace/lib/standalone-workspace" ) // Ensure at least one workspace exists - ensureDefaultWorkspace() + try { + ensureDefaultWorkspace() + } catch (error) { + console.error("[Login] Failed to ensure default workspace:", error) + }apps/web/app/api/health/route.ts (1)
111-122: Inconsistent standalone mode detection approaches.The Redis check relies on
redisbeing null (implicit detection viagetRedisUrl()returning null), while the database check explicitly checksprocess.env.BRIDGE_ENV === "standalone". This inconsistency could lead to subtle bugs if these conditions diverge.Consider using explicit
BRIDGE_ENVchecks in both places for clarity, or document why the approaches differ.Also applies to: 158-168
apps/web/app/api/workspaces/local/route.ts (1)
46-60: Duplicated session validation logic.The session cookie validation code is identical in both GET and POST handlers. Consider extracting this to a shared helper function to reduce duplication and ensure consistent behavior.
♻️ Proposed refactor
// Add helper at top of file after imports async function validateStandaloneSession(requestId: string): Promise<NextResponse | null> { const jar = await cookies() const sessionCookie = jar.get(COOKIE_NAMES.SESSION) if (!sessionCookie?.value || sessionCookie.value !== STANDALONE.SESSION_VALUE) { return NextResponse.json( { ok: false, error: ErrorCodes.NO_SESSION, message: getErrorMessage(ErrorCodes.NO_SESSION), requestId, }, { status: 401 }, ) } return null // Valid session } // Then in handlers: const authError = await validateStandaloneSession(requestId) if (authError) return authErrorAlso applies to: 98-112
|
❌ Type Check Failed Please run |
- Add path traversal validation to getStandaloneWorkspacePath - Use dynamic workspace base path in error suggestions - Remove merge conflict markers from GETTING_STARTED.md - Fix stream-buffer test Redis null type - Fix local path leak in conversation-tree-architecture.md - Format e2e test files
Summary by CodeRabbit
Release Notes
New Features
Documentation
Tests