Skip to content

Commit 0c977cd

Browse files
committed
fix: avoid early Slack credential leases in Mantis
1 parent 70d92b5 commit 0c977cd

3 files changed

Lines changed: 89 additions & 9 deletions

File tree

.github/workflows/mantis-slack-desktop-smoke.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ jobs:
229229
keep_args=(--no-keep-lease)
230230
fi
231231
232+
set +e
232233
pnpm openclaw qa mantis slack-desktop-smoke \
233234
--repo-root "$candidate_repo" \
234235
--output-dir "$output_rel" \
@@ -245,6 +246,13 @@ jobs:
245246
--fast \
246247
--scenario "$SCENARIO_ID" \
247248
"${keep_args[@]}"
249+
mantis_exit=$?
250+
set -e
251+
252+
if [[ ! -f "$root/mantis-slack-desktop-smoke-summary.json" ]]; then
253+
echo "Mantis Slack desktop smoke did not produce a summary." >&2
254+
exit "$mantis_exit"
255+
fi
248256
249257
if [[ -f "$root/slack-desktop-smoke.mp4" ]]; then
250258
if ! command -v ffmpeg >/dev/null 2>&1 || ! command -v ffprobe >/dev/null 2>&1; then
@@ -296,6 +304,10 @@ jobs:
296304
echo "Slack desktop smoke failed." >&2
297305
exit 1
298306
fi
307+
if [[ "$mantis_exit" -ne 0 ]]; then
308+
echo "Slack desktop smoke exited with $mantis_exit after reporting status $status." >&2
309+
exit "$mantis_exit"
310+
fi
299311
300312
- name: Upload Mantis Slack desktop artifacts
301313
id: upload_artifact

extensions/qa-lab/src/mantis/slack-desktop-smoke.runtime.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,11 @@ describe("mantis Slack desktop smoke runtime", () => {
157157

158158
it("leases Convex Slack credentials for gateway setup and maps them into the VM env", async () => {
159159
const commands: { args: readonly string[]; command: string; env?: NodeJS.ProcessEnv }[] = [];
160+
const events: string[] = [];
160161
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
161162
const url = describeFetchInput(input);
162163
if (url.endsWith("/acquire")) {
164+
events.push("acquire");
163165
return new Response(
164166
JSON.stringify({
165167
credentialId: "cred-slack",
@@ -177,6 +179,7 @@ describe("mantis Slack desktop smoke runtime", () => {
177179
);
178180
}
179181
if (url.endsWith("/release") || url.endsWith("/heartbeat")) {
182+
events.push(url.endsWith("/release") ? "release" : "heartbeat");
180183
return new Response(JSON.stringify({ status: "ok" }), { status: 200 });
181184
}
182185
throw new Error(`unexpected fetch: ${url} ${describeFetchBody(init?.body)}`);
@@ -186,6 +189,7 @@ describe("mantis Slack desktop smoke runtime", () => {
186189
const runner = vi.fn(
187190
async (command: string, args: readonly string[], options: { env?: NodeJS.ProcessEnv }) => {
188191
commands.push({ command, args, env: options.env });
192+
events.push(`${command}:${args[0]}`);
189193
if (command === "/tmp/crabbox" && args[0] === "warmup") {
190194
return { stdout: "ready lease cbx_c0ffee\n", stderr: "" };
191195
}
@@ -244,6 +248,17 @@ describe("mantis Slack desktop smoke runtime", () => {
244248
});
245249

246250
expect(result.status).toBe("pass");
251+
expect(events).toEqual(
252+
expect.arrayContaining([
253+
"/tmp/crabbox:warmup",
254+
"/tmp/crabbox:inspect",
255+
"acquire",
256+
"/tmp/crabbox:run",
257+
"release",
258+
]),
259+
);
260+
expect(events.indexOf("acquire")).toBeGreaterThan(events.indexOf("/tmp/crabbox:inspect"));
261+
expect(events.indexOf("acquire")).toBeLessThan(events.indexOf("/tmp/crabbox:run"));
247262
const runCommand = commands.find(
248263
(entry) => entry.command === "/tmp/crabbox" && entry.args[0] === "run",
249264
);
@@ -274,6 +289,59 @@ describe("mantis Slack desktop smoke runtime", () => {
274289
expect(summary.slackUrl).toBe("https://app.slack.com/client/TLEASED/CLEASED");
275290
});
276291

292+
it("stops a created no-keep lease when the remote Slack QA run fails", async () => {
293+
const commands: { args: readonly string[]; command: string }[] = [];
294+
const runner = vi.fn(async (command: string, args: readonly string[]) => {
295+
commands.push({ command, args });
296+
if (command === "/tmp/crabbox" && args[0] === "warmup") {
297+
return { stdout: "ready lease cbx_fade123\n", stderr: "" };
298+
}
299+
if (command === "/tmp/crabbox" && args[0] === "inspect") {
300+
return {
301+
stdout: `${JSON.stringify({
302+
host: "203.0.113.10",
303+
id: "cbx_fade123",
304+
provider: "hetzner",
305+
sshKey: "/tmp/key",
306+
sshPort: "2222",
307+
sshUser: "crabbox",
308+
})}\n`,
309+
stderr: "",
310+
};
311+
}
312+
if (command === "/tmp/crabbox" && args[0] === "run") {
313+
throw new Error("remote Slack QA failed");
314+
}
315+
if (command === "rsync") {
316+
const outputDir = args.at(-1);
317+
await fs.mkdir(outputDir as string, { recursive: true });
318+
if (!String(outputDir).endsWith("slack-qa/")) {
319+
await fs.writeFile(path.join(outputDir as string, "slack-desktop-smoke.png"), "png");
320+
await fs.writeFile(path.join(outputDir as string, "remote-metadata.json"), "{}\n");
321+
}
322+
}
323+
return { stdout: "", stderr: "" };
324+
});
325+
326+
const result = await runMantisSlackDesktopSmoke({
327+
commandRunner: runner,
328+
crabboxBin: "/tmp/crabbox",
329+
keepLease: false,
330+
outputDir: ".artifacts/qa-e2e/mantis/slack-desktop-created-fail",
331+
repoRoot,
332+
});
333+
334+
expect(result.status).toBe("fail");
335+
expect(commands).toEqual(
336+
expect.arrayContaining([
337+
expect.objectContaining({
338+
args: ["stop", "--provider", "hetzner", "cbx_fade123"],
339+
command: "/tmp/crabbox",
340+
}),
341+
]),
342+
);
343+
});
344+
277345
it("passes gateway setup when Crabbox returns non-zero after remote metadata proves success", async () => {
278346
const runner = vi.fn(async (command: string, args: readonly string[]) => {
279347
if (command === "/tmp/crabbox" && args[0] === "warmup") {

extensions/qa-lab/src/mantis/slack-desktop-smoke.runtime.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -822,14 +822,6 @@ export async function runMantisSlackDesktopSmoke(
822822
let remoteMetadata: SlackDesktopRemoteMetadata | undefined;
823823

824824
try {
825-
const preparedCredentialEnv = await prepareGatewayCredentialEnv({
826-
credentialRole,
827-
credentialSource,
828-
env,
829-
gatewaySetup,
830-
});
831-
credentialLease = preparedCredentialEnv.credentialLease;
832-
leaseHeartbeat = preparedCredentialEnv.leaseHeartbeat;
833825
leaseId =
834826
leaseId ??
835827
(await warmupCrabbox({
@@ -850,6 +842,14 @@ export async function runMantisSlackDesktopSmoke(
850842
provider,
851843
runner,
852844
});
845+
const preparedCredentialEnv = await prepareGatewayCredentialEnv({
846+
credentialRole,
847+
credentialSource,
848+
env,
849+
gatewaySetup,
850+
});
851+
credentialLease = preparedCredentialEnv.credentialLease;
852+
leaseHeartbeat = preparedCredentialEnv.leaseHeartbeat;
853853
let remoteRunError: unknown;
854854
await runCommand({
855855
command: crabboxBin,
@@ -989,7 +989,7 @@ export async function runMantisSlackDesktopSmoke(
989989
await fs.writeFile(summaryPath, `${JSON.stringify(summary, null, 2)}\n`, "utf8");
990990
await fs.writeFile(reportPath, renderReport(summary), "utf8");
991991
}
992-
if (summary?.status === "pass" && createdLease && leaseId && !keepLease) {
992+
if (createdLease && leaseId && !keepLease) {
993993
await stopCrabbox({ crabboxBin, cwd: repoRoot, env, leaseId, provider, runner });
994994
}
995995
if (leaseHeartbeat) {

0 commit comments

Comments
 (0)