fix: pin GitHub Actions to commit SHAs, version-lock CI tools (#2983)

* fix: pin all GitHub Actions to commit SHAs and version-lock tools

Addresses supply chain hardening findings from issue #2982:

- Pin all 6 GitHub Actions to full commit SHAs with version comments:
  - actions/checkout@v4 → SHA 34e1148...
  - oven-sh/setup-bun@v2 → SHA 0c5077e...
  - actions/github-script@v7 → SHA f28e40c...
  - docker/login-action@v3 → SHA c94ce9f...
  - docker/build-push-action@v6 → SHA 10e90e3...
  - hashicorp/setup-packer@main → SHA c3d53c5... (v3.2.0)
- Pin Packer version: latest → 1.15.0 (in packer-snapshots.yml)
- Pin bun version: latest → 1.3.11 (in agent-tarballs.yml)
- Pin shellcheck: replace apt-get (no version) with pinned download
  of v0.10.0 from GitHub releases with SHA256 integrity check

These changes eliminate the primary LiteLLM-style attack vector:
a compromised action maintainer can no longer force-push malicious
code to an existing tag and have it run in CI.

Fixes #2982

Agent: issue-fixer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: exclude import aliases from no-type-assertion lint rule

The `JsNamedImportSpecifier` exclusion prevents `import { foo as bar }`
patterns from being flagged as type assertions. Previously, any `as`
keyword in import/export statements triggered the ban because the GritQL
pattern `$value as $type` matched import specifiers as well as actual
TypeScript type assertions.

This also removes the `as _foo` import aliases in the script-failure-guidance
test file (replaced with direct imports + distinctly-named wrapper functions)
which were the original manifestation of this bug.

All 1944 tests pass. Biome check clean across 169 files.

Agent: issue-fixer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
A 2026-03-25 10:27:58 -07:00 committed by GitHub
parent d161458c13
commit 76bdaf2042
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 87 additions and 84 deletions

View file

@ -20,7 +20,7 @@ jobs:
outputs:
agents: ${{ steps.set-matrix.outputs.agents }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- id: set-matrix
env:
@ -58,12 +58,12 @@ jobs:
- agent: claude
arch: arm64
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Install Bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: latest
bun-version: "1.3.11"
- name: Install agent under /root
env:

View file

@ -22,10 +22,10 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Install dependencies and build
working-directory: packages/cli

View file

@ -22,15 +22,15 @@ jobs:
matrix:
agent: [claude, codex, openclaw, opencode, kilocode, zeroclaw, hermes, junie]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: docker/login-action@v3
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
file: sh/docker/${{ matrix.agent }}.Dockerfile

View file

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check org membership and close if external
uses: actions/github-script@v7
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |

View file

@ -13,12 +13,18 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Install ShellCheck
run: |
sudo apt-get update
sudo apt-get install -y shellcheck
# Pin shellcheck v0.10.0 for reproducible CI — verifies SHA256 before install
SHELLCHECK_VERSION="0.10.0"
SHELLCHECK_SHA256="6c881ab0698e4e6ea235245f22832860544f17ba386442fe7e9d629f8cbedf87"
TARBALL="shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz"
curl -sSL "https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/${TARBALL}" -o /tmp/${TARBALL}
echo "${SHELLCHECK_SHA256} /tmp/${TARBALL}" | sha256sum -c
tar -xJf "/tmp/${TARBALL}" -C /tmp "shellcheck-v${SHELLCHECK_VERSION}/shellcheck"
sudo mv "/tmp/shellcheck-v${SHELLCHECK_VERSION}/shellcheck" /usr/local/bin/shellcheck
- name: Run ShellCheck on all bash scripts
run: |
@ -41,10 +47,10 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Install dependencies
run: bun install
@ -58,7 +64,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Run macOS compat linter
run: bash sh/test/macos-compat.sh

View file

@ -21,7 +21,7 @@ jobs:
outputs:
include: ${{ steps.set.outputs.include }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- id: set
run: |
SINGLE_AGENT="${SINGLE_AGENT_INPUT}"
@ -48,7 +48,7 @@ jobs:
matrix:
include: ${{ fromJson(needs.matrix.outputs.include) }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Read agent config
id: config
@ -61,9 +61,9 @@ jobs:
AGENT_NAME: ${{ matrix.agent }}
- name: Setup Packer
uses: hashicorp/setup-packer@main
uses: hashicorp/setup-packer@c3d53c525d422944e50ee27b840746d6522b08de # v3.2.0
with:
version: latest
version: "1.15.0"
- name: Init Packer plugins
run: packer init packer/digitalocean.pkr.hcl

View file

@ -15,10 +15,10 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Install dependencies
run: bun install
@ -32,10 +32,10 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Install dependencies
run: bun install

View file

@ -2,5 +2,6 @@ language js(typescript)
`$value as $type` as $expr where {
! $expr <: `$_ as const`,
! $expr <: JsNamedImportSpecifier(),
register_diagnostic(span=$expr, message="Type assertions (`as`) are banned. Use schema validation (parseJsonWith), type guards, or `satisfies` instead.", severity="error")
}

View file

@ -1,9 +1,5 @@
import { describe, expect, it } from "bun:test";
import {
getScriptFailureGuidance as _getScriptFailureGuidance,
getSignalGuidance as _getSignalGuidance,
buildRetryCommand,
} from "../commands/index.js";
import { buildRetryCommand, getScriptFailureGuidance, getSignalGuidance } from "../commands/index.js";
/** Strip ANSI escape codes from a string so assertions work regardless of color support. */
function stripAnsi(s: string): string {
@ -11,17 +7,17 @@ function stripAnsi(s: string): string {
}
/** Wrapper that strips ANSI codes from all returned lines. */
function getScriptFailureGuidance(...args: Parameters<typeof _getScriptFailureGuidance>): string[] {
return _getScriptFailureGuidance(...args).map(stripAnsi);
function stripped_getScriptFailureGuidance(...args: Parameters<typeof getScriptFailureGuidance>): string[] {
return getScriptFailureGuidance(...args).map(stripAnsi);
}
/** Wrapper that strips ANSI codes from all returned lines. */
function getSignalGuidance(...args: Parameters<typeof _getSignalGuidance>): string[] {
return _getSignalGuidance(...args).map(stripAnsi);
function stripped_getSignalGuidance(...args: Parameters<typeof getSignalGuidance>): string[] {
return getSignalGuidance(...args).map(stripAnsi);
}
/**
* Tests for getScriptFailureGuidance() in commands/run.ts.
* Tests for stripped_getScriptFailureGuidance() in commands/run.ts.
*
* This function maps exit codes from failed spawn scripts to user-facing
* guidance strings. It was recently modified (PRs #450, #449) but has
@ -33,7 +29,7 @@ describe("getScriptFailureGuidance", () => {
describe("exit code 127 (command not found)", () => {
it("should return guidance about missing commands with required tools and cloud name", () => {
const lines = getScriptFailureGuidance(127, "hetzner");
const lines = stripped_getScriptFailureGuidance(127, "hetzner");
const joined = lines.join("\n");
expect(lines[0]).toContain("command was not found");
expect(joined).toContain("bash");
@ -44,14 +40,14 @@ describe("getScriptFailureGuidance", () => {
});
it("should embed a different cloud name when provided", () => {
const lines = getScriptFailureGuidance(127, "vultr");
const lines = stripped_getScriptFailureGuidance(127, "vultr");
const joined = lines.join("\n");
expect(joined).toContain("spawn vultr");
expect(joined).not.toContain("spawn hetzner");
});
it("should return exactly 3 guidance lines", () => {
const lines = getScriptFailureGuidance(127, "sprite");
const lines = stripped_getScriptFailureGuidance(127, "sprite");
expect(lines).toHaveLength(3);
});
});
@ -60,7 +56,7 @@ describe("getScriptFailureGuidance", () => {
describe("exit code 126 (permission denied)", () => {
it("should mention permission denied, causes, issue link, and return 4 lines", () => {
const lines = getScriptFailureGuidance(126, "sprite");
const lines = stripped_getScriptFailureGuidance(126, "sprite");
const joined = lines.join("\n");
expect(joined).toContain("permission denied");
expect(joined).toContain("could not be executed");
@ -76,7 +72,7 @@ describe("getScriptFailureGuidance", () => {
describe("exit code 1 (generic failure)", () => {
it("should start with Common causes, mention credentials, and reference cloud name", () => {
const lines = getScriptFailureGuidance(1, "digital-ocean");
const lines = stripped_getScriptFailureGuidance(1, "digital-ocean");
const joined = lines.join("\n");
expect(lines[0]).toBe("Common causes:");
expect(joined).toContain("credentials");
@ -84,7 +80,7 @@ describe("getScriptFailureGuidance", () => {
});
it("should mention API error causes, provisioning failure, and return at least 4 lines", () => {
const lines = getScriptFailureGuidance(1, "sprite");
const lines = stripped_getScriptFailureGuidance(1, "sprite");
const joined = lines.join("\n");
expect(joined).toContain("API error");
expect(joined).toContain("quota");
@ -97,7 +93,7 @@ describe("getScriptFailureGuidance", () => {
describe("default case (unknown exit codes)", () => {
it("should return common causes with credentials, rate limits, dependencies, and cloud name", () => {
const lines = getScriptFailureGuidance(42, "linode");
const lines = stripped_getScriptFailureGuidance(42, "linode");
const joined = lines.join("\n");
expect(lines[0]).toBe("Common causes:");
expect(joined).toContain("credentials");
@ -115,7 +111,7 @@ describe("getScriptFailureGuidance", () => {
describe("null exit code", () => {
it("should fall through to default case with credentials and cloud name", () => {
const lines = getScriptFailureGuidance(null, "sprite");
const lines = stripped_getScriptFailureGuidance(null, "sprite");
const joined = lines.join("\n");
expect(lines[0]).toBe("Common causes:");
expect(joined).toContain("credentials");
@ -128,7 +124,7 @@ describe("getScriptFailureGuidance", () => {
describe("exit code 130 (user interrupt)", () => {
it("should mention Ctrl+C, interruption, orphaned server warning, and return 3 lines", () => {
const lines = getScriptFailureGuidance(130, "sprite");
const lines = stripped_getScriptFailureGuidance(130, "sprite");
const joined = lines.join("\n");
expect(joined).toContain("Ctrl+C");
expect(joined).toContain("interrupted");
@ -142,7 +138,7 @@ describe("getScriptFailureGuidance", () => {
describe("exit code 137 (killed)", () => {
it("should mention killed, timeout/OOM, larger instance suggestion, and return 4 lines", () => {
const lines = getScriptFailureGuidance(137, "sprite");
const lines = stripped_getScriptFailureGuidance(137, "sprite");
const joined = lines.join("\n");
expect(joined).toContain("killed");
expect(joined).toContain("timeout");
@ -157,7 +153,7 @@ describe("getScriptFailureGuidance", () => {
describe("exit code 255 (SSH failure)", () => {
it("should mention SSH failure, booting, firewall, termination, and return 4 lines", () => {
const lines = getScriptFailureGuidance(255, "sprite");
const lines = stripped_getScriptFailureGuidance(255, "sprite");
const joined = lines.join("\n");
expect(joined).toContain("SSH connection failed");
expect(joined).toContain("still booting");
@ -172,7 +168,7 @@ describe("getScriptFailureGuidance", () => {
describe("exit code 2 (shell syntax error)", () => {
it("should mention syntax error, bug report link, and return 2 lines", () => {
const lines = getScriptFailureGuidance(2, "sprite");
const lines = stripped_getScriptFailureGuidance(2, "sprite");
const joined = lines.join("\n");
expect(joined).toContain("Shell syntax or argument error");
expect(joined).toContain("bug in the script");
@ -190,7 +186,7 @@ describe("getScriptFailureGuidance", () => {
const savedHC = process.env.HCLOUD_TOKEN;
delete process.env.OPENROUTER_API_KEY;
delete process.env.HCLOUD_TOKEN;
const lines = getScriptFailureGuidance(1, "hetzner", "HCLOUD_TOKEN");
const lines = stripped_getScriptFailureGuidance(1, "hetzner", "HCLOUD_TOKEN");
const joined = lines.join("\n");
expect(joined).toContain("HCLOUD_TOKEN");
expect(joined).toContain("OPENROUTER_API_KEY");
@ -205,7 +201,7 @@ describe("getScriptFailureGuidance", () => {
});
it("should show generic setup hint for exit code 1 when no authHint", () => {
const lines = getScriptFailureGuidance(1, "hetzner");
const lines = stripped_getScriptFailureGuidance(1, "hetzner");
const joined = lines.join("\n");
expect(joined).toContain("spawn hetzner");
expect(joined).not.toContain("HCLOUD_TOKEN");
@ -216,7 +212,7 @@ describe("getScriptFailureGuidance", () => {
const savedDO = process.env.DO_API_TOKEN;
delete process.env.OPENROUTER_API_KEY;
delete process.env.DO_API_TOKEN;
const lines = getScriptFailureGuidance(42, "digitalocean", "DO_API_TOKEN");
const lines = stripped_getScriptFailureGuidance(42, "digitalocean", "DO_API_TOKEN");
const joined = lines.join("\n");
expect(joined).toContain("DO_API_TOKEN");
expect(joined).toContain("OPENROUTER_API_KEY");
@ -231,14 +227,14 @@ describe("getScriptFailureGuidance", () => {
});
it("should show generic setup hint for default case when no authHint", () => {
const lines = getScriptFailureGuidance(42, "digitalocean");
const lines = stripped_getScriptFailureGuidance(42, "digitalocean");
const joined = lines.join("\n");
expect(joined).toContain("spawn digitalocean");
expect(joined).not.toContain("DO_API_TOKEN");
});
it("should handle multi-credential auth hint", () => {
const lines = getScriptFailureGuidance(1, "contabo", "CONTABO_CLIENT_ID + CONTABO_CLIENT_SECRET");
const lines = stripped_getScriptFailureGuidance(1, "contabo", "CONTABO_CLIENT_ID + CONTABO_CLIENT_SECRET");
const joined = lines.join("\n");
// Each credential var should be listed individually
expect(joined).toContain("CONTABO_CLIENT_ID");
@ -246,19 +242,19 @@ describe("getScriptFailureGuidance", () => {
});
it("should not affect non-credential exit codes (130, 137, etc.)", () => {
const lines130 = getScriptFailureGuidance(130, "hetzner", "HCLOUD_TOKEN");
const lines130 = stripped_getScriptFailureGuidance(130, "hetzner", "HCLOUD_TOKEN");
const joined130 = lines130.join("\n");
expect(joined130).not.toContain("HCLOUD_TOKEN");
expect(joined130).toContain("Ctrl+C");
const lines255 = getScriptFailureGuidance(255, "hetzner", "HCLOUD_TOKEN");
const lines255 = stripped_getScriptFailureGuidance(255, "hetzner", "HCLOUD_TOKEN");
const joined255 = lines255.join("\n");
expect(joined255).not.toContain("HCLOUD_TOKEN");
expect(joined255).toContain("SSH");
});
it("should include setup instruction line for exit code 1 with authHint", () => {
const lines = getScriptFailureGuidance(1, "hetzner", "HCLOUD_TOKEN");
const lines = stripped_getScriptFailureGuidance(1, "hetzner", "HCLOUD_TOKEN");
expect(lines.length).toBeGreaterThanOrEqual(5);
const joined = lines.join("\n");
expect(joined).toContain("spawn hetzner");
@ -266,7 +262,7 @@ describe("getScriptFailureGuidance", () => {
});
it("should include setup instruction line for default case with authHint", () => {
const lines = getScriptFailureGuidance(42, "hetzner", "HCLOUD_TOKEN");
const lines = stripped_getScriptFailureGuidance(42, "hetzner", "HCLOUD_TOKEN");
expect(lines.length).toBeGreaterThanOrEqual(5);
const joined = lines.join("\n");
expect(joined).toContain("spawn hetzner");
@ -278,23 +274,23 @@ describe("getScriptFailureGuidance", () => {
describe("edge cases", () => {
it("should handle exit code 0 as default case", () => {
const lines = getScriptFailureGuidance(0, "sprite");
const lines = stripped_getScriptFailureGuidance(0, "sprite");
expect(lines[0]).toBe("Common causes:");
});
it("should handle negative exit code as default case", () => {
const lines = getScriptFailureGuidance(-1, "hetzner");
const lines = stripped_getScriptFailureGuidance(-1, "hetzner");
expect(lines[0]).toBe("Common causes:");
});
it("should handle empty cloud name", () => {
const lines = getScriptFailureGuidance(127, "");
const lines = stripped_getScriptFailureGuidance(127, "");
const joined = lines.join("\n");
expect(joined).toContain("spawn ");
});
it("should handle cloud name with special characters", () => {
const lines = getScriptFailureGuidance(1, "digital-ocean");
const lines = stripped_getScriptFailureGuidance(1, "digital-ocean");
const joined = lines.join("\n");
expect(joined).toContain("spawn digital-ocean");
});
@ -304,14 +300,14 @@ describe("getScriptFailureGuidance", () => {
describe("return type and structure", () => {
it("should produce different output for each handled exit code", () => {
const result130 = getScriptFailureGuidance(130, "sprite");
const result137 = getScriptFailureGuidance(137, "sprite");
const result255 = getScriptFailureGuidance(255, "sprite");
const result127 = getScriptFailureGuidance(127, "sprite");
const result126 = getScriptFailureGuidance(126, "sprite");
const result2 = getScriptFailureGuidance(2, "sprite");
const result1 = getScriptFailureGuidance(1, "sprite");
const resultDefault = getScriptFailureGuidance(42, "sprite");
const result130 = stripped_getScriptFailureGuidance(130, "sprite");
const result137 = stripped_getScriptFailureGuidance(137, "sprite");
const result255 = stripped_getScriptFailureGuidance(255, "sprite");
const result127 = stripped_getScriptFailureGuidance(127, "sprite");
const result126 = stripped_getScriptFailureGuidance(126, "sprite");
const result2 = stripped_getScriptFailureGuidance(2, "sprite");
const result1 = stripped_getScriptFailureGuidance(1, "sprite");
const resultDefault = stripped_getScriptFailureGuidance(42, "sprite");
const all = [
result130,
@ -336,7 +332,7 @@ describe("getScriptFailureGuidance", () => {
describe("getSignalGuidance", () => {
describe("SIGKILL", () => {
it("should mention OOM killer, larger instance size, and cloud provider dashboard", () => {
const lines = getSignalGuidance("SIGKILL");
const lines = stripped_getSignalGuidance("SIGKILL");
const joined = lines.join("\n");
expect(joined).toContain("SIGKILL");
expect(joined).toContain("Out of memory");
@ -347,7 +343,7 @@ describe("getSignalGuidance", () => {
describe("SIGTERM", () => {
it("should mention process was terminated and server shutdown", () => {
const lines = getSignalGuidance("SIGTERM");
const lines = stripped_getSignalGuidance("SIGTERM");
const joined = lines.join("\n");
expect(joined).toContain("terminated");
expect(joined).toContain("SIGTERM");
@ -357,7 +353,7 @@ describe("getSignalGuidance", () => {
describe("SIGINT", () => {
it("should mention Ctrl+C and warn about orphaned servers", () => {
const lines = getSignalGuidance("SIGINT");
const lines = stripped_getSignalGuidance("SIGINT");
const joined = lines.join("\n");
expect(joined).toContain("Ctrl+C");
expect(joined).toContain("cloud provider dashboard");
@ -366,7 +362,7 @@ describe("getSignalGuidance", () => {
describe("SIGHUP", () => {
it("should mention terminal disconnection and suggest tmux/screen", () => {
const lines = getSignalGuidance("SIGHUP");
const lines = stripped_getSignalGuidance("SIGHUP");
const joined = lines.join("\n");
expect(joined).toContain("terminal connection");
expect(joined).toContain("SIGHUP");
@ -376,7 +372,7 @@ describe("getSignalGuidance", () => {
describe("unknown signal", () => {
it("should show the signal name for unknown signals", () => {
const lines = getSignalGuidance("SIGUSR1");
const lines = stripped_getSignalGuidance("SIGUSR1");
const joined = lines.join("\n");
expect(joined).toContain("SIGUSR1");
});
@ -384,10 +380,10 @@ describe("getSignalGuidance", () => {
describe("signal output uniqueness", () => {
it("should produce different output for each handled signal", () => {
const sigkill = getSignalGuidance("SIGKILL").join("\n");
const sigterm = getSignalGuidance("SIGTERM").join("\n");
const sigint = getSignalGuidance("SIGINT").join("\n");
const sighup = getSignalGuidance("SIGHUP").join("\n");
const sigkill = stripped_getSignalGuidance("SIGKILL").join("\n");
const sigterm = stripped_getSignalGuidance("SIGTERM").join("\n");
const sigint = stripped_getSignalGuidance("SIGINT").join("\n");
const sighup = stripped_getSignalGuidance("SIGHUP").join("\n");
expect(sigkill).not.toBe(sigterm);
expect(sigterm).not.toBe(sigint);
expect(sigint).not.toBe(sighup);
@ -489,7 +485,7 @@ describe("buildRetryCommand", () => {
describe("dashboard URL in guidance", () => {
describe("getScriptFailureGuidance with dashboardUrl", () => {
it("should include dashboard URL for exit code 1 when provided", () => {
const lines = getScriptFailureGuidance(1, "hetzner", undefined, "https://console.hetzner.cloud/");
const lines = stripped_getScriptFailureGuidance(1, "hetzner", undefined, "https://console.hetzner.cloud/");
const joined = lines.join("\n");
expect(joined).toContain("https://console.hetzner.cloud/");
expect(joined).toContain("dashboard");
@ -520,13 +516,13 @@ describe("dashboard URL in guidance", () => {
],
];
for (const [code, cloud, url] of cases) {
const joined = getScriptFailureGuidance(code, cloud, undefined, url).join("\n");
const joined = stripped_getScriptFailureGuidance(code, cloud, undefined, url).join("\n");
expect(joined, `exit code ${code}`).toContain(url);
}
});
it("should fall back to generic message when no dashboardUrl", () => {
const lines = getScriptFailureGuidance(130, "sprite");
const lines = stripped_getScriptFailureGuidance(130, "sprite");
const joined = lines.join("\n");
expect(joined).toContain("cloud provider dashboard");
expect(joined).not.toContain("https://");
@ -539,7 +535,7 @@ describe("dashboard URL in guidance", () => {
255,
2,
]) {
const lines = getScriptFailureGuidance(code, "hetzner", undefined, "https://console.hetzner.cloud/");
const lines = stripped_getScriptFailureGuidance(code, "hetzner", undefined, "https://console.hetzner.cloud/");
const joined = lines.join("\n");
expect(joined).not.toContain("https://console.hetzner.cloud/");
}
@ -548,7 +544,7 @@ describe("dashboard URL in guidance", () => {
describe("getSignalGuidance with dashboardUrl", () => {
it("should include dashboard URL for SIGKILL when provided", () => {
const lines = getSignalGuidance("SIGKILL", "https://console.hetzner.cloud/");
const lines = stripped_getSignalGuidance("SIGKILL", "https://console.hetzner.cloud/");
const joined = lines.join("\n");
expect(joined).toContain("https://console.hetzner.cloud/");
expect(joined).toContain("dashboard");
@ -565,20 +561,20 @@ describe("dashboard URL in guidance", () => {
"https://cloud.digitalocean.com/",
],
] as const) {
const joined = getSignalGuidance(signal, url).join("\n");
const joined = stripped_getSignalGuidance(signal, url).join("\n");
expect(joined, signal).toContain(url);
}
});
it("should fall back to generic message when no dashboardUrl", () => {
const lines = getSignalGuidance("SIGKILL");
const lines = stripped_getSignalGuidance("SIGKILL");
const joined = lines.join("\n");
expect(joined).toContain("cloud provider dashboard");
expect(joined).not.toContain("https://");
});
it("should not add dashboard URL for SIGHUP", () => {
const lines = getSignalGuidance("SIGHUP", "https://example.com");
const lines = stripped_getSignalGuidance("SIGHUP", "https://example.com");
const joined = lines.join("\n");
expect(joined).not.toContain("https://example.com");
});