fix: avoid early Slack credential leases in Mantis

This commit is contained in:
Peter Steinberger
2026-05-05 12:50:44 +01:00
parent 70d92b5e59
commit 0c977cd687
3 changed files with 89 additions and 9 deletions

View File

@@ -229,6 +229,7 @@ jobs:
keep_args=(--no-keep-lease)
fi
set +e
pnpm openclaw qa mantis slack-desktop-smoke \
--repo-root "$candidate_repo" \
--output-dir "$output_rel" \
@@ -245,6 +246,13 @@ jobs:
--fast \
--scenario "$SCENARIO_ID" \
"${keep_args[@]}"
mantis_exit=$?
set -e
if [[ ! -f "$root/mantis-slack-desktop-smoke-summary.json" ]]; then
echo "Mantis Slack desktop smoke did not produce a summary." >&2
exit "$mantis_exit"
fi
if [[ -f "$root/slack-desktop-smoke.mp4" ]]; then
if ! command -v ffmpeg >/dev/null 2>&1 || ! command -v ffprobe >/dev/null 2>&1; then
@@ -296,6 +304,10 @@ jobs:
echo "Slack desktop smoke failed." >&2
exit 1
fi
if [[ "$mantis_exit" -ne 0 ]]; then
echo "Slack desktop smoke exited with $mantis_exit after reporting status $status." >&2
exit "$mantis_exit"
fi
- name: Upload Mantis Slack desktop artifacts
id: upload_artifact

View File

@@ -157,9 +157,11 @@ describe("mantis Slack desktop smoke runtime", () => {
it("leases Convex Slack credentials for gateway setup and maps them into the VM env", async () => {
const commands: { args: readonly string[]; command: string; env?: NodeJS.ProcessEnv }[] = [];
const events: string[] = [];
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
const url = describeFetchInput(input);
if (url.endsWith("/acquire")) {
events.push("acquire");
return new Response(
JSON.stringify({
credentialId: "cred-slack",
@@ -177,6 +179,7 @@ describe("mantis Slack desktop smoke runtime", () => {
);
}
if (url.endsWith("/release") || url.endsWith("/heartbeat")) {
events.push(url.endsWith("/release") ? "release" : "heartbeat");
return new Response(JSON.stringify({ status: "ok" }), { status: 200 });
}
throw new Error(`unexpected fetch: ${url} ${describeFetchBody(init?.body)}`);
@@ -186,6 +189,7 @@ describe("mantis Slack desktop smoke runtime", () => {
const runner = vi.fn(
async (command: string, args: readonly string[], options: { env?: NodeJS.ProcessEnv }) => {
commands.push({ command, args, env: options.env });
events.push(`${command}:${args[0]}`);
if (command === "/tmp/crabbox" && args[0] === "warmup") {
return { stdout: "ready lease cbx_c0ffee\n", stderr: "" };
}
@@ -244,6 +248,17 @@ describe("mantis Slack desktop smoke runtime", () => {
});
expect(result.status).toBe("pass");
expect(events).toEqual(
expect.arrayContaining([
"/tmp/crabbox:warmup",
"/tmp/crabbox:inspect",
"acquire",
"/tmp/crabbox:run",
"release",
]),
);
expect(events.indexOf("acquire")).toBeGreaterThan(events.indexOf("/tmp/crabbox:inspect"));
expect(events.indexOf("acquire")).toBeLessThan(events.indexOf("/tmp/crabbox:run"));
const runCommand = commands.find(
(entry) => entry.command === "/tmp/crabbox" && entry.args[0] === "run",
);
@@ -274,6 +289,59 @@ describe("mantis Slack desktop smoke runtime", () => {
expect(summary.slackUrl).toBe("https://app.slack.com/client/TLEASED/CLEASED");
});
it("stops a created no-keep lease when the remote Slack QA run fails", async () => {
const commands: { args: readonly string[]; command: string }[] = [];
const runner = vi.fn(async (command: string, args: readonly string[]) => {
commands.push({ command, args });
if (command === "/tmp/crabbox" && args[0] === "warmup") {
return { stdout: "ready lease cbx_fade123\n", stderr: "" };
}
if (command === "/tmp/crabbox" && args[0] === "inspect") {
return {
stdout: `${JSON.stringify({
host: "203.0.113.10",
id: "cbx_fade123",
provider: "hetzner",
sshKey: "/tmp/key",
sshPort: "2222",
sshUser: "crabbox",
})}\n`,
stderr: "",
};
}
if (command === "/tmp/crabbox" && args[0] === "run") {
throw new Error("remote Slack QA failed");
}
if (command === "rsync") {
const outputDir = args.at(-1);
await fs.mkdir(outputDir as string, { recursive: true });
if (!String(outputDir).endsWith("slack-qa/")) {
await fs.writeFile(path.join(outputDir as string, "slack-desktop-smoke.png"), "png");
await fs.writeFile(path.join(outputDir as string, "remote-metadata.json"), "{}\n");
}
}
return { stdout: "", stderr: "" };
});
const result = await runMantisSlackDesktopSmoke({
commandRunner: runner,
crabboxBin: "/tmp/crabbox",
keepLease: false,
outputDir: ".artifacts/qa-e2e/mantis/slack-desktop-created-fail",
repoRoot,
});
expect(result.status).toBe("fail");
expect(commands).toEqual(
expect.arrayContaining([
expect.objectContaining({
args: ["stop", "--provider", "hetzner", "cbx_fade123"],
command: "/tmp/crabbox",
}),
]),
);
});
it("passes gateway setup when Crabbox returns non-zero after remote metadata proves success", async () => {
const runner = vi.fn(async (command: string, args: readonly string[]) => {
if (command === "/tmp/crabbox" && args[0] === "warmup") {

View File

@@ -822,14 +822,6 @@ export async function runMantisSlackDesktopSmoke(
let remoteMetadata: SlackDesktopRemoteMetadata | undefined;
try {
const preparedCredentialEnv = await prepareGatewayCredentialEnv({
credentialRole,
credentialSource,
env,
gatewaySetup,
});
credentialLease = preparedCredentialEnv.credentialLease;
leaseHeartbeat = preparedCredentialEnv.leaseHeartbeat;
leaseId =
leaseId ??
(await warmupCrabbox({
@@ -850,6 +842,14 @@ export async function runMantisSlackDesktopSmoke(
provider,
runner,
});
const preparedCredentialEnv = await prepareGatewayCredentialEnv({
credentialRole,
credentialSource,
env,
gatewaySetup,
});
credentialLease = preparedCredentialEnv.credentialLease;
leaseHeartbeat = preparedCredentialEnv.leaseHeartbeat;
let remoteRunError: unknown;
await runCommand({
command: crabboxBin,
@@ -989,7 +989,7 @@ export async function runMantisSlackDesktopSmoke(
await fs.writeFile(summaryPath, `${JSON.stringify(summary, null, 2)}\n`, "utf8");
await fs.writeFile(reportPath, renderReport(summary), "utf8");
}
if (summary?.status === "pass" && createdLease && leaseId && !keepLease) {
if (createdLease && leaseId && !keepLease) {
await stopCrabbox({ crabboxBin, cwd: repoRoot, env, leaseId, provider, runner });
}
if (leaseHeartbeat) {