Skip to content

Commit 96c209e

Browse files
committed
fix(messaging): require Google Chat gateway ownership
Signed-off-by: Ho Lim <subhoya@gmail.com>
1 parent bb35c48 commit 96c209e

2 files changed

Lines changed: 34 additions & 6 deletions

File tree

src/lib/actions/sandbox/exec-googlechat-pairing-restart.test.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const CLEANUP_SKIPPED: SandboxExecCleanupDeps = {
2323
function depsFor(status: number, restartGateway = vi.fn(() => ({ ok: true }))): ExecSandboxDeps {
2424
return {
2525
resolveBinary: () => "openshell",
26-
selectGateway: () => ({ outcome: "unregistered", gatewayName: null }),
26+
selectGateway: () => ({ outcome: "selected", gatewayName: "nemoclaw-alpha" }),
2727
run: () => ({ status }),
2828
cleanupDeps: CLEANUP_SKIPPED,
2929
restartGateway,
@@ -196,9 +196,7 @@ describe("Google Chat pairing approval gateway activation (#8553)", () => {
196196

197197
expect(restartGateway).toHaveBeenCalledOnce();
198198
expect(exitCode).toBe(1);
199-
expect(errorSpy).toHaveBeenCalledWith(
200-
expect.stringContaining("approval was not rolled back"),
201-
);
199+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("approval was not rolled back"));
202200
expect(errorSpy).toHaveBeenCalledWith(
203201
expect.stringContaining("nemoclaw alpha gateway restart"),
204202
);
@@ -257,6 +255,34 @@ describe("Google Chat pairing approval gateway activation (#8553)", () => {
257255
expect(exitCode).toBe(0);
258256
});
259257

258+
it("does not activate or claim managed recovery without an owning gateway", async () => {
259+
const restartGateway = vi.fn(() => ({ ok: true }));
260+
const deps = depsFor(0, restartGateway);
261+
deps.selectGateway = () => ({ outcome: "unregistered", gatewayName: null });
262+
deps.cleanupDeps = {
263+
getSandbox: () => {
264+
throw new Error("invalid registry JSON");
265+
},
266+
inspectMutableConfigPerms: CLEANUP_SKIPPED.inspectMutableConfigPerms,
267+
repairMutableConfigPerms: CLEANUP_SKIPPED.repairMutableConfigPerms,
268+
};
269+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
270+
271+
const exitCode = await runAndCaptureExit(
272+
["openclaw", "pairing", "approve", "googlechat", "ABCD1234"],
273+
deps,
274+
);
275+
276+
expect(restartGateway).not.toHaveBeenCalled();
277+
expect(exitCode).toBe(1);
278+
expect(errorSpy).not.toHaveBeenCalledWith(
279+
expect.stringContaining("managed gateway activation failed"),
280+
);
281+
expect(errorSpy).not.toHaveBeenCalledWith(
282+
expect.stringContaining("nemoclaw alpha gateway restart"),
283+
);
284+
});
285+
260286
it("fails closed when the recorded sandbox identity cannot be read", async () => {
261287
const restartGateway = vi.fn(() => ({ ok: true }));
262288
const deps = depsFor(0, restartGateway);

src/lib/actions/sandbox/exec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,10 +484,12 @@ export async function execSandbox(
484484
let exitCode = completion.code;
485485
const googleChatApprovalCommitted =
486486
completion.commandCode === 0 && isGoogleChatPairingApproval(command);
487-
if (googleChatApprovalCommitted && completion.cleanupError) {
487+
const managedGoogleChatApproval =
488+
googleChatApprovalCommitted && gatewaySelection.outcome === "selected";
489+
if (managedGoogleChatApproval && completion.cleanupError) {
488490
console.error(googleChatPairingActivationFailureMessage(CLI_NAME, sandboxName));
489491
}
490-
if (exitCode === 0 && googleChatApprovalCommitted) {
492+
if (exitCode === 0 && managedGoogleChatApproval) {
491493
let recordedAgent: string | null;
492494
try {
493495
recordedAgent = (deps.resolveSandboxAgent ?? defaultResolveSandboxAgent)(sandboxName);

0 commit comments

Comments
 (0)