Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: "1.3.9"
bun-version: "1.4.4"

- name: Install dependencies
run: bun install --frozen-lockfile
Expand Down Expand Up @@ -48,7 +48,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: "1.3.9"
bun-version: "1.4.4"

- name: Install dependencies
run: bun install --frozen-lockfile
Expand All @@ -67,7 +67,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: "1.3.9"
bun-version: "1.4.4"
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Verify native Windows provenance
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24.11.0"
node-version: "24.22.1"
registry-url: "https://registry.npmjs.org"

- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: "1.3.9"
bun-version: "1.4.4"

- name: Install dependencies
run: bun install --frozen-lockfile
Expand Down Expand Up @@ -129,7 +129,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: "1.3.9"
bun-version: "1.4.4"

- name: Install dependencies
run: bun install --frozen-lockfile
Expand Down Expand Up @@ -275,7 +275,7 @@ jobs:
if: steps.tap-token.outputs.enabled == 'true'
uses: oven-sh/setup-bun@v1
with:
bun-version: "1.3.9"
bun-version: "1.4.4"

- name: Install dependencies
if: steps.tap-token.outputs.enabled == 'true'
Expand Down Expand Up @@ -366,13 +366,13 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24.11.0"
node-version: "24.22.1"
registry-url: "https://registry.npmjs.org"

- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: "1.3.9"
bun-version: "1.4.4"

- name: Install dependencies
run: bun install --frozen-lockfile
Expand Down
37 changes: 37 additions & 0 deletions scripts/release-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,40 @@ it("publishes npm through OIDC without a stored publish token", async () => {
)
).toBe(true);
});

it("keeps CI and release runtime pins consistent", async () => {
const versions: Record<string, string[]> = { bun: [], node: [] };
for (const name of ["ci", "release"]) {
const workflow: unknown = parseYaml(
await Bun.file(
new URL(`../.github/workflows/${name}.yml`, import.meta.url)
).text()
);
if (!(isPlainObject(workflow) && isPlainObject(workflow.jobs))) {
throw new Error("Workflow jobs are missing.");
}
for (const job of Object.values(workflow.jobs)) {
if (!(isPlainObject(job) && Array.isArray(job.steps))) {
continue;
}
for (const step of job.steps.filter(isPlainObject)) {
if (!isPlainObject(step.with)) {
continue;
}
for (const runtime of ["bun", "node"]) {
const version = step.with[`${runtime}-version`];
if (typeof version === "string") {
versions[runtime]!.push(version);
}
}
}
}
}
for (const [runtime, expected] of [
["bun", "1.4.4"],
["node", "24.22.1"],
] as const) {
expect(versions[runtime]!.length).toBeGreaterThan(0);
expect([...new Set(versions[runtime]!)]).toEqual([expected]);
}
});
176 changes: 121 additions & 55 deletions src/audit/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { basename, delimiter, dirname, join } from "node:path";
import { restoreEnvironmentVariable } from "../../test/environment";
import {
evaluateAgentAudit,
makeAgentRuntimeDir,
Expand Down Expand Up @@ -758,12 +759,18 @@ for (const tool of ["claude", "codex"] as const) {
SERVICE_TOKEN: process.env.SERVICE_TOKEN,
};
process.env.PATH = `${bin}${delimiter}${previousPath ?? ""}`;
process.env.ANTHROPIC_API_KEY =
tool === "claude" ? "fixture-environment-auth" : undefined;
process.env.CLAUDE_CODE_OAUTH_TOKEN =
tool === "claude" ? "fixture-environment-auth" : undefined;
process.env.OPENAI_API_KEY =
tool === "codex" ? "fixture-environment-auth" : undefined;
restoreEnvironmentVariable(
"ANTHROPIC_API_KEY",
tool === "claude" ? "fixture-environment-auth" : undefined
);
restoreEnvironmentVariable(
"CLAUDE_CODE_OAUTH_TOKEN",
tool === "claude" ? "fixture-environment-auth" : undefined
);
restoreEnvironmentVariable(
"OPENAI_API_KEY",
tool === "codex" ? "fixture-environment-auth" : undefined
);
process.env.ALL_PROXY = "http://fixture.invalid";
process.env.AWS_SECRET_ACCESS_KEY = "fixture-unrelated-secret";
process.env.BASH_ENV = join(dir, "fixture-hook");
Expand Down Expand Up @@ -849,10 +856,10 @@ for (const tool of ["claude", "codex"] as const) {
const profileBackup = `${authMarkers[tool]}.disabled`;
await rename(authMarkers[tool], profileBackup);
if (tool === "claude") {
process.env.ANTHROPIC_API_KEY = undefined;
process.env.CLAUDE_CODE_OAUTH_TOKEN = undefined;
restoreEnvironmentVariable("ANTHROPIC_API_KEY", undefined);
restoreEnvironmentVariable("CLAUDE_CODE_OAUTH_TOKEN", undefined);
} else {
process.env.OPENAI_API_KEY = undefined;
restoreEnvironmentVariable("OPENAI_API_KEY", undefined);
}
const nativeReport = await runAgentAudit({
argv: ["ok-skill", "--with", tool, "--max-items", "1"],
Expand All @@ -875,10 +882,10 @@ for (const tool of ["claude", "codex"] as const) {

if (phase === "profile-precondition") {
if (tool === "claude") {
process.env.ANTHROPIC_API_KEY = undefined;
process.env.CLAUDE_CODE_OAUTH_TOKEN = undefined;
restoreEnvironmentVariable("ANTHROPIC_API_KEY", undefined);
restoreEnvironmentVariable("CLAUDE_CODE_OAUTH_TOKEN", undefined);
} else {
process.env.OPENAI_API_KEY = undefined;
restoreEnvironmentVariable("OPENAI_API_KEY", undefined);
}
await expect(
runAgentAudit({
Expand Down Expand Up @@ -958,22 +965,51 @@ for (const tool of ["claude", "codex"] as const) {
expect(await readdir(scratch)).toEqual([]);
}
} finally {
process.env.PATH = previousPath;
process.env.ANTHROPIC_API_KEY = previousAnthropicApiKey;
process.env.CLAUDE_CODE_OAUTH_TOKEN = previousClaudeOauthToken;
process.env.OPENAI_API_KEY = previousOpenAiApiKey;
process.env.ALL_PROXY = unrelatedEnvironment.ALL_PROXY;
process.env.AWS_SECRET_ACCESS_KEY =
unrelatedEnvironment.AWS_SECRET_ACCESS_KEY;
process.env.BASH_ENV = unrelatedEnvironment.BASH_ENV;
process.env.DATABASE_URL = unrelatedEnvironment.DATABASE_URL;
process.env.GH_TOKEN = unrelatedEnvironment.GH_TOKEN;
process.env.GIT_CONFIG_COUNT = unrelatedEnvironment.GIT_CONFIG_COUNT;
process.env.GIT_SSH_COMMAND = unrelatedEnvironment.GIT_SSH_COMMAND;
process.env.HTTP_PROXY = unrelatedEnvironment.HTTP_PROXY;
process.env.HTTPS_PROXY = unrelatedEnvironment.HTTPS_PROXY;
process.env.NODE_OPTIONS = unrelatedEnvironment.NODE_OPTIONS;
process.env.SERVICE_TOKEN = unrelatedEnvironment.SERVICE_TOKEN;
restoreEnvironmentVariable("PATH", previousPath);
restoreEnvironmentVariable(
"ANTHROPIC_API_KEY",
previousAnthropicApiKey
);
restoreEnvironmentVariable(
"CLAUDE_CODE_OAUTH_TOKEN",
previousClaudeOauthToken
);
restoreEnvironmentVariable("OPENAI_API_KEY", previousOpenAiApiKey);
restoreEnvironmentVariable("ALL_PROXY", unrelatedEnvironment.ALL_PROXY);
restoreEnvironmentVariable(
"AWS_SECRET_ACCESS_KEY",
unrelatedEnvironment.AWS_SECRET_ACCESS_KEY
);
restoreEnvironmentVariable("BASH_ENV", unrelatedEnvironment.BASH_ENV);
restoreEnvironmentVariable(
"DATABASE_URL",
unrelatedEnvironment.DATABASE_URL
);
restoreEnvironmentVariable("GH_TOKEN", unrelatedEnvironment.GH_TOKEN);
restoreEnvironmentVariable(
"GIT_CONFIG_COUNT",
unrelatedEnvironment.GIT_CONFIG_COUNT
);
restoreEnvironmentVariable(
"GIT_SSH_COMMAND",
unrelatedEnvironment.GIT_SSH_COMMAND
);
restoreEnvironmentVariable(
"HTTP_PROXY",
unrelatedEnvironment.HTTP_PROXY
);
restoreEnvironmentVariable(
"HTTPS_PROXY",
unrelatedEnvironment.HTTPS_PROXY
);
restoreEnvironmentVariable(
"NODE_OPTIONS",
unrelatedEnvironment.NODE_OPTIONS
);
restoreEnvironmentVariable(
"SERVICE_TOKEN",
unrelatedEnvironment.SERVICE_TOKEN
);
}
});
}
Expand Down Expand Up @@ -1186,8 +1222,8 @@ beforeAll(async () => {
let evaluation: Awaited<ReturnType<typeof evaluateAgentAudit>> | undefined;
let commandError: unknown;
process.env.PATH = `${bin}${delimiter}${previousEnvironment.PATH ?? ""}`;
process.env.ANTHROPIC_API_KEY = undefined;
process.env.CLAUDE_CODE_OAUTH_TOKEN = undefined;
restoreEnvironmentVariable("ANTHROPIC_API_KEY", undefined);
restoreEnvironmentVariable("CLAUDE_CODE_OAUTH_TOKEN", undefined);
process.env.OPENAI_API_KEY = "fixture-codex-cold-start-auth";
try {
evaluation = await evaluateAgentAudit({
Expand All @@ -1202,11 +1238,19 @@ beforeAll(async () => {
commandError = error;
} finally {
clearTimeout(deadline);
process.env.PATH = previousEnvironment.PATH;
process.env.ANTHROPIC_API_KEY = previousEnvironment.ANTHROPIC_API_KEY;
process.env.CLAUDE_CODE_OAUTH_TOKEN =
previousEnvironment.CLAUDE_CODE_OAUTH_TOKEN;
process.env.OPENAI_API_KEY = previousEnvironment.OPENAI_API_KEY;
restoreEnvironmentVariable("PATH", previousEnvironment.PATH);
restoreEnvironmentVariable(
"ANTHROPIC_API_KEY",
previousEnvironment.ANTHROPIC_API_KEY
);
restoreEnvironmentVariable(
"CLAUDE_CODE_OAUTH_TOKEN",
previousEnvironment.CLAUDE_CODE_OAUTH_TOKEN
);
restoreEnvironmentVariable(
"OPENAI_API_KEY",
previousEnvironment.OPENAI_API_KEY
);
}
if (commandError) {
throw commandError;
Expand Down Expand Up @@ -1336,22 +1380,34 @@ for (const tool of ["claude", "codex"] as const) {
| undefined;
try {
process.env.PATH = `${bin}${delimiter}${previousEnvironment.PATH ?? ""}`;
process.env.ANTHROPIC_API_KEY =
tool === "claude" ? selectedSecret : undefined;
process.env.CLAUDE_CODE_OAUTH_TOKEN = undefined;
process.env.OPENAI_API_KEY =
tool === "codex" ? selectedSecret : undefined;
restoreEnvironmentVariable(
"ANTHROPIC_API_KEY",
tool === "claude" ? selectedSecret : undefined
);
restoreEnvironmentVariable("CLAUDE_CODE_OAUTH_TOKEN", undefined);
restoreEnvironmentVariable(
"OPENAI_API_KEY",
tool === "codex" ? selectedSecret : undefined
);
await Bun.write(modePath, mode);
evaluation = await evaluateMode(commandController.signal);
} catch (error) {
commandError = error;
} finally {
clearTimeout(commandDeadline);
process.env.PATH = previousEnvironment.PATH;
process.env.ANTHROPIC_API_KEY = previousEnvironment.ANTHROPIC_API_KEY;
process.env.CLAUDE_CODE_OAUTH_TOKEN =
previousEnvironment.CLAUDE_CODE_OAUTH_TOKEN;
process.env.OPENAI_API_KEY = previousEnvironment.OPENAI_API_KEY;
restoreEnvironmentVariable("PATH", previousEnvironment.PATH);
restoreEnvironmentVariable(
"ANTHROPIC_API_KEY",
previousEnvironment.ANTHROPIC_API_KEY
);
restoreEnvironmentVariable(
"CLAUDE_CODE_OAUTH_TOKEN",
previousEnvironment.CLAUDE_CODE_OAUTH_TOKEN
);
restoreEnvironmentVariable(
"OPENAI_API_KEY",
previousEnvironment.OPENAI_API_KEY
);
}

expect({
Expand Down Expand Up @@ -1449,11 +1505,15 @@ for (const tool of ["claude", "codex"] as const) {
const previousOpenAiApiKey = process.env.OPENAI_API_KEY;
const previousServiceToken = process.env.SERVICE_TOKEN;
process.env.PATH = `${bin}${delimiter}${previousPath ?? ""}`;
process.env.ANTHROPIC_API_KEY =
tool === "claude" ? selectedSecret : undefined;
process.env.CLAUDE_CODE_OAUTH_TOKEN = undefined;
process.env.OPENAI_API_KEY =
tool === "codex" ? selectedSecret : undefined;
restoreEnvironmentVariable(
"ANTHROPIC_API_KEY",
tool === "claude" ? selectedSecret : undefined
);
restoreEnvironmentVariable("CLAUDE_CODE_OAUTH_TOKEN", undefined);
restoreEnvironmentVariable(
"OPENAI_API_KEY",
tool === "codex" ? selectedSecret : undefined
);
process.env.SERVICE_TOKEN = unrelatedSecret;

const assertNoHostileValue = (value: unknown) => {
Expand Down Expand Up @@ -1549,11 +1609,17 @@ for (const tool of ["claude", "codex"] as const) {
}
expect(await readdir(scratch)).toEqual([]);
} finally {
process.env.PATH = previousPath;
process.env.ANTHROPIC_API_KEY = previousAnthropicApiKey;
process.env.CLAUDE_CODE_OAUTH_TOKEN = previousClaudeOauthToken;
process.env.OPENAI_API_KEY = previousOpenAiApiKey;
process.env.SERVICE_TOKEN = previousServiceToken;
restoreEnvironmentVariable("PATH", previousPath);
restoreEnvironmentVariable(
"ANTHROPIC_API_KEY",
previousAnthropicApiKey
);
restoreEnvironmentVariable(
"CLAUDE_CODE_OAUTH_TOKEN",
previousClaudeOauthToken
);
restoreEnvironmentVariable("OPENAI_API_KEY", previousOpenAiApiKey);
restoreEnvironmentVariable("SERVICE_TOKEN", previousServiceToken);
}
}, 15_000);
}
Expand Down
5 changes: 3 additions & 2 deletions src/cli-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { afterEach, describe, expect, it } from "bun:test";
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { restoreEnvironmentVariable } from "../test/environment";
import {
parseCliContextArgs,
resolveCliContextRoot,
Expand All @@ -21,8 +22,8 @@ afterEach(async () => {
await rm(tempRoot, { recursive: true, force: true });
}
tempRoot = null;
process.env.FACULT_ROOT_DIR = ORIGINAL_ROOT;
process.env.FACULT_ROOT_SCOPE = ORIGINAL_ROOT_SCOPE;
restoreEnvironmentVariable("FACULT_ROOT_DIR", ORIGINAL_ROOT);
restoreEnvironmentVariable("FACULT_ROOT_SCOPE", ORIGINAL_ROOT_SCOPE);
});

describe("parseCliContextArgs", () => {
Expand Down
Loading
Loading