Skip to content

fix(cli): honor local context for memory and token auth#1011

Merged
buremba merged 1 commit into
mainfrom
fix-local-context-auth
May 21, 2026
Merged

fix(cli): honor local context for memory and token auth#1011
buremba merged 1 commit into
mainfrom
fix-local-context-auth

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented May 21, 2026

Summary

  • derive the memory MCP URL from loopback contexts instead of defaulting to https://lobu.ai/mcp
  • call /api/local-init against the context origin
  • store the local-init Better Auth session token for admin REST + MCP, while preserving the local worker PAT for lobu chat / gateway agent API
  • heal stale local credentials that only stored the worker PAT by re-running local-init
  • add regression coverage for local memory URL and token selection

Fixes #1008

Tests

  • bun test packages/cli/src/internal/__tests__/context.test.ts packages/cli/src/internal/__tests__/credentials.test.ts packages/cli/src/commands/memory/_lib/openclaw-auth.test.ts
  • bunx biome check --config-path config/biome.config.json packages/cli/src/internal/credentials.ts packages/cli/src/internal/__tests__/credentials.test.ts
  • cd packages/cli && bunx tsc -p tsconfig.json --noEmit
  • pre-commit: tsc --noEmit

Summary by CodeRabbit

  • Tests

    • Added coverage for local initialization token selection and per-context memory URL derivation.
  • Changes

    • CLI now prefers session tokens during local initialization and persists a local worker API token when provided.
    • Memory URL handling improved so loopback contexts derive the correct per-context memory endpoint.
  • Exports

    • CLI exposes a dedicated agent API token retrieval used by chat and related commands.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

Local bootstrap now prefers session_token from /api/local-init for the CLI accessToken and stores device_token as localWorkerToken; a new getAgentApiToken exposes worker tokens for agent APIs. Memory URL resolution derives /mcp endpoints for loopback contexts. Chat/dev commands and tests updated to use the new helpers.

Changes

Local Context Authentication and Token Flow

Layer / File(s) Summary
Credentials model and origin/loopback helpers
packages/cli/src/internal/credentials.ts
Adds localWorkerToken?: string, originFromContextUrl(), and isLoopbackContext() helpers used to normalize context origins and detect loopback contexts.
Local-init token handling, agent accessor, and CLI wiring
packages/cli/src/internal/credentials.ts, packages/cli/src/internal/index.ts, packages/cli/src/commands/chat.ts, packages/cli/src/commands/dev.ts, packages/cli/src/internal/__tests__/credentials.test.ts
tryLocalInit() POSTs to ${originFromContextUrl(context.url)}/api/local-init, prefers session_token for the CLI accessToken, and persists device_token as localWorkerToken when present. Adds exported getAgentApiToken() (env override or loopback worker token), refactors getToken() to use an internal helper, re-exports the agent helper, updates chat/dev to call getAgentApiToken, and adds tests validating request shape, token selection, and v2 credential persistence.

Local Loopback Memory URL Derivation

Layer / File(s) Summary
Memory URL derivation helpers for loopback contexts
packages/cli/src/internal/context.ts
Adds defaultMemoryUrlForContext() and isLoopbackContextUrl() to derive context-specific memory endpoints, preferring context.memoryUrl, deriving http://localhost:<port>/mcp for loopback context URLs, or falling back to DEFAULT_MEMORY_URL. getMemoryUrl() and findContextByMemoryUrl() now use these helpers.
Memory URL derivation tests
packages/cli/src/internal/__tests__/context.test.ts
Adds tests verifying getMemoryUrl('local') yields the loopback /mcp endpoint and that findContextByMemoryUrl() matches the localhost-derived form but not the 127.0.0.1 variant.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • lobu-ai/lobu#999: Updates CLI /api/local-init token/persistence behavior and X-Lobu-Client usage, aligning with this PR's changes.
  • lobu-ai/lobu#830: Prior work adjusting CLI local-init token selection and persistence; related code-level area.
  • lobu-ai/lobu#944: Other edits to local-dev bootstrap and announceLocalSignIn flows touching similar files.

"i hopped to localhost in the night,
session-token snug, device-token light,
/mcp doors opened on loopback land,
CLI and agent now go hand in hand,
rabbit cheers: tokens in perfect sight!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(cli): honor local context for memory and token auth' directly summarizes the main changes: fixing CLI handling of local contexts for both memory URLs and token authentication.
Description check ✅ Passed The description covers all key changes with bullet points, includes test commands demonstrating validation, and references the linked issue #1008.
Linked Issues check ✅ Passed The PR fully addresses issue #1008 by implementing context-aware memory URLs [context.ts], preferring session tokens for admin APIs [credentials.ts], storing worker PATs separately [credentials.ts], and adding comprehensive test coverage [context.test.ts, credentials.test.ts].
Out of Scope Changes check ✅ Passed All changes directly support the stated objectives: memory URL context awareness, local-init token handling, session/worker token separation, and test coverage. No extraneous modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-local-context-auth

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 21, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 84.05797% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/cli/src/commands/dev.ts 0.00% 8 Missing ⚠️
packages/cli/src/internal/context.ts 90.47% 2 Missing ⚠️
packages/cli/src/internal/credentials.ts 97.43% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
packages/cli/src/internal/__tests__/context.test.ts (1)

146-163: ⚡ Quick win

Add an IPv6 loopback regression assertion in this test case.

This scenario currently validates localhost and 127.0.0.1; adding http://[::1]:8787/api/v1 -> http://[::1]:8787/mcp coverage will lock the loopback contract across all supported local forms.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/internal/__tests__/context.test.ts` around lines 146 - 163,
Add an IPv6 loopback context and assertions mirroring the IPv4/localhost checks:
extend the test's configData to include a new context (e.g. name it "localIPv6")
with url "http://[::1]:8787/api/v1", then call getMemoryUrl("localIPv6") and
assert it returns "http://[::1]:8787/mcp", and verify
findContextByMemoryUrl("http://[::1]:8787/mcp")?.name === "localIPv6"; this uses
the existing getMemoryUrl and findContextByMemoryUrl helpers and preserves the
same pattern as the existing localhost/127.0.0.1 assertions.
🤖 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.

Nitpick comments:
In `@packages/cli/src/internal/__tests__/context.test.ts`:
- Around line 146-163: Add an IPv6 loopback context and assertions mirroring the
IPv4/localhost checks: extend the test's configData to include a new context
(e.g. name it "localIPv6") with url "http://[::1]:8787/api/v1", then call
getMemoryUrl("localIPv6") and assert it returns "http://[::1]:8787/mcp", and
verify findContextByMemoryUrl("http://[::1]:8787/mcp")?.name === "localIPv6";
this uses the existing getMemoryUrl and findContextByMemoryUrl helpers and
preserves the same pattern as the existing localhost/127.0.0.1 assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 64d546e8-0686-44dc-95fb-c974c3df2285

📥 Commits

Reviewing files that changed from the base of the PR and between 47f7e4f and 4f7010d.

📒 Files selected for processing (5)
  • packages/cli/src/commands/dev.ts
  • packages/cli/src/internal/__tests__/context.test.ts
  • packages/cli/src/internal/__tests__/credentials.test.ts
  • packages/cli/src/internal/context.ts
  • packages/cli/src/internal/credentials.ts

@buremba buremba force-pushed the fix-local-context-auth branch from 4f7010d to b9c1a65 Compare May 21, 2026 02:55
@buremba buremba force-pushed the fix-local-context-auth branch from b9c1a65 to 9fc30a4 Compare May 21, 2026 03:01
Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
packages/cli/src/internal/__tests__/context.test.ts (1)

146-163: ⚡ Quick win

Add test coverage for IPv6 loopback context.

The test verifies IPv4 loopback (localhost and 127.0.0.1) behavior but doesn't cover IPv6 loopback ([::1]). Adding a test case for an IPv6 loopback context would improve coverage and help catch the IPv6 bracket handling issue flagged in context.ts.

🧪 Suggested test case for IPv6 loopback
test("derives local memory URL from an IPv6 loopback context URL", async () => {
  const configData = {
    currentContext: "local-v6",
    contexts: {
      lobu: { url: "https://app.lobu.ai/api/v1" },
      "local-v6": { url: "http://[::1]:8787/api/v1" },
    },
  };
  readFileSpy.mockResolvedValue(JSON.stringify(configData));

  expect(await getMemoryUrl("local-v6")).toBe("http://[::1]:8787/mcp");
  expect(
    (await findContextByMemoryUrl("http://[::1]:8787/mcp"))?.name
  ).toBe("local-v6");
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/internal/__tests__/context.test.ts` around lines 146 - 163,
Add a new unit test in packages/cli/src/internal/__tests__/context.test.ts that
mirrors the existing IPv4 loopback case but uses an IPv6 loopback address; set a
context URL to "http://[::1]:8787/api/v1", mock the config read like the other
tests, and assert that getMemoryUrl("local-v6") returns "http://[::1]:8787/mcp"
and that findContextByMemoryUrl("http://[::1]:8787/mcp")?.name === "local-v6" to
ensure bracketed IPv6 host handling in getMemoryUrl and findContextByMemoryUrl
is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli/src/internal/context.ts`:
- Around line 512-521: isLoopbackContextUrl fails to detect bracketed IPv6
hostnames because new URL(...).hostname returns "[::1]"; update the
isLoopbackContextUrl function to normalize the parsed hostname by stripping
surrounding brackets if present before comparing: obtain hostname via new
URL(input).hostname, remove a leading "[" and trailing "]" when both exist, then
check equality against "localhost", "127.0.0.1", and "::1" (keeping the existing
try/catch behavior) so bracketed IPv6 URLs like "http://[::1]:8787/..." are
correctly recognized as loopback.

In `@packages/cli/src/internal/credentials.ts`:
- Around line 169-176: The migration only calls tryLocalInit when
localWorkerToken is missing, but we also need to heal cases where accessToken is
still the old worker PAT (accessToken === localWorkerToken/device_token); update
the branch that checks isLoopbackContext(contextName) to also trigger remint
when creds.accessToken equals creds.localWorkerToken (or device token field) so
tryLocalInit(contextName) is run and creds replaced; locate the block using
isLoopbackContext, creds, tryLocalInit and getToken and add the equality check
for accessToken->localWorkerToken to force re-minting and persisting the new
session token.

---

Nitpick comments:
In `@packages/cli/src/internal/__tests__/context.test.ts`:
- Around line 146-163: Add a new unit test in
packages/cli/src/internal/__tests__/context.test.ts that mirrors the existing
IPv4 loopback case but uses an IPv6 loopback address; set a context URL to
"http://[::1]:8787/api/v1", mock the config read like the other tests, and
assert that getMemoryUrl("local-v6") returns "http://[::1]:8787/mcp" and that
findContextByMemoryUrl("http://[::1]:8787/mcp")?.name === "local-v6" to ensure
bracketed IPv6 host handling in getMemoryUrl and findContextByMemoryUrl is
covered.
🪄 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: 2a78e9ba-dd70-4d45-9ec2-2c00c39148d9

📥 Commits

Reviewing files that changed from the base of the PR and between b9c1a65 and 9fc30a4.

📒 Files selected for processing (7)
  • packages/cli/src/commands/chat.ts
  • packages/cli/src/commands/dev.ts
  • packages/cli/src/internal/__tests__/context.test.ts
  • packages/cli/src/internal/__tests__/credentials.test.ts
  • packages/cli/src/internal/context.ts
  • packages/cli/src/internal/credentials.ts
  • packages/cli/src/internal/index.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/cli/src/internal/tests/credentials.test.ts

Comment on lines +512 to +521
function isLoopbackContextUrl(input: string): boolean {
try {
const { hostname } = new URL(input);
return (
hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1"
);
} catch {
return false;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

IPv6 loopback detection fails for bracketed addresses.

The hostname property of a parsed IPv6 URL includes brackets. For example, new URL("http://[::1]:8787").hostname returns "[::1]", not "::1" (as noted in the comment at line 432). The check hostname === "::1" will never match a stored context URL like "http://[::1]:8787/api/v1", causing isLoopbackContextUrl to return false and preventing local memory URL derivation for IPv6 loopback contexts.

🔧 Proposed fix to handle bracketed IPv6 addresses
 function isLoopbackContextUrl(input: string): boolean {
   try {
     const { hostname } = new URL(input);
     return (
-      hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1"
+      hostname === "localhost" ||
+      hostname === "127.0.0.1" ||
+      hostname === "::1" ||
+      hostname === "[::1]"
     );
   } catch {
     return false;
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function isLoopbackContextUrl(input: string): boolean {
try {
const { hostname } = new URL(input);
return (
hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1"
);
} catch {
return false;
}
}
function isLoopbackContextUrl(input: string): boolean {
try {
const { hostname } = new URL(input);
return (
hostname === "localhost" ||
hostname === "127.0.0.1" ||
hostname === "::1" ||
hostname === "[::1]"
);
} catch {
return false;
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/internal/context.ts` around lines 512 - 521,
isLoopbackContextUrl fails to detect bracketed IPv6 hostnames because new
URL(...).hostname returns "[::1]"; update the isLoopbackContextUrl function to
normalize the parsed hostname by stripping surrounding brackets if present
before comparing: obtain hostname via new URL(input).hostname, remove a leading
"[" and trailing "]" when both exist, then check equality against "localhost",
"127.0.0.1", and "::1" (keeping the existing try/catch behavior) so bracketed
IPv6 URLs like "http://[::1]:8787/..." are correctly recognized as loopback.

Comment on lines +169 to +176
} else if (
!creds.localWorkerToken &&
(await isLoopbackContext(contextName))
) {
// Heal credentials saved by older CLIs that stored only the local-init
// worker PAT as accessToken. Re-mint so admin REST/MCP get the session
// token while chat keeps the companion worker PAT.
creds = (await tryLocalInit(contextName)) ?? creds;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Also heal loopback creds when accessToken is still the worker token.

This migration only reruns /api/local-init when localWorkerToken is missing. But Lines 224-228 now persist accessToken === localWorkerToken === device_token for older local servers, so after that server is upgraded to return session_token, getToken() will keep returning the stale worker PAT and admin REST/MCP stays broken until the user manually deletes credentials.

Proposed fix
-  } else if (
-    !creds.localWorkerToken &&
-    (await isLoopbackContext(contextName))
-  ) {
+  } else if (
+    (await isLoopbackContext(contextName)) &&
+    (!creds.localWorkerToken ||
+      creds.accessToken === creds.localWorkerToken)
+  ) {
     // Heal credentials saved by older CLIs that stored only the local-init
     // worker PAT as accessToken. Re-mint so admin REST/MCP get the session
     // token while chat keeps the companion worker PAT.
     creds = (await tryLocalInit(contextName)) ?? creds;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/internal/credentials.ts` around lines 169 - 176, The
migration only calls tryLocalInit when localWorkerToken is missing, but we also
need to heal cases where accessToken is still the old worker PAT (accessToken
=== localWorkerToken/device_token); update the branch that checks
isLoopbackContext(contextName) to also trigger remint when creds.accessToken
equals creds.localWorkerToken (or device token field) so
tryLocalInit(contextName) is run and creds replaced; locate the block using
isLoopbackContext, creds, tryLocalInit and getToken and add the equality check
for accessToken->localWorkerToken to force re-minting and persisting the new
session token.

@buremba buremba merged commit 176a3f1 into main May 21, 2026
21 checks passed
@buremba buremba deleted the fix-local-context-auth branch May 21, 2026 03:06
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.

CLI: lobu call / lobu memory run ignore -c local and can't auth to a local lobu run

2 participants