Repository navigation
darwin-ci: address review of the tart host scripts #37771
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,8 @@ | ||
| import { $ } from "bun"; | ||
| import { unlinkSync, writeFileSync } from "node:fs"; | ||
| import { join } from "node:path"; | ||
| import { config } from "../lib/config"; | ||
| import { guest } from "../lib/guest"; | ||
| import { fail } from "../lib/shell"; | ||
| import { fail, runInherit, shellQuote } from "../lib/shell"; | ||
| import { tart } from "../lib/tart"; | ||
|
|
||
| const env = process.env; | ||
|
|
@@ -26,14 +25,16 @@ const hostOnlyEnv = new Set([ | |
|
|
||
| if (!command.includes("runner.node.mjs")) { | ||
| console.log("--- running on host (not a test step)"); | ||
| process.exit((await $`bash -c ${command}`.nothrow()).exitCode); | ||
| // same flags buildkite-agent's own command runner uses, so a failing middle line still fails the step | ||
| process.exit(await runInherit(["bash", "-e", "-o", "pipefail", "-c", command], { cwd: checkout })); | ||
| } | ||
|
|
||
| let cleaned = false; | ||
| async function cleanup(): Promise<void> { | ||
| if (cleaned) return; | ||
| cleaned = true; | ||
| await tart.destroy(vm); | ||
| // pre-exit destroys it again and reaps whatever this misses, so a failure here must not override the test status | ||
| await tart.destroy(vm).catch((error: unknown) => console.error(`failed to destroy ${vm}: ${error}`)); | ||
| } | ||
| for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"] as const) { | ||
| process.on(signal, () => cleanup().finally(() => process.exit(130))); | ||
|
|
@@ -61,10 +62,11 @@ async function runInGuest(): Promise<number> { | |
| console.log(`+++ :test_tube: ${command}`); | ||
| const socket = "/tmp/bk-job-api.sock"; | ||
| const forward = ["-o", "StreamLocalBindUnlink=yes", "-R", `${socket}:${env.BUILDKITE_AGENT_JOB_API_SOCKET}`]; | ||
| const status = await g.run( | ||
| `BUILDKITE_AGENT_JOB_API_SOCKET=${socket} BUILDKITE_AGENT_JOB_API_TOKEN=${env.BUILDKITE_AGENT_JOB_API_TOKEN ?? ""} /bin/bash ~/job.sh`, | ||
| forward, | ||
| ); | ||
| const jobEnv = [ | ||
| `BUILDKITE_AGENT_JOB_API_SOCKET=${socket}`, | ||
| `BUILDKITE_AGENT_JOB_API_TOKEN=${shellQuote(env.BUILDKITE_AGENT_JOB_API_TOKEN ?? "")}`, | ||
| ]; | ||
| const status = await g.run(`${jobEnv.join(" ")} /bin/bash ~/job.sh`, forward); | ||
|
|
||
| console.log("--- :outbox_tray: collect reports"); | ||
| await g.collectReports("work", checkout); | ||
|
|
@@ -87,9 +89,12 @@ async function retryBoot(): Promise<string | undefined> { | |
| async function pushEnv(g: ReturnType<typeof guest>): Promise<void> { | ||
| const lines = Object.entries(env) | ||
| .filter(([key]) => /^(BUILDKITE|BUN_|EXPECTED_PLATFORM_)|^(CI|ASAN_OPTIONS)$/.test(key) && !hostOnlyEnv.has(key)) | ||
| .map(([key, value]) => `${key}=${$.escape(String(value ?? ""))}`); | ||
| .map(([key, value]) => `${key}=${shellQuote(value ?? "")}`); | ||
| const file = `/tmp/${vm}.env`; | ||
| writeFileSync(file, lines.join("\n") + "\n"); | ||
| await g.push(file, "job.env"); | ||
| unlinkSync(file); | ||
| writeFileSync(file, lines.join("\n") + "\n", { mode: 0o600 }); | ||
| try { | ||
| await g.push(file, "job.env"); | ||
| } finally { | ||
| unlinkSync(file); | ||
| } | ||
|
Comment on lines
93
to
+99
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: Bun's Citations:
🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- target file ---'
sed -n '1,130p' scripts/darwin-ci/hooks/command.ts
printf '%s\n' '--- related temporary-file patterns ---'
rg -n --glob '*.ts' --glob '*.js' 'mkdtempSync|openSync\\([^\\n]*[\"'\"']wx|writeFileSync\\([^\\n]*flag|O_EXCL|O_NOFOLLOW|unlinkSync' scripts src test 2>/dev/null | head -200Repository: oven-sh/bun Length of output: 3789 🏁 Script executed: #!/bin/bash
set -eu
node - <<'JS'
const fs = require("node:fs");
const flags = fs.constants.O_CREAT | fs.constants.O_EXCL;
console.log(JSON.stringify({
wxIncludesCreate: (fs.constants.O_CREAT & flags) === fs.constants.O_CREAT,
wxIncludesExclusive: (fs.constants.O_EXCL & flags) === fs.constants.O_EXCL,
}));
let observed;
try {
try {
throw new Error("push failed");
} finally {
throw new Error("unlink failed");
}
} catch (error) {
observed = error.message;
}
console.log(JSON.stringify({finallyErrorReplacesPrimary: observed === "unlink failed"}));
JSRepository: oven-sh/bun Length of output: 239 Make the temporary environment file exclusive and cleanup-safe.
🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,32 @@ | ||
| import { $ } from "bun"; | ||
| import { readdirSync, statSync } from "node:fs"; | ||
| import { homedir } from "node:os"; | ||
| import { join } from "node:path"; | ||
| import { succeeds } from "../lib/shell"; | ||
| import { tart } from "../lib/tart"; | ||
|
|
||
| await tart.destroy(`bk-${process.env.BUILDKITE_JOB_ID ?? "none"}`); | ||
| let failed = false; | ||
| async function reap(name: string, action: () => Promise<void>): Promise<void> { | ||
| try { | ||
| await action(); | ||
| } catch (error) { | ||
| failed = true; | ||
| console.error(`pre-exit: could not remove ${name}: ${error}`); | ||
| } | ||
| } | ||
|
|
||
| const own = `bk-${process.env.BUILDKITE_JOB_ID ?? "none"}`; | ||
| await reap(own, () => tart.destroy(own)); | ||
|
Comment on lines
+17
to
+18
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Refuse destructive cleanup when
As per coding guidelines: “Distinguish empty, zero, and unset” and “Validate untrusted input before ... side effects.” 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
| // a bk-* guest with no `tart run` behind it belongs to a job that died without its cleanup running | ||
| const vms = join(homedir(), ".tart", "vms"); | ||
| const tenMinutesAgo = Date.now() - 10 * 60_000; | ||
| for (const name of readdirSync(vms).filter((name: string) => name.startsWith("bk-"))) { | ||
| const stat = statSync(join(vms, name), { throwIfNoEntry: false }); | ||
| if (!stat || stat.mtimeMs > tenMinutesAgo) continue; | ||
|
Comment on lines
21
to
25
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Handle VM discovery I/O failures explicitly.
As per coding guidelines: “Never swallow failures” and “Every error, abort, and timeout path must complete the operation.” 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| if (await succeeds($`pgrep -f ${`tart run ${name}`}`)) continue; | ||
| await tart.remove(name); | ||
| console.log(`pre-exit: reaped orphan guest ${name}`); | ||
| if (await succeeds(["pgrep", "-f", `tart run ${name}`])) continue; | ||
| await reap(name, async () => { | ||
| await tart.remove(name); | ||
| console.log(`pre-exit: reaped orphan guest ${name}`); | ||
| }); | ||
| } | ||
|
Comment on lines
+26
to
31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
root="$(pwd)"
ast-grep outline "$root/scripts/darwin-ci/lib/tart.ts" --items all
rg -n -C 8 'remove|delete|run' "$root/scripts/darwin-ci/lib/tart.ts"Repository: oven-sh/bun Length of output: 3289 🏁 Script executed: #!/bin/bash
set -euo pipefail
root="$(pwd)"
printf '%s\n' '--- pre-exit.ts ---'
cat -n "$root/scripts/darwin-ci/hooks/pre-exit.ts"
printf '%s\n' '--- shell.ts outline and relevant implementations ---'
ast-grep outline "$root/scripts/darwin-ci/lib/shell.ts" --items all
rg -n -C 12 'function (succeeds|probe|run|spawn)|export .*succeeds|succeeds|probe' "$root/scripts/darwin-ci/lib/shell.ts"
printf '%s\n' '--- pre-exit references and tart lifecycle callers ---'
rg -n -C 6 'pre-exit|reap\(|tart\.remove|tart\.destroy|pgrep' "$root/scripts/darwin-ci"Repository: oven-sh/bun Length of output: 16260 🌐 Web query:
💡 Result: On macOS, the pgrep utility follows standard BSD-style exit codes as documented in its manual page [1][2]. The exit statuses are defined as follows: 0: One or more processes were matched [1][2]. 1: No processes were matched [1][2]. 2: Invalid options were specified on the command line [1][2]. 3: An internal error occurred [1][2]. Because pgrep returns an exit status of 1 when no matching processes are found, it is common in shell scripts to handle this behavior specifically, especially when using set -e (which causes a script to exit immediately if any command returns a non-zero status) [3]. To prevent your script from exiting when no processes are matched, you can append || true or another appropriate fallback to the pgrep command [3]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
root="$(pwd)"
printf '%s\n' '--- tart delete and destroy implementation ---'
cat -n "$root/scripts/darwin-ci/lib/tart.ts" | sed -n '35,66p'
printf '%s\n' '--- read-only exit-code probe ---'
python3 - <<'PY'
import shutil
import subprocess
pgrep = shutil.which("pgrep")
if not pgrep:
print("pgrep: unavailable")
raise SystemExit(0)
cases = [
("no-match", [pgrep, "-f", "coderabbit-no-such-process-8f8c9d7b"]),
("invalid-pattern", [pgrep, "-f", "["]),
]
for label, argv in cases:
result = subprocess.run(argv, stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True)
print(f"{label}: exit={result.returncode}, stdout={result.stdout!r}, stderr={result.stderr!r}")
print(f"{label}: succeeds-equivalent={result.returncode == 0}")
PYRepository: oven-sh/bun Length of output: 1782 Fail closed on orphan-process detection.
Use 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| process.exit(failed ? 1 : 0); | ||
|
Check warning on line 32 in scripts/darwin-ci/hooks/pre-exit.ts
|
||
|
Comment on lines
+26
to
+32
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 The orphan-reap loop sets the same Extended reasoning...What changedBefore this PR,
Because both paths flip the same flag, a failure in (2) — cleanup of a VM this job never touched — makes pre-exit exit 1. Concrete triggerWith
The same happens for any other reason Why nothing prevents it
Why this is the wrong shapeREVIEW.md: "Operations the user explicitly requested fail the whole command on any failure … best-effort auxiliary steps (hints, optional deps) degrade quietly instead of aborting." Orphan reaping is textbook best-effort auxiliary work — it's cleaning up after other jobs, and there's already a nightly FixOnly let the own guest's destroy failure set for (const name of readdirSync(vms).filter(n => n.startsWith("bk-"))) {
...
try {
await tart.remove(name);
console.log(`pre-exit: reaped orphan guest ${name}`);
} catch (error) {
console.error(`pre-exit: could not remove orphan ${name}: ${error}`);
}
}(Or drop |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,20 +4,36 @@ set -euo pipefail | |||||||||||||||||||||||||||
| here=$(cd "$(dirname "$0")" && pwd) | ||||||||||||||||||||||||||||
| sudo -n true 2>/dev/null || { echo "needs passwordless sudo for $(whoami)"; exit 1; } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if [ "$(uname -m)" = arm64 ]; then prefix=/opt/homebrew; target=aarch64; else prefix=/usr/local; target=x64; fi | ||||||||||||||||||||||||||||
| # https://github.com/Homebrew/install/commits/master | ||||||||||||||||||||||||||||
| brew_installer_commit=a34ae4ee9151cbce4c3b33bca7043a972b7ae9a5 | ||||||||||||||||||||||||||||
| brew_installer_sha256=12479a24be3f5307eecac7cde670fad7118640f031229e964f544b1367b52a41 | ||||||||||||||||||||||||||||
| # https://github.com/oven-sh/bun/releases/download/bun-v$bun_version/SHASUMS256.txt | ||||||||||||||||||||||||||||
| bun_version=1.3.14 | ||||||||||||||||||||||||||||
| bun_sha256_aarch64=d8b96221828ad6f97ac7ac0ab7e95872341af763001e8803e8267652c2652620 | ||||||||||||||||||||||||||||
| bun_sha256_x64=4183df3374623e5bab315c547cfa0974533cd457d86b73b639f7a87974cd6633 | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if [ "$(uname -m)" = arm64 ]; then prefix=/opt/homebrew; target=aarch64; bun_sha256=$bun_sha256_aarch64; else prefix=/usr/local; target=x64; bun_sha256=$bun_sha256_x64; fi | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| tmp=$(mktemp -d) | ||||||||||||||||||||||||||||
| trap 'rm -rf "$tmp"' EXIT | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| fetch() { # <url> <file> <sha256> | ||||||||||||||||||||||||||||
| curl -fsSL -o "$2" "$1" | ||||||||||||||||||||||||||||
| echo "$3 $2" | shasum -a 256 -c - >/dev/null || { echo "checksum mismatch for $1"; exit 1; } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if [ ! -x "$prefix/bin/brew" ]; then | ||||||||||||||||||||||||||||
| NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | ||||||||||||||||||||||||||||
| fetch "https://raw.githubusercontent.com/Homebrew/install/$brew_installer_commit/install.sh" "$tmp/brew-install.sh" "$brew_installer_sha256" | ||||||||||||||||||||||||||||
| NONINTERACTIVE=1 /bin/bash "$tmp/brew-install.sh" | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
| eval "$("$prefix/bin/brew" shellenv)" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if [ ! -x /usr/local/bin/bun ]; then | ||||||||||||||||||||||||||||
| tmp=$(mktemp -d) | ||||||||||||||||||||||||||||
| curl -fsSL -o "$tmp/bun.zip" "https://github.com/oven-sh/bun/releases/latest/download/bun-darwin-$target.zip" | ||||||||||||||||||||||||||||
| fetch "https://github.com/oven-sh/bun/releases/download/bun-v$bun_version/bun-darwin-$target.zip" "$tmp/bun.zip" "$bun_sha256" | ||||||||||||||||||||||||||||
| unzip -q "$tmp/bun.zip" -d "$tmp" | ||||||||||||||||||||||||||||
| sudo mkdir -p /usr/local/bin | ||||||||||||||||||||||||||||
| sudo install -m 755 "$tmp/bun-darwin-$target/bun" /usr/local/bin/bun | ||||||||||||||||||||||||||||
|
Comment on lines
31
to
35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Verify the installed Bun binary, not only its existence. This condition skips the verified archive path whenever Fetch and verify the pinned archive, then compare its Proposed fix-if [ ! -x /usr/local/bin/bun ]; then
- fetch "https://github.com/oven-sh/bun/releases/download/bun-v$bun_version/bun-darwin-$target.zip" "$tmp/bun.zip" "$bun_sha256"
- unzip -q "$tmp/bun.zip" -d "$tmp"
+fetch "https://github.com/oven-sh/bun/releases/download/bun-v$bun_version/bun-darwin-$target.zip" "$tmp/bun.zip" "$bun_sha256"
+unzip -q "$tmp/bun.zip" -d "$tmp"
+if ! cmp -s "$tmp/bun-darwin-$target/bun" /usr/local/bin/bun 2>/dev/null; then
sudo mkdir -p /usr/local/bin
sudo install -m 755 "$tmp/bun-darwin-$target/bun" /usr/local/bin/bun
fi📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| rm -rf "$tmp" | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| rm -rf "$tmp"; trap - EXIT | ||||||||||||||||||||||||||||
| exec /usr/local/bin/bun "$here/main.ts" provision "$@" | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,22 @@ | ||
| import { $ } from "bun"; | ||
| import { join } from "node:path"; | ||
| import { ciUserHome, ciUserId } from "./ci-user"; | ||
| import { config, releaseTier } from "./config"; | ||
| import { config } from "./config"; | ||
| import { bootstrapCheckout } from "./host"; | ||
| import { plist } from "./launchd"; | ||
| import { consoleUser, fail, output, poll, sleep, succeeds, sudoRead, sudoWrite } from "./shell"; | ||
| import { darwinReleaseTier } from "./release-tier.mjs"; | ||
| import { | ||
| consoleUser, | ||
| fail, | ||
| poll, | ||
| probe, | ||
| run, | ||
| runInheritOrThrow, | ||
| sleep, | ||
| spawn, | ||
| succeeds, | ||
| sudoRead, | ||
| sudoWrite, | ||
| } from "./shell"; | ||
|
|
||
| const path = "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin"; | ||
| const agentLabel = "com.buildkite.buildkite-agent"; | ||
|
|
@@ -17,13 +30,14 @@ async function agentToken(): Promise<string> { | |
| ); | ||
| } | ||
|
|
||
| type TartAgentOptions = { release: number; spawn: number }; | ||
| export type TartAgentOptions = { release: number; spawn: number }; | ||
|
|
||
| export async function installTartAgent({ release, spawn }: TartAgentOptions): Promise<void> { | ||
| export async function installTartAgent({ release, spawn: workers }: TartAgentOptions): Promise<void> { | ||
| const user = config.ciUser; | ||
| if ((await consoleUser()) !== user) | ||
| fail(`${user} is not logged in at the console; run setup-user, reboot, then retry`); | ||
| const baked = await succeeds($`sudo -u ${user} -H ${config.tart.bin} get ${config.tart.image}`); | ||
| const asCiUser = ["sudo", "-u", user, "-H"]; | ||
| const baked = await succeeds([...asCiUser, config.tart.bin, "get", config.tart.image]); | ||
| if (!baked) fail(`no ${config.tart.image} image for ${user}; run bake as ${user} first`); | ||
|
|
||
| const home = await ciUserHome(); | ||
|
|
@@ -34,17 +48,17 @@ export async function installTartAgent({ release, spawn }: TartAgentOptions): Pr | |
| const hooks = join(config.installDir, "hooks"); | ||
| const owner = `${user}:staff`; | ||
|
|
||
| await $`sudo install -d -o ${user} -g staff ${builds} ${logs} ${join(home, ".buildkite-agent")}`; | ||
| await run(["sudo", "install", "-d", "-o", user, "-g", "staff", builds, logs, join(home, ".buildkite-agent")]); | ||
|
|
||
| await sudoWrite( | ||
| configPath, | ||
| [ | ||
| `token="${await agentToken()}"`, | ||
| `name="%hostname-tart-${release}-%spawn"`, | ||
| `tags="queue=${config.queue},os=darwin,arch=aarch64,distro=macOS,release=${release},release-tier=${releaseTier(release)},tart=true"`, | ||
| `tags="queue=${config.queue},os=darwin,arch=aarch64,distro=macOS,release=${release},release-tier=${darwinReleaseTier(release)},tart=true"`, | ||
| `hooks-path="${hooks}"`, | ||
| `build-path="${builds}"`, | ||
| `spawn=${spawn}`, | ||
| `spawn=${workers}`, | ||
| `cancel-grace-period=30`, | ||
| "", | ||
| ].join("\n"), | ||
|
|
@@ -71,11 +85,12 @@ export async function installTartAgent({ release, spawn }: TartAgentOptions): Pr | |
| }), | ||
| ); | ||
|
|
||
| const tartAsCiUser = `${asCiUser.join(" ")} ${config.tart.bin}`; | ||
| const nightly = [ | ||
| `PATH=${path}`, | ||
| `launchctl bootout gui/${uid}/${agentLabel}`, | ||
| "sleep 40", | ||
| `for vm in $(sudo -u ${user} tart list --source local --quiet | grep '^bk-'); do sudo -u ${user} tart delete $vm; done`, | ||
| `for vm in $(${tartAsCiUser} list --source local --quiet | grep '^bk-'); do ${tartAsCiUser} delete $vm; done`, | ||
| `rm -rf ${builds}/* /tmp/bk-*.log`, | ||
| "shutdown -r now", | ||
| ].join("; "); | ||
|
|
@@ -89,28 +104,33 @@ export async function installTartAgent({ release, spawn }: TartAgentOptions): Pr | |
| }), | ||
| ); | ||
|
|
||
| await $`sudo launchctl bootout system/buildkite-agent`.quiet().nothrow(); | ||
| await $`sudo launchctl bootout system/${cleanupLabel}`.quiet().nothrow(); | ||
| await spawn(["sudo", "launchctl", "bootout", "system/buildkite-agent"]); | ||
| await spawn(["sudo", "launchctl", "bootout", `system/${cleanupLabel}`]); | ||
| await unload(`gui/${uid}/${agentLabel}`); | ||
|
Comment on lines
+107
to
109
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Do not discard
Accept only the known absent-service result. Throw all other As per coding guidelines, “Never swallow failures or signal success after failure; propagate I/O, syscall, cleanup, and requested-operation errors explicitly.” Also applies to: 132-135 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| await $`sudo launchctl bootstrap gui/${uid} /Library/LaunchAgents/${agentLabel}.plist`; | ||
| await $`sudo launchctl bootstrap system /Library/LaunchDaemons/${cleanupLabel}.plist`; | ||
| await run(["sudo", "launchctl", "bootstrap", `gui/${uid}`, `/Library/LaunchAgents/${agentLabel}.plist`]); | ||
| await run(["sudo", "launchctl", "bootstrap", "system", `/Library/LaunchDaemons/${cleanupLabel}.plist`]); | ||
|
|
||
| await sleep(4000); | ||
| console.log(await output($`sudo tail -4 ${join(logs, "buildkite-agent.log")}`)); | ||
| console.log( | ||
| (await probe(["sudo", "tail", "-4", join(logs, "buildkite-agent.log")])) ?? "(agent log not written yet)", | ||
| ); | ||
| } | ||
|
|
||
| export async function installBareAgent(): Promise<void> { | ||
| const checkout = join(process.env.HOME!, "bun-bootstrap"); | ||
| const token = await agentToken(); | ||
| await $`sudo env BUILDKITE_AGENT_TOKEN=${token} PATH=${path} node scripts/agent.mjs install`.cwd(checkout); | ||
| await sleep(4000); | ||
| console.log( | ||
| await output($`tail -4 ${join(process.env.HOME!, "Library", "Logs", "buildkite-agent", "buildkite-agent.log")}`), | ||
| await runInheritOrThrow( | ||
| ["sudo", "env", `PATH=${path}`, `BUILDKITE_AGENT_TOKEN=${token}`, "node", "scripts/agent.mjs", "install"], | ||
| { | ||
| cwd: bootstrapCheckout, | ||
| }, | ||
| ); | ||
| await sleep(4000); | ||
| const log = join(process.env.HOME!, "Library", "Logs", "buildkite-agent", "buildkite-agent.log"); | ||
| console.log((await probe(["tail", "-4", log])) ?? "(agent log not written yet)"); | ||
| } | ||
|
|
||
| // bootout returns before a busy agent has finished its cancel grace period, and bootstrap fails with EIO until it has | ||
| async function unload(target: string): Promise<void> { | ||
| await $`sudo launchctl bootout ${target}`.quiet().nothrow(); | ||
| await poll(30, 2000, async () => ((await succeeds($`sudo launchctl print ${target}`)) ? undefined : true)); | ||
| await spawn(["sudo", "launchctl", "bootout", target]); | ||
| await poll(30, 2000, async () => ((await succeeds(["sudo", "launchctl", "print", target])) ? undefined : true)); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
Repository: oven-sh/bun
Length of output: 1812
🏁 Script executed:
Repository: oven-sh/bun
Length of output: 28404
🏁 Script executed:
Repository: oven-sh/bun
Length of output: 18786
🏁 Script executed:
Repository: oven-sh/bun
Length of output: 50367
🏁 Script executed:
Repository: oven-sh/bun
Length of output: 15986
🏁 Script executed:
Repository: oven-sh/bun
Length of output: 50369
🏁 Script executed:
Repository: oven-sh/bun
Length of output: 396
🏁 Script executed:
Repository: oven-sh/bun
Length of output: 335
Keep the API token out of SSH arguments and validate the environment file.
guest.runpasses the command toBun.spawn(["ssh", ..., command]), soshellQuotedoes not hide the token from process arguments. Add the token to the protectedjob.envtransport and reject unset or empty credentials before starting SSH.In
pushEnv, validate environment keys before writing a file thatjob.shsources. The current filter accepts keys containing newlines, which can execute injected commands. Create/tmp/${vm}.envexclusively with private permissions;{ mode: 0o600 }does not change an existing file’s mode. Preserve the original push error if cleanup fails.🤖 Prompt for AI Agents
Source: Coding guidelines