Skip to content
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
44caf41
fix(uninstall): exit nonzero when the gateway registration cannot be …
udsy19 Aug 20, 2026
ad8588a
merge: synchronize main into uninstall failure fix
apurvvkumaria Aug 21, 2026
578192e
fix(uninstall): preserve state on registration failure
apurvvkumaria Aug 21, 2026
fa76588
merge: synchronize main into uninstall failure fix
apurvvkumaria Aug 21, 2026
53a51b3
test(uninstall): cover registration failure paths
apurvvkumaria Aug 21, 2026
cee0668
test(uninstall): isolate provider no-op wording
apurvvkumaria Aug 21, 2026
21e1070
merge: synchronize current main into uninstall failure fix
apurvvkumaria Aug 21, 2026
ac95e65
test(uninstall): constrain gateway removal mock
apurvvkumaria Aug 21, 2026
0c50b09
merge: sync current main into uninstall exit fix
apurvvkumaria Aug 21, 2026
ef75481
merge: synchronize current main into uninstall exit fix
apurvvkumaria Aug 21, 2026
4c0c9b7
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Aug 22, 2026
d0b576b
test(uninstall): split user-data cases
cv Aug 22, 2026
09fee88
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
prekshivyas Aug 23, 2026
4ca1dc7
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
prekshivyas Aug 23, 2026
4a8ee2c
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
prekshivyas Aug 23, 2026
5799085
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
prekshivyas Aug 23, 2026
2f50234
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
prekshivyas Aug 23, 2026
5ec315d
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
prekshivyas Aug 23, 2026
9c28ee8
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
prekshivyas Aug 23, 2026
9cb692d
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
apurvvkumaria Aug 24, 2026
92f2c7e
test(uninstall): isolate legacy failure fixture
apurvvkumaria Aug 24, 2026
6086b14
fix(uninstall): match explicit gateway absence
apurvvkumaria Aug 24, 2026
947a335
merge: update PR branch with main
apurvvkumaria Aug 24, 2026
b87fce1
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Aug 24, 2026
b5a1c73
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Aug 24, 2026
9f77384
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Aug 24, 2026
d71d8eb
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Aug 24, 2026
4290af2
fix(uninstall): stage selective cleanup safely
apurvvkumaria Aug 24, 2026
9869d83
fix(uninstall): explain staged state recovery
apurvvkumaria Aug 24, 2026
7bb5bbe
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Aug 24, 2026
9239c1d
fix(uninstall): delete verified staged state
cv Aug 24, 2026
2b614a5
test(uninstall): isolate staged cleanup race
apurvvkumaria Aug 24, 2026
f346e5c
fix(uninstall): detect abandoned staged state
apurvvkumaria Aug 24, 2026
554fb65
fix(uninstall): reject stale cleanup staging
apurvvkumaria Aug 24, 2026
981deeb
fix(uninstall): preflight staged cleanup
apurvvkumaria Aug 24, 2026
af617f4
fix(uninstall): distinguish staged recovery paths
apurvvkumaria Aug 24, 2026
c27dbcf
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Aug 24, 2026
0ee0302
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Aug 24, 2026
a6569dc
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Aug 25, 2026
d2ded93
merge: refresh from main
apurvvkumaria Aug 25, 2026
71189ef
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Aug 27, 2026
0c5c9a8
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Aug 28, 2026
d055965
fix(uninstall): detect incomplete cleanup staging
cv Aug 28, 2026
a69e8a7
fix(uninstall): clarify staged cleanup recovery
cv Aug 28, 2026
b22ac11
ci: retrigger PR review advisor
cv Aug 28, 2026
d5fb80d
merge: refresh uninstall failure handling
jyaunches Sep 2, 2026
5809cd4
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Sep 9, 2026
7aa0f3c
merge: refresh uninstall gateway failure handling
jyaunches Sep 9, 2026
87eded4
fix(uninstall): require explicit gateway absence
jyaunches Sep 9, 2026
2a45a75
fix(uninstall): accept structured gateway absence
jyaunches Sep 9, 2026
af0cc1e
merge: refresh uninstall gateway failure handling
jyaunches Sep 9, 2026
8b8818f
fix(uninstall): tighten absence evidence
jyaunches Sep 9, 2026
4bd3fb0
Merge remote-tracking branch 'origin/main' into HEAD
jyaunches Sep 9, 2026
bdb3ee4
fix(uninstall): reject mixed absence diagnostics
jyaunches Sep 9, 2026
4afe9c4
fix(uninstall): verify generic gateway absence
jyaunches Sep 9, 2026
4350162
docs(uninstall): clarify registration failure recovery
jyaunches Sep 9, 2026
1be0516
docs(uninstall): distinguish registry cleanup scopes
jyaunches Sep 9, 2026
37db0d1
merge: refresh uninstall gateway cleanup
jyaunches Sep 9, 2026
144c1ab
test(uninstall): verify absence postcondition order
jyaunches Sep 9, 2026
c89fe72
merge: refresh uninstall gateway cleanup
jyaunches Sep 9, 2026
397f4f4
fix(uninstall): verify gateway absence postconditions
jyaunches Sep 9, 2026
7cd7eb3
refactor(uninstall): simplify absence classification
jyaunches Sep 9, 2026
b932437
Merge remote-tracking branch 'origin/main' into HEAD
jyaunches Sep 9, 2026
9c2333e
test(uninstall): consolidate registration failure coverage
jyaunches Sep 9, 2026
b70d39d
Merge branch 'main' into fix/uninstall-gateway-registration-exit-code
cv Sep 9, 2026
dad58c6
test(uninstall): cover unverified absence state
jyaunches Sep 9, 2026
1dc0717
merge: refresh uninstall gateway cleanup
jyaunches Sep 9, 2026
ecd2eeb
merge: preserve concurrent branch refresh
jyaunches Sep 9, 2026
d9ca515
test(uninstall): cover stdout diagnostic redaction
jyaunches Sep 9, 2026
92c3f94
refactor(gateway): centralize registration removal policy
jyaunches Sep 9, 2026
ba7a5fa
merge: refresh shared gateway cleanup
jyaunches Sep 9, 2026
5585e78
fix(uninstall): centralize gateway removal policy
jyaunches Sep 9, 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
52 changes: 49 additions & 3 deletions src/lib/actions/uninstall/run-plan-gateway-segregation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ describe("uninstall gateway-port segregation (#3053)", () => {
},
);

expect(result.exitCode).toBe(0);
expect(result.exitCode).toBe(1);
const openshellCalls = calls
.filter(({ command }) => command === "openshell")
.map(({ args }) => args);
Expand Down Expand Up @@ -341,13 +341,59 @@ describe("uninstall gateway-port segregation (#3053)", () => {
},
);

expect(result.exitCode).toBe(0);
expect(result.exitCode).toBe(1);
const openshellCalls = calls
.filter(({ command }) => command === "openshell")
.map(({ args }) => args);
expect(openshellCalls).toContainEqual(["gateway", "remove", "nemoclaw"]);
expect(openshellCalls.some((args) => args[1] === "destroy")).toBe(false);
expect(warnings.join("\n")).toContain("Gateway 'nemoclaw' already removed or unreachable");
expect(warnings.join("\n")).toContain(
"Could not remove gateway registration 'nemoclaw': openshell gateway remove failed (exit 1).",
);
});

it("exits nonzero and preserves cleanup state when gateway registration removal fails (#9859)", () => {
const warnings: string[] = [];
const logs: string[] = [];
const rmSync = vi.fn();
const result = runUninstallPlan(
{ assumeYes: true, deleteModels: false, keepOpenShell: false },
{
commandExists: () => true,
env: { HOME: "/tmp/nemoclaw-uninstall-test-gateway-remove" } as NodeJS.ProcessEnv,
error: (line: string) => warnings.push(line),
existsSync: () => false,
hasPortableRuntimeCleanup: () => false,
isTty: false,
kill: () => true,
log: (line) => logs.push(line),
rmSync,
run: (command: string, args: string[]) =>
command === "openshell" && args[0] === "gateway" && args[1] === "remove"
? {
status: 1,
stdout: "",
stderr: "connection refused; OPENAI_API_KEY=must-not-be-logged",
}
: command === "openshell" && args[0] === "gateway" && args[1] === "list"
? ok(JSON.stringify([{ name: "nemoclaw" }]))
: ok(),
runDocker: () => ok(),
},
);

expect(result.exitCode).toBe(1);
expect(warnings).toContain(
"Could not remove gateway registration 'nemoclaw': openshell gateway remove failed (connection refused; exit 1).",
);
expect(warnings.join("\n")).not.toContain("must-not-be-logged");
expect(warnings).not.toContain("Gateway 'nemoclaw' already removed or unreachable");
expect(warnings).toContain(
"Uninstall completed with errors. Some state may remain on disk; see warnings above.",
);
expect(rmSync).not.toHaveBeenCalled();
expect(logs).not.toContain("[3/6] NemoClaw CLI");
expect(logs).not.toContain("Claws retracted. Until next time.");
});

it("preserves the gateways/ subtree so uninstalling one environment leaves the others", () => {
Expand Down
6 changes: 4 additions & 2 deletions src/lib/actions/uninstall/run-plan-preserved-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,10 @@ describe("uninstall messaging for a preserved-but-orphaned sandbox registry (#65
run: (command, args) =>
command === "openshell" && args[0] === "gateway" && args[1] === "list"
? ok(JSON.stringify([{ name: "nemoclaw" }]))
: command === "openshell"
? notFound()
: command === "openshell" && args[0] === "gateway" && args[1] === "remove"
? { status: 1, stdout: "", stderr: "gateway not found" }
: command === "openshell"
? notFound()
: args[0] === "-c"
? ok("/fake/bin/tool\n")
: ok(),
Expand Down
1 change: 1 addition & 0 deletions src/lib/actions/uninstall/run-plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1494,4 +1494,5 @@ describe("uninstall run plan", () => {
expect(killed).toContain(9999887);
expect(logs).toContain("Stopped host openshell-gateway process 9999887");
});

});
57 changes: 49 additions & 8 deletions src/lib/actions/uninstall/run-plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -906,11 +906,45 @@ function deletePortableOpenShellSandbox(
return false;
}

const GATEWAY_ALREADY_ABSENT =
/gateway[^\n]*(?:does not exist|not found)|No (?:active )?gateway|No gateway metadata found/i;
const GATEWAY_REMOVE_UNSUPPORTED =
/unrecognized subcommand ['"]remove['"]|unknown command ['"]remove['"]/i;

function isExplicitGatewayRegistrationAbsence(output: string, gatewayLabel: string): boolean {
const clean = output.replace(/\x1B\[[0-?]*[ -/]*[@-~]/gu, "");
const escapedLabel = gatewayLabel.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
const namedGateway = `(?:['"]${escapedLabel}['"]|${escapedLabel})`;
if (
clean.includes(`No gateway metadata found for '${gatewayLabel}'.`) ||
clean.includes(`No gateway metadata found for \"${gatewayLabel}\".`) ||
clean.includes(`No gateway metadata found for ${gatewayLabel}.`)
) {
return true;
}
return clean.split(/\r?\n/u).some((rawLine) => {
const line = rawLine
.trim()
.replace(/^Error:\s*/iu, "")
.replace(/^×\s*/u, "");
return (
/^gateway not found\.?$/iu.test(line) ||
new RegExp(`^No gateway metadata found for ${namedGateway}\\.?$`, "iu").test(line) ||
new RegExp(`^gateway\\s+${namedGateway}\\s+(?:does not exist|not found)\\.?$`, "iu").test(line) ||
new RegExp(`^status:\\s*NotFound,\\s*message:\\s*['"]gateway\\s+${escapedLabel}\\s+(?:does not exist|not found)['"]\\.?$`, "iu").test(line)
);
});
}

function gatewayRegistrationRemovalFailureMessage(
gatewayLabel: string,
operation: "destroy" | "remove",
result: RunResult,
): string {
const output = `${result.stdout}\n${result.stderr}`;
const cause = /connection refused/iu.test(output) ? "connection refused; " : "";
const status = result.status === null ? "no exit status" : `exit ${String(result.status)}`;
return `Could not remove gateway registration '${gatewayLabel}': openshell gateway ${operation} failed (${cause}${status}).`;
}

function removeGatewayRegistration(
runtime: UninstallRuntime,
gatewayLabel: string,
Expand All @@ -925,12 +959,15 @@ function removeGatewayRegistration(
}

const removeOutput = `${removeResult.stdout}\n${removeResult.stderr}`;
if (GATEWAY_ALREADY_ABSENT.test(removeOutput)) {
if (
isExplicitGatewayRegistrationAbsence(removeOutput, gatewayLabel) ||
(/No gateway metadata found for/iu.test(removeOutput) && removeOutput.includes(gatewayLabel))
) {
runtime.warn(gatewayDestroySkipMessage(gatewayLabel));
return true;
}
if (!GATEWAY_REMOVE_UNSUPPORTED.test(removeOutput)) {
runtime.warn(gatewayDestroySkipMessage(gatewayLabel));
runtime.warn(gatewayRegistrationRemovalFailureMessage(gatewayLabel, "remove", removeResult));
return false;
}
if (!allowLegacyDestroy) {
Expand All @@ -951,11 +988,16 @@ function removeGatewayRegistration(
runtime.log(`Destroyed legacy gateway '${gatewayLabel}'`);
return true;
}
if (GATEWAY_ALREADY_ABSENT.test(`${destroyResult.stdout}\n${destroyResult.stderr}`)) {
if (
isExplicitGatewayRegistrationAbsence(
`${destroyResult.stdout}\n${destroyResult.stderr}`,
gatewayLabel,
)
) {
runtime.warn(gatewayDestroySkipMessage(gatewayLabel));
return true;
}
runtime.warn(gatewayDestroySkipMessage(gatewayLabel));
runtime.warn(gatewayRegistrationRemovalFailureMessage(gatewayLabel, "destroy", destroyResult));
return false;
}

Expand Down Expand Up @@ -1614,8 +1656,7 @@ function removeOpenShellResources(
{ onSkip: providerDeleteSkipMessage(provider) },
);
}
removeGatewayRegistration(runtime, gatewayLabel, !externallySupervised);
return true;
return removeGatewayRegistration(runtime, gatewayLabel, !externallySupervised);
}

function normalizeGatewayProcessExecutable(
Expand Down
Loading