Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8effeb7
fix(inference): share the Ollama auth-proxy token across gateway ports
laitingsheng Aug 10, 2026
79e186a
merge(main): refresh PR #8736 for v0.0.106
apurvvkumaria Aug 11, 2026
705d813
fix(inference): serialize shared Ollama proxy state
apurvvkumaria Aug 11, 2026
33a5d74
merge(main): refresh PR #8736 for v0.0.107
apurvvkumaria Aug 11, 2026
fd19ca4
test(inference): keep proxy recovery cases linear
apurvvkumaria Aug 11, 2026
265d385
fix(inference): clarify shared proxy recovery
apurvvkumaria Aug 11, 2026
6b62457
docs(inference): explain proxy conflict recovery
apurvvkumaria Aug 11, 2026
102b71b
Merge branch 'main' into fix/ollama-proxy-token-gateway-scope
cv Aug 11, 2026
40a5062
Merge branch 'main' into fix/ollama-proxy-token-gateway-scope
cv Aug 11, 2026
60ad460
Merge branch 'main' into fix/ollama-proxy-token-gateway-scope
cv Aug 11, 2026
bda447c
fix(uninstall): preserve shared Ollama proxy state
apurvvkumaria Aug 11, 2026
30c5d1e
test(inference): cover shared Ollama proxy port
apurvvkumaria Aug 11, 2026
26be276
Merge branch 'main' into fix/ollama-proxy-token-gateway-scope
cv Aug 11, 2026
1bee935
fix(inference): preserve mixed legacy Ollama state
apurvvkumaria Aug 11, 2026
820a71e
Merge branch 'main' into fix/ollama-proxy-token-gateway-scope
cv Aug 11, 2026
6dd5cb2
Merge branch 'main' into fix/ollama-proxy-token-gateway-scope
cv Aug 11, 2026
5ca5a0d
fix(inference): serialize proxy token commit
apurvvkumaria Aug 12, 2026
8a7719c
merge(main): refresh PR #8736 for v0.0.108
apurvvkumaria Aug 12, 2026
8d9720a
Merge branch 'main' into fix/ollama-proxy-token-gateway-scope
prekshivyas Aug 13, 2026
3492826
fix(ci): restore static lint baseline
prekshivyas Aug 13, 2026
e542bf9
fix(ci): track managed image test size
prekshivyas Aug 13, 2026
9b72d02
test(ci): split managed image retention coverage
prekshivyas Aug 13, 2026
ae76878
test(ci): extract managed image workflow types
prekshivyas Aug 13, 2026
229e1b2
test(ci): allow loaded shields setup
prekshivyas Aug 13, 2026
c6c3c31
fix(ci): refresh libssh2 staging source
prekshivyas Aug 13, 2026
6291af0
merge: resolve conflicts with main
github-actions[bot] Aug 13, 2026
b85c75a
merge: resolve conflicts with main
github-actions[bot] Aug 13, 2026
cd1ad30
ci: run required checks for PR 8736
cv Aug 13, 2026
7d7d0c1
Merge branch 'main' into fix/ollama-proxy-token-gateway-scope
prekshivyas Aug 13, 2026
bafd8d8
fix(inference): bind shared Ollama proxy port
cv Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/managed-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ jobs:
CANDIDATE_SHA: ${{ github.event.pull_request.head.sha }}
# Retains the reviewed discovery-permission repair and the current
# managed-image security inventory. The previous staging source pinned
# Vim 9.2.0782, which cannot satisfy the candidate's 9.2.0858 contract.
STAGING_QA_SOURCE_SHA: af2a73f0d6ce8f08a2975560f376470387c535d0
# libssh2 nemoclaw1, which cannot satisfy the candidate's nemoclaw2 contract.
STAGING_QA_SOURCE_SHA: ce96811ddb418ad01c040521a1fe912b5bcb405e
STAGING_QA_BASE_IMAGE: nemoclaw-deepagents-code-base:staging-31396519688
STAGING_QA_FINAL_IMAGE: nemoclaw-managed-pr/langchain-deepagents-code-staging-qa
steps:
Expand Down
2 changes: 1 addition & 1 deletion ci/source-architecture-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"src/lib/security/redact.ts": 52,
"src/lib/state/onboard-session.ts": 36,
"src/lib/state/registry.ts": 97,
"src/lib/state/state-root.ts": 21,
"src/lib/state/state-root.ts": 20,
"src/lib/subprocess-env.ts": 24,
"src/lib/validation.ts": 25
}
Expand Down
23 changes: 22 additions & 1 deletion docs/inference/set-up-ollama.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,23 @@ The wizard manages the proxy lifecycle:
- It starts and verifies the proxy only after you accept the onboarding configuration.
- It removes stale matching proxy processes from previous runs.
- It probes the sandbox Docker network path before saving the inference route.
- It stops matching proxy processes during uninstall.
- It stops matching proxy processes when uninstall removes the final or only gateway that uses the proxy.
- A scoped uninstall preserves the shared proxy process and state while another gateway remains.
- It reuses the persisted token after a host reboot and during re-onboarding for the committed local Ollama route.
- It reuses that same token for every gateway port on the host, because one proxy serves them all.
- NemoClaw records the selected proxy port in shared host state. Export the same `NEMOCLAW_OLLAMA_PROXY_PORT` for every gateway port on the host. A different value stops before NemoClaw changes the shared proxy process.

<Warning title="Conflicting Legacy Proxy State">
An older multi-gateway installation can contain proxy token or backend files in more than one gateway state root.
During Local Ollama onboarding, NemoClaw copies one consistent credential and backend URL into the shared host root.
If token values conflict, NemoClaw uses a running proxy only when one token receives an authenticated response.
NemoClaw stops before proxy restart when it cannot select one token or one backend URL.
For token conflicts, the error lists the token files that require review.
Keep every token file private.
Back up the gateway state roots and confirm which token and backend serve the active sandboxes before you remove stale legacy files.
Rerun onboarding for every affected Local Ollama gateway after you resolve the conflict.
Scoped uninstall preserves the shared proxy state while a sibling gateway remains; removing the final gateway stops the proxy and removes that state.
</Warning>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

All proxy endpoints require the token, including `GET /api/tags`.
The host-side proxy liveness check treats any HTTP response, including `401`, as evidence that the proxy answered.
Expand Down Expand Up @@ -249,6 +264,12 @@ The following command uses `8990` as an example; choose another unused port when
NEMOCLAW_GATEWAY_PORT=8990 NEMOCLAW_MODEL=qwen3.5:9b $$nemoclaw onboard
```

Every gateway port on the host shares one authenticated proxy and one token.
When token and backend state are compatible, onboarding another gateway port restarts the shared proxy with the token already stored on the host.
Sandboxes on the gateway you onboarded first keep reaching Ollama after the restart.
If the restart fails, onboarding reports the port owner or the startup error.
No sandbox on any gateway port can reach Ollama until you resolve the error and rerun onboarding.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Interactive onboarding filters installed registry-known tags that do not fit current GPU memory.
If no installed known tag fits, NemoClaw displays starter choices and warns when even the smallest tag might not fit.
After a model fails validation, NemoClaw excludes it from the next installed-model menu.
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ NemoClaw keeps non-secret operator-facing state on the host rather than inside t
| Path | Purpose |
|---|---|
| `~/.nemoclaw/sandboxes.json` | Registered sandbox metadata for the default gateway port, including the default sandbox selection. |
| `~/.nemoclaw/gateways/<port>/` | Segregated host state root (its own registry, snapshots, and legacy credential-migration files) for a non-default `NEMOCLAW_GATEWAY_PORT`. On upgrade, rows and related state move out of the legacy shared root only when their recorded gateway identity matches the selected port. Provider credentials remain in the OpenShell gateway store. The default gateway port uses the top-level `~/.nemoclaw/` location, so existing single-gateway hosts are unchanged. |
| `~/.nemoclaw/gateways/<port>/` | Segregated host state root (its own registry, snapshots, and legacy credential-migration files) for a non-default `NEMOCLAW_GATEWAY_PORT`. On upgrade, rows and related state move out of the legacy shared root only when their recorded gateway identity matches the selected port. Provider credentials remain in the OpenShell gateway store. The Ollama auth proxy token, backend URL, PID, and status files stay in the top-level `~/.nemoclaw/` location for every gateway port, because one proxy on the host serves them all. The default gateway port uses the top-level `~/.nemoclaw/` location, so existing single-gateway hosts are unchanged. |
<AgentOnly variant="openclaw">
| `~/.openclaw/openclaw.json` | Host OpenClaw configuration that NemoClaw snapshots or restores during migration flows. |
</AgentOnly>
Expand Down
1 change: 1 addition & 0 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4022,6 +4022,7 @@ Uninstall also stops any orphaned `openshell` host processes left behind by prev
Earlier releases only stopped `openshell forward` processes, so those orphans accumulated across runs.

For Local Ollama setups, uninstall also stops matching Ollama auth proxy processes before deleting `~/.nemoclaw` state so stale proxy listeners do not block a later reinstall.
When sibling gateways remain, uninstall leaves the shared proxy running for them.

For Hermes setups, uninstall inspects the selected gateway's managed port-forward watcher state, stops each verified watcher process and its sandbox-scoped forward, and leaves sibling gateway state untouched.
If any watcher or forward cleanup cannot be confirmed, uninstall exits nonzero and preserves the selected gateway's watcher state so you can retry cleanup.
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/host-files-and-state.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ Share redacted diagnostics only.
| `~/.nemoclaw/sandboxes.json` | Current sandbox registry used by `$$nemoclaw list`, default sandbox selection, rebuild, and recovery commands. See [Malformed Registry File](#malformed-registry-file) when a command reports that this file is not valid JSON. | No. Deleting it makes the host forget existing sandboxes and can block state-preserving recovery. |
| `~/.nemoclaw/onboard-session.json` | Resume marker for an onboarding attempt that failed before completion. | Yes, when you intentionally want to discard the failed session and start over. Prefer `$$nemoclaw onboard --fresh` when available. |
| `~/.nemoclaw/usage-notice.json` | Records the third-party software notice version in `acceptedVersion` and the acceptance time in `acceptedAt`. Install, onboarding, and rebuild flows consult this file and prompt again when its recorded version differs from the current notice or the file is absent. | Yes; deleting it makes the next applicable install, onboarding, or rebuild flow prompt for acceptance again. |
| `~/.nemoclaw/ollama-proxy-token` | Local auth token used by the host-side Ollama auth proxy. | Yes, but re-run onboarding afterward so NemoClaw recreates and registers the proxy token. |
| `~/.nemoclaw/ollama-proxy-token` | Host-global auth token used by the host-side Ollama auth proxy. Every gateway port on the host uses this one file. | Yes, but rerun onboarding for every gateway port that uses Local Ollama afterward, because a new token invalidates the shared proxy for all of them. |
| `~/.nemoclaw/ollama-proxy-port` | Host-global port used by the Ollama auth proxy and every Local Ollama sandbox route on the host. A command with a different `NEMOCLAW_OLLAMA_PROXY_PORT` stops before changing the shared proxy. | No while any gateway uses Local Ollama. Full uninstall uses this file to find the proxy and removes it with the other shared proxy state. |
| `~/.nemoclaw/ollama/user-local-ownership.json` | Mode-`0600`, credential-free receipt containing a schema version and the fixed `~/.local/bin/ollama` path. Portable OpenClaw recovery requires this receipt before it starts the binary. A successful system Ollama install removes the receipt. | No while a portable OpenClaw sandbox uses the user-local daemon. Deleting it disables automatic daemon restart until you reinstall Ollama through onboarding. |
| `~/.nemoclaw/dual-station-vllm-api-key` | Owner-only host-global bearer API key shared by authenticated NemoClaw-managed vLLM profiles. | No while any managed vLLM runtime uses it. Full uninstall removes it only after matching runtime cleanup succeeds and no sibling gateway remains. |
| `~/.nemoclaw/host-local-vllm-runtime.json` | Owner-only, credential-free receipt for a catalog-selected single-host vLLM runtime. It binds the immutable catalog, preset, and recipe digests to the exact container ID and API-key fingerprint. | No while that runtime exists. Recovery and full uninstall fail closed if its profile labels do not match this receipt, and uninstall removes it only after exact container cleanup succeeds. |
Expand Down
48 changes: 46 additions & 2 deletions src/lib/actions/uninstall/run-plan-gateway-segregation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,9 +786,22 @@ describe("uninstall gateway-port segregation (#3053)", () => {
},
}),
);
const proxyStateEntries = [
"ollama-proxy-token",
"ollama-backend",
"ollama-auth-proxy.pid",
"ollama-auth-proxy.status",
];
for (const entry of proxyStateEntries) {
const value = entry === "ollama-auth-proxy.pid" ? "4242\n" : `${entry}\n`;
fs.writeFileSync(path.join(shared, entry), value);
fs.writeFileSync(path.join(selected, entry), `legacy-${value}`);
}

const runCalls: Array<{ command: string; args: string[] }> = [];
const dockerCalls: string[][] = [];
const logs: string[] = [];
const kill = vi.fn((_pid: number, _signal?: NodeJS.Signals | number) => true);
const dockerOutputByCommand: Record<string, string> = {
images: "shared-image nemoclaw:latest",
ps: [
Expand All @@ -809,10 +822,13 @@ describe("uninstall gateway-port segregation (#3053)", () => {
env: { HOME: tmpHome, NEMOCLAW_GATEWAY_PORT: String(port) } as NodeJS.ProcessEnv,
existsSync: (target) => target.startsWith(tmpHome) && fs.existsSync(target),
isTty: false,
log: vi.fn(),
kill,
log: (line) => logs.push(line),
run: (command, args) => {
runCalls.push({ command, args });
return ok();
return command === "ps" && args.includes("4242") && args.includes("args=")
? ok("node /opt/nemoclaw/scripts/ollama-auth-proxy.mts\n")
: ok();
},
runDocker: (args) => {
dockerCalls.push(args);
Expand Down Expand Up @@ -842,6 +858,13 @@ describe("uninstall gateway-port segregation (#3053)", () => {
expect(fs.existsSync(servicePath)).toBe(true);
expect(runCalls.some(({ command }) => command === "systemctl")).toBe(false);
expect(fs.existsSync(path.join(nemoclawConfig, "keep"))).toBe(true);
expect(kill.mock.calls.every(([pid]) => pid !== 4242)).toBe(true);
for (const entry of proxyStateEntries) {
expect(fs.existsSync(path.join(shared, entry))).toBe(true);
}
expect(logs).toContain(
"Preserving the shared Ollama auth proxy for the remaining gateway ports",
);
} finally {
fs.rmSync(tmpHome, { recursive: true, force: true });
}
Expand Down Expand Up @@ -1192,15 +1215,32 @@ describe("uninstall gateway-port segregation (#3053)", () => {
},
}),
);
const proxyStateEntries = [
"ollama-proxy-token",
"ollama-backend",
"ollama-auth-proxy.pid",
"ollama-auth-proxy.status",
];
for (const entry of proxyStateEntries) {
fs.writeFileSync(
path.join(stateDir, entry),
entry === "ollama-auth-proxy.pid" ? "4242\n" : "seeded\n",
);
}
const logs: string[] = [];
const openshellCalls: string[][] = [];
let proxyProcessIsRunning = true;
const result = runUninstallPlan(
{ assumeYes: true, deleteModels: false, destroyUserData: true, keepOpenShell: false },
{
commandExists: (command) => command === "openshell",
env: { HOME: tmpHome, NEMOCLAW_NON_INTERACTIVE: "1" } as NodeJS.ProcessEnv,
existsSync: (target) => target.startsWith(tmpHome) && fs.existsSync(target),
isTty: false,
kill: () => {
proxyProcessIsRunning = false;
return true;
},
log: (line) => logs.push(line),
rmSync: fs.rmSync,
run: (_command, args) => {
Expand All @@ -1220,6 +1260,10 @@ describe("uninstall gateway-port segregation (#3053)", () => {
expect(openshellCalls).not.toContainEqual(["sandbox", "delete", "--all"]);
expect(logs.join("\n")).toContain("Sibling gateways remain");
expect(fs.existsSync(path.join(stateDir, "gateways", "8091"))).toBe(true);
expect(proxyProcessIsRunning).toBe(true);
for (const entry of proxyStateEntries) {
expect(fs.existsSync(path.join(stateDir, entry))).toBe(true);
}
} finally {
fs.rmSync(tmpHome, { recursive: true, force: true });
}
Expand Down
14 changes: 10 additions & 4 deletions src/lib/actions/uninstall/run-plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ describe("uninstall run plan", () => {
expect(logs).toContain("No Ollama auth proxy processes found");
});

it("scans the custom NEMOCLAW_OLLAMA_PROXY_PORT for orphan auth proxies", () => {
it("uses the persisted Ollama proxy port for orphan cleanup (#8704)", () => {
const logs: string[] = [];
const killed: number[] = [];
const exited = new Set<number>();
Expand All @@ -551,16 +551,22 @@ describe("uninstall run plan", () => {
env: {
HOME: "/tmp/nemoclaw-uninstall-test-2759-custom-port",
LOGNAME: "testuser",
NEMOCLAW_OLLAMA_PROXY_PORT: "12000",
NEMOCLAW_OLLAMA_PROXY_PORT: "13000",
} as NodeJS.ProcessEnv,
existsSync: () => false,
existsSync: (target) => target.endsWith("/ollama-proxy-port"),
isTty: false,
kill: (pid, _signal) => {
killed.push(pid);
exited.add(pid);
return true;
},
log: (line) => logs.push(line),
openRegularFile: () => ({
close: () => {},
readBytes: () => Buffer.from("12000\n"),
readUtf8: () => "12000\n",
replaceUtf8: () => {},
}),
rmSync: vi.fn(),
run: (command, args) => {
if (command === "lsof" && args[0] === "-ti") {
Expand All @@ -583,7 +589,7 @@ describe("uninstall run plan", () => {

expect(result.exitCode).toBe(0);
expect(lsofPorts).toContain(":12000");
expect(lsofPorts).not.toContain(":11435");
expect(lsofPorts).not.toContain(":13000");
expect(killed).toContain(33333);
expect(logs).toContain("Stopped Ollama auth proxy 33333");
});
Expand Down
Loading
Loading