mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-08-03 17:58:30 +08:00
Compare commits
No commits in common. "main" and "v1.0.180" have entirely different histories.
@ -145,7 +145,7 @@ runs:
|
|||||||
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
||||||
run: |
|
run: |
|
||||||
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
|
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
|
||||||
CLAUDE_CODE_VERSION="2.1.220"
|
CLAUDE_CODE_VERSION="2.1.217"
|
||||||
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
|
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
|
||||||
for attempt in 1 2 3; do
|
for attempt in 1 2 3; do
|
||||||
echo "Installation attempt $attempt..."
|
echo "Installation attempt $attempt..."
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
"name": "@anthropic-ai/claude-code-base-action",
|
"name": "@anthropic-ai/claude-code-base-action",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|
"@anthropic-ai/claude-agent-sdk": "^0.3.217",
|
||||||
"shell-quote": "^1.8.3",
|
"shell-quote": "^1.8.3",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -27,23 +27,23 @@
|
|||||||
|
|
||||||
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
|
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.3.220", "", { "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.220", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.220", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.220", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.220", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.220", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.220", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.220", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.220" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.0.0" } }, "sha512-glc7SdwPkOkLw8oxwLo9PKTdLJGqW/PIR4urWXFoRtX9YllwozsEVc5Tc1+EvLSkfrsxPJqQWqOgpjUOQXf1oA=="],
|
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.3.217", "", { "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.217", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.217", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.217", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.217", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.217", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.217", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.217", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.217" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.0.0" } }, "sha512-juszT3itL8R6OQ6nb/8IZE34UjKps8Jf7N8vjCXLx+vbJc+k3EojZOs93tJwT5iTRvfV1a0N53zJbKn/iJpKrQ=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.220", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7VxlbEosK7DODiOnsjoVd0DSJzbnaPrM2jelMHI0y8zx1UnLS3WC6EFUXbvy74F2sXqEznh2tzn7EKWInaRN6Q=="],
|
"@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.217", "", { "os": "darwin", "cpu": "arm64" }, "sha512-dl119zmL1Ssyd8Fx0xfVMpss2scrGCZwf+rhZwl2lHa2dYuXVluLgqi4DUIWDj3rRYdrAvaMpjCAv6a5w07ddw=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.220", "", { "os": "darwin", "cpu": "x64" }, "sha512-X9RwDsSmbF6ultKZroaip+DL8WRgC64gHbrAwrRlAFSPNZV7zmJyP2ur8rW7KrxqmtuehdMMkw8+SAC/6hD2PA=="],
|
"@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.217", "", { "os": "darwin", "cpu": "x64" }, "sha512-IeKL1HN8fEcRQ4uw5d02by1ThpjhRtOgfHcCTBQ2KS4JfEIHvc1VGWt6Exb2a7VHhT8uRcfjPk9urbmYayZmaw=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.220", "", { "os": "linux", "cpu": "arm64" }, "sha512-WkROPwWskqhKR9XgnmseHQ6rLi9zM9qt57IWoToIjL/eXOqDWipp7JXZ1L5ud+LrA42dunHPZfBwD/vXZ+A7LA=="],
|
"@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.217", "", { "os": "linux", "cpu": "arm64" }, "sha512-KtrnfEwUSCdq2cc4Pgysl+U66vqw3h7u04N5/OLHmYZ4AZYy8JcqdOaSJZ27iL2bgbAxyKwu5/9YmEk9A4IswA=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.220", "", { "os": "linux", "cpu": "arm64" }, "sha512-OHoZOZ8Cf2TBr6oXIXPwyvUxj9jrq2w8E4poA8dMpacXszcPSPiCQCMuuOh4aWJzfeJE1+TtWxhKMVb2csXyZQ=="],
|
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.217", "", { "os": "linux", "cpu": "arm64" }, "sha512-Bb4AJxqrVPouM4sYIdvX3/AO5womhe70u3Euv+6B5J2OoqcRaWarVvYevX3KRruC5TvlV2Josw14dsL5qVNL+A=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.3.220", "", { "os": "linux", "cpu": "x64" }, "sha512-tkTJFnpR9VifvWX2fmkCAPkT6+8Wk/gVu8B5jsVekKZPiZoWRHmMXO30BnZn+f0TZhgYP+82PSX3S8crH1kn+w=="],
|
"@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.3.217", "", { "os": "linux", "cpu": "x64" }, "sha512-JsAQyfl4n0PR4LX0h1SxMo0raERGb8B8dvbaoNQRRSpb9A2vvcwPEjyKu0eRKHRhTvspvuD6TfNxzxrmnouX9A=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.220", "", { "os": "linux", "cpu": "x64" }, "sha512-K+FWj+LcGhC1Z7wqeWoLxm1iemcba5xKpLLFVwYm4V6HyMx3ruYd/2r2TiQtjT+JWeNFWIys0ScHiItR6vWAiA=="],
|
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.217", "", { "os": "linux", "cpu": "x64" }, "sha512-qhugNZd77vAoPMIGM8vFHlbwTltFyI1POmfyl0ZJSpc6v7RE9+5+nqL2aGbGSDsDQkEHrJasXURxIeTMn9ut2w=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.220", "", { "os": "win32", "cpu": "arm64" }, "sha512-rIwgq0UwQExWl6KrHUyC4w5KwpL9l6nd95aUTx6RitexaAuEw//xtfTVLnuE4hDDQZFkzEwpdKc3nxDWoGcUbA=="],
|
"@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.217", "", { "os": "win32", "cpu": "arm64" }, "sha512-LuaQ+PXZvIToAR81JoiGa6Me9HDma2WH2oiYlAWh43IWaXHyOqgaI1aqSM0BjDhy2UiYWTvGzAopnqPnk+jSBw=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.3.220", "", { "os": "win32", "cpu": "x64" }, "sha512-MuOuXhbr66HlGaWXD2f3w0k2PsvmnbkwcUZ0dAe2poFLdl72GC2dapwwOBefxm9QmoNqk9+jmv/dSKGOVWyvLw=="],
|
"@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.3.217", "", { "os": "win32", "cpu": "x64" }, "sha512-4r/T+ze/S/CLZ58tP4Mw52XPmsc/LOrCOd8jZOqM13FCPWdCMU2osWmszEIKGVMRG2cGsaLVDYcks5cWFqjCjw=="],
|
||||||
|
|
||||||
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.93.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-q9vaSZQVFx6B/gPxetGYfLXSJD5v0sOmh0OpZDq7yCrTSA+Rscvrtyol7JJTW40wEpQB4U1B4JXzxQitbQ3CAA=="],
|
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.93.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-q9vaSZQVFx6B/gPxetGYfLXSJD5v0sOmh0OpZDq7yCrTSA+Rscvrtyol7JJTW40wEpQB4U1B4JXzxQitbQ3CAA=="],
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|
"@anthropic-ai/claude-agent-sdk": "^0.3.217",
|
||||||
"shell-quote": "^1.8.3"
|
"shell-quote": "^1.8.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -75,8 +75,7 @@ async function run() {
|
|||||||
core.setOutput("conclusion", "failure");
|
core.setOutput("conclusion", "failure");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} finally {
|
} finally {
|
||||||
// Stop refreshing the workload identity token file (so the process can
|
// Stop refreshing the workload identity token file so the process can exit
|
||||||
// exit) and delete the token material so it doesn't outlive this step
|
|
||||||
workloadIdentity?.stop();
|
workloadIdentity?.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,8 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import { createHash } from "crypto";
|
import { mkdirSync, writeFileSync } from "fs";
|
||||||
import { mkdirSync, rmSync, writeFileSync } from "fs";
|
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { retryWithBackoff } from "./retry";
|
import { retryWithBackoff } from "./retry";
|
||||||
|
|
||||||
@ -51,63 +50,6 @@ async function fetchIdentityToken(audience: string) {
|
|||||||
return retryWithBackoff(() => core.getIDToken(audience));
|
return retryWithBackoff(() => core.getIDToken(audience));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a profile config that switches federation resolution to the
|
|
||||||
* file-backed path. Resolving federation through a profile (rather than bare
|
|
||||||
* env vars) enables the SDK's on-disk credentials cache, so the several
|
|
||||||
* `claude` processes the action spawns (plugin installs, main query) share
|
|
||||||
* one exchanged access token instead of each re-exchanging the single-use
|
|
||||||
* GitHub OIDC token, which fails with 401 (`jti_reused`).
|
|
||||||
*
|
|
||||||
* The profile is intentionally minimal: the SDK gap-fills the federation
|
|
||||||
* fields (rule, organization, identity-token file, service account, base URL)
|
|
||||||
* from the ANTHROPIC_* env vars the action already exports, so the file only
|
|
||||||
* needs to exist to turn the cache on.
|
|
||||||
*
|
|
||||||
* The config dir name embeds a fingerprint of the federation inputs. The
|
|
||||||
* SDK's cache reuses a token on `expires_at` alone, with no record of the
|
|
||||||
* config that minted it, and the token's scope is bound at mint time — so a
|
|
||||||
* later action step in the same job (RUNNER_TEMP is per-job) with different
|
|
||||||
* federation inputs must land in a different dir or it would silently reuse
|
|
||||||
* the first step's token.
|
|
||||||
*
|
|
||||||
* Sharing the cache is only safe while the action spawns its `claude`
|
|
||||||
* subprocesses sequentially: the SDK cache is not cross-process serialized,
|
|
||||||
* and concurrent cache misses would each re-exchange the same single-use
|
|
||||||
* identity token. Parallelizing the plugin installs would reintroduce the
|
|
||||||
* `jti_reused` failures.
|
|
||||||
*/
|
|
||||||
function writeFederationProfile(baseDir: string): string {
|
|
||||||
// Every input that changes which credential the exchange mints must be in
|
|
||||||
// here; service_account_id and scope are sent in the exchange request body.
|
|
||||||
const fingerprint = createHash("sha256")
|
|
||||||
.update(
|
|
||||||
JSON.stringify([
|
|
||||||
process.env.ANTHROPIC_FEDERATION_RULE_ID?.trim() ?? "",
|
|
||||||
process.env.ANTHROPIC_ORGANIZATION_ID?.trim() ?? "",
|
|
||||||
process.env.ANTHROPIC_SERVICE_ACCOUNT_ID?.trim() ?? "",
|
|
||||||
process.env.ANTHROPIC_WORKSPACE_ID?.trim() ?? "",
|
|
||||||
process.env.ANTHROPIC_BASE_URL?.trim() ?? "",
|
|
||||||
process.env.ANTHROPIC_SCOPE?.trim() ?? "",
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
.digest("hex")
|
|
||||||
.slice(0, 16);
|
|
||||||
const configDir = join(baseDir, `config-${fingerprint}`);
|
|
||||||
|
|
||||||
mkdirSync(join(configDir, "configs"), { recursive: true, mode: 0o700 });
|
|
||||||
writeFileSync(
|
|
||||||
join(configDir, "configs", "default.json"),
|
|
||||||
JSON.stringify(
|
|
||||||
{ version: "1.0", authentication: { type: "oidc_federation" } },
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
{ mode: 0o600 },
|
|
||||||
);
|
|
||||||
return configDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches a GitHub Actions OIDC token, writes it to a file in RUNNER_TEMP,
|
* Fetches a GitHub Actions OIDC token, writes it to a file in RUNNER_TEMP,
|
||||||
* exports ANTHROPIC_IDENTITY_TOKEN_FILE, and starts a background refresh so
|
* exports ANTHROPIC_IDENTITY_TOKEN_FILE, and starts a background refresh so
|
||||||
@ -115,8 +57,7 @@ function writeFederationProfile(baseDir: string): string {
|
|||||||
*
|
*
|
||||||
* Returns undefined when federation is not configured or is shadowed by a
|
* Returns undefined when federation is not configured or is shadowed by a
|
||||||
* higher-precedence credential. Callers must invoke stop() when execution
|
* higher-precedence credential. Callers must invoke stop() when execution
|
||||||
* finishes; it also deletes the identity token and any cached exchanged
|
* finishes.
|
||||||
* credential.
|
|
||||||
*/
|
*/
|
||||||
export async function setupWorkloadIdentity(): Promise<
|
export async function setupWorkloadIdentity(): Promise<
|
||||||
WorkloadIdentityHandle | undefined
|
WorkloadIdentityHandle | undefined
|
||||||
@ -160,17 +101,6 @@ export async function setupWorkloadIdentity(): Promise<
|
|||||||
}
|
}
|
||||||
|
|
||||||
process.env.ANTHROPIC_IDENTITY_TOKEN_FILE = tokenFile;
|
process.env.ANTHROPIC_IDENTITY_TOKEN_FILE = tokenFile;
|
||||||
if (
|
|
||||||
process.env.ANTHROPIC_CONFIG_DIR?.trim() ||
|
|
||||||
process.env.ANTHROPIC_PROFILE?.trim()
|
|
||||||
) {
|
|
||||||
core.warning(
|
|
||||||
"ANTHROPIC_CONFIG_DIR or ANTHROPIC_PROFILE is already set, so the action will not write its own federation profile. Credential caching across the spawned Claude processes follows the existing profile configuration.",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
process.env.ANTHROPIC_CONFIG_DIR = writeFederationProfile(tokenDir);
|
|
||||||
process.env.ANTHROPIC_PROFILE = "default";
|
|
||||||
}
|
|
||||||
console.log(
|
console.log(
|
||||||
`Workload identity federation configured (rule: ${process.env.ANTHROPIC_FEDERATION_RULE_ID}, identity token file: ${tokenFile})`,
|
`Workload identity federation configured (rule: ${process.env.ANTHROPIC_FEDERATION_RULE_ID}, identity token file: ${tokenFile})`,
|
||||||
);
|
);
|
||||||
@ -185,12 +115,6 @@ export async function setupWorkloadIdentity(): Promise<
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
tokenFile,
|
tokenFile,
|
||||||
stop: () => {
|
stop: () => clearInterval(refreshInterval),
|
||||||
clearInterval(refreshInterval);
|
|
||||||
// RUNNER_TEMP is per-job, not per-step: remove the identity token, the
|
|
||||||
// profile, and the cached exchanged credential so they don't outlive
|
|
||||||
// this step.
|
|
||||||
rmSync(tokenDir, { recursive: true, force: true });
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,14 +2,7 @@
|
|||||||
|
|
||||||
import { describe, test, expect, beforeEach, afterEach, spyOn } from "bun:test";
|
import { describe, test, expect, beforeEach, afterEach, spyOn } from "bun:test";
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import {
|
import { existsSync, mkdtempSync, readFileSync, rmSync, statSync } from "fs";
|
||||||
existsSync,
|
|
||||||
mkdtempSync,
|
|
||||||
readdirSync,
|
|
||||||
readFileSync,
|
|
||||||
rmSync,
|
|
||||||
statSync,
|
|
||||||
} from "fs";
|
|
||||||
import { tmpdir } from "os";
|
import { tmpdir } from "os";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import {
|
import {
|
||||||
@ -34,12 +27,6 @@ describe("workload identity federation", () => {
|
|||||||
delete process.env.ANTHROPIC_ORGANIZATION_ID;
|
delete process.env.ANTHROPIC_ORGANIZATION_ID;
|
||||||
delete process.env.ANTHROPIC_OIDC_AUDIENCE;
|
delete process.env.ANTHROPIC_OIDC_AUDIENCE;
|
||||||
delete process.env.ANTHROPIC_IDENTITY_TOKEN_FILE;
|
delete process.env.ANTHROPIC_IDENTITY_TOKEN_FILE;
|
||||||
delete process.env.ANTHROPIC_SERVICE_ACCOUNT_ID;
|
|
||||||
delete process.env.ANTHROPIC_WORKSPACE_ID;
|
|
||||||
delete process.env.ANTHROPIC_BASE_URL;
|
|
||||||
delete process.env.ANTHROPIC_SCOPE;
|
|
||||||
delete process.env.ANTHROPIC_CONFIG_DIR;
|
|
||||||
delete process.env.ANTHROPIC_PROFILE;
|
|
||||||
|
|
||||||
getIDTokenSpy = spyOn(core, "getIDToken").mockResolvedValue(
|
getIDTokenSpy = spyOn(core, "getIDToken").mockResolvedValue(
|
||||||
"test-identity-token",
|
"test-identity-token",
|
||||||
@ -136,123 +123,5 @@ describe("workload identity federation", () => {
|
|||||||
handle?.stop();
|
handle?.stop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("writes a minimal federation profile and selects it", async () => {
|
|
||||||
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
|
|
||||||
process.env.ANTHROPIC_ORGANIZATION_ID =
|
|
||||||
"00000000-0000-0000-0000-000000000000";
|
|
||||||
process.env.ANTHROPIC_SERVICE_ACCOUNT_ID = "svac_test";
|
|
||||||
process.env.ANTHROPIC_WORKSPACE_ID = "wrkspc_test";
|
|
||||||
|
|
||||||
const handle = await setupWorkloadIdentity();
|
|
||||||
try {
|
|
||||||
const configDir = process.env.ANTHROPIC_CONFIG_DIR;
|
|
||||||
expect(configDir).toBeDefined();
|
|
||||||
expect(
|
|
||||||
configDir!.startsWith(
|
|
||||||
join(tempDir, "claude-workload-identity", "config-"),
|
|
||||||
),
|
|
||||||
).toBe(true);
|
|
||||||
expect(process.env.ANTHROPIC_PROFILE).toBe("default");
|
|
||||||
|
|
||||||
const profilePath = join(configDir!, "configs", "default.json");
|
|
||||||
expect(statSync(profilePath).mode & 0o777).toBe(0o600);
|
|
||||||
// Minimal on purpose: the SDK gap-fills the federation fields from
|
|
||||||
// the ANTHROPIC_* env vars the action exports.
|
|
||||||
expect(JSON.parse(readFileSync(profilePath, "utf-8"))).toEqual({
|
|
||||||
version: "1.0",
|
|
||||||
authentication: { type: "oidc_federation" },
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
handle?.stop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("derives the config dir from the federation inputs", async () => {
|
|
||||||
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
|
|
||||||
process.env.ANTHROPIC_ORGANIZATION_ID =
|
|
||||||
"00000000-0000-0000-0000-000000000000";
|
|
||||||
process.env.ANTHROPIC_WORKSPACE_ID = "wrkspc_a";
|
|
||||||
|
|
||||||
(await setupWorkloadIdentity())?.stop();
|
|
||||||
const firstConfigDir = process.env.ANTHROPIC_CONFIG_DIR;
|
|
||||||
expect(firstConfigDir).toBeDefined();
|
|
||||||
|
|
||||||
// A later step in the same job with a different workspace must not
|
|
||||||
// share the first step's credentials cache.
|
|
||||||
delete process.env.ANTHROPIC_CONFIG_DIR;
|
|
||||||
delete process.env.ANTHROPIC_PROFILE;
|
|
||||||
process.env.ANTHROPIC_WORKSPACE_ID = "wrkspc_b";
|
|
||||||
|
|
||||||
(await setupWorkloadIdentity())?.stop();
|
|
||||||
const secondConfigDir = process.env.ANTHROPIC_CONFIG_DIR;
|
|
||||||
expect(secondConfigDir).toBeDefined();
|
|
||||||
expect(secondConfigDir).not.toBe(firstConfigDir);
|
|
||||||
|
|
||||||
// Same inputs land in the same dir, so an unchanged config can still
|
|
||||||
// reuse a cached token.
|
|
||||||
delete process.env.ANTHROPIC_CONFIG_DIR;
|
|
||||||
delete process.env.ANTHROPIC_PROFILE;
|
|
||||||
|
|
||||||
(await setupWorkloadIdentity())?.stop();
|
|
||||||
expect(process.env.ANTHROPIC_CONFIG_DIR).toBe(secondConfigDir!);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("does not overwrite an operator-set ANTHROPIC_PROFILE", async () => {
|
|
||||||
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
|
|
||||||
process.env.ANTHROPIC_ORGANIZATION_ID =
|
|
||||||
"00000000-0000-0000-0000-000000000000";
|
|
||||||
process.env.ANTHROPIC_PROFILE = "operator";
|
|
||||||
|
|
||||||
const handle = await setupWorkloadIdentity();
|
|
||||||
try {
|
|
||||||
expect(process.env.ANTHROPIC_PROFILE).toBe("operator");
|
|
||||||
expect(process.env.ANTHROPIC_CONFIG_DIR).toBeUndefined();
|
|
||||||
expect(warningSpy).toHaveBeenCalled();
|
|
||||||
|
|
||||||
const entries = readdirSync(join(tempDir, "claude-workload-identity"));
|
|
||||||
expect(entries.filter((e) => e.startsWith("config-"))).toEqual([]);
|
|
||||||
|
|
||||||
// The identity token file is still provisioned for the operator's
|
|
||||||
// profile (or the env-var fallback) to consume.
|
|
||||||
expect(process.env.ANTHROPIC_IDENTITY_TOKEN_FILE).toBe(
|
|
||||||
handle!.tokenFile,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
handle?.stop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("does not overwrite an operator-set ANTHROPIC_CONFIG_DIR", async () => {
|
|
||||||
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
|
|
||||||
process.env.ANTHROPIC_ORGANIZATION_ID =
|
|
||||||
"00000000-0000-0000-0000-000000000000";
|
|
||||||
const operatorConfigDir = join(tempDir, "operator-config");
|
|
||||||
process.env.ANTHROPIC_CONFIG_DIR = operatorConfigDir;
|
|
||||||
|
|
||||||
const handle = await setupWorkloadIdentity();
|
|
||||||
try {
|
|
||||||
expect(process.env.ANTHROPIC_CONFIG_DIR).toBe(operatorConfigDir);
|
|
||||||
expect(process.env.ANTHROPIC_PROFILE).toBeUndefined();
|
|
||||||
expect(warningSpy).toHaveBeenCalled();
|
|
||||||
} finally {
|
|
||||||
handle?.stop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("stop removes the identity token and credential cache", async () => {
|
|
||||||
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
|
|
||||||
process.env.ANTHROPIC_ORGANIZATION_ID =
|
|
||||||
"00000000-0000-0000-0000-000000000000";
|
|
||||||
|
|
||||||
const handle = await setupWorkloadIdentity();
|
|
||||||
const tokenDir = join(tempDir, "claude-workload-identity");
|
|
||||||
expect(existsSync(handle!.tokenFile)).toBe(true);
|
|
||||||
expect(existsSync(process.env.ANTHROPIC_CONFIG_DIR!)).toBe(true);
|
|
||||||
|
|
||||||
handle!.stop();
|
|
||||||
|
|
||||||
expect(existsSync(tokenDir)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
20
bun.lock
20
bun.lock
@ -7,7 +7,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/github": "^6.0.1",
|
"@actions/github": "^6.0.1",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|
"@anthropic-ai/claude-agent-sdk": "^0.3.217",
|
||||||
"@modelcontextprotocol/sdk": "^1.11.0",
|
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||||
"@octokit/graphql": "^8.2.2",
|
"@octokit/graphql": "^8.2.2",
|
||||||
"@octokit/rest": "^21.1.1",
|
"@octokit/rest": "^21.1.1",
|
||||||
@ -37,23 +37,23 @@
|
|||||||
|
|
||||||
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
|
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.3.220", "", { "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.220", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.220", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.220", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.220", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.220", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.220", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.220", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.220" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.0.0" } }, "sha512-glc7SdwPkOkLw8oxwLo9PKTdLJGqW/PIR4urWXFoRtX9YllwozsEVc5Tc1+EvLSkfrsxPJqQWqOgpjUOQXf1oA=="],
|
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.3.217", "", { "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.217", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.217", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.217", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.217", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.217", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.217", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.217", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.217" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.0.0" } }, "sha512-juszT3itL8R6OQ6nb/8IZE34UjKps8Jf7N8vjCXLx+vbJc+k3EojZOs93tJwT5iTRvfV1a0N53zJbKn/iJpKrQ=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.220", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7VxlbEosK7DODiOnsjoVd0DSJzbnaPrM2jelMHI0y8zx1UnLS3WC6EFUXbvy74F2sXqEznh2tzn7EKWInaRN6Q=="],
|
"@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.217", "", { "os": "darwin", "cpu": "arm64" }, "sha512-dl119zmL1Ssyd8Fx0xfVMpss2scrGCZwf+rhZwl2lHa2dYuXVluLgqi4DUIWDj3rRYdrAvaMpjCAv6a5w07ddw=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.220", "", { "os": "darwin", "cpu": "x64" }, "sha512-X9RwDsSmbF6ultKZroaip+DL8WRgC64gHbrAwrRlAFSPNZV7zmJyP2ur8rW7KrxqmtuehdMMkw8+SAC/6hD2PA=="],
|
"@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.217", "", { "os": "darwin", "cpu": "x64" }, "sha512-IeKL1HN8fEcRQ4uw5d02by1ThpjhRtOgfHcCTBQ2KS4JfEIHvc1VGWt6Exb2a7VHhT8uRcfjPk9urbmYayZmaw=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.220", "", { "os": "linux", "cpu": "arm64" }, "sha512-WkROPwWskqhKR9XgnmseHQ6rLi9zM9qt57IWoToIjL/eXOqDWipp7JXZ1L5ud+LrA42dunHPZfBwD/vXZ+A7LA=="],
|
"@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.217", "", { "os": "linux", "cpu": "arm64" }, "sha512-KtrnfEwUSCdq2cc4Pgysl+U66vqw3h7u04N5/OLHmYZ4AZYy8JcqdOaSJZ27iL2bgbAxyKwu5/9YmEk9A4IswA=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.220", "", { "os": "linux", "cpu": "arm64" }, "sha512-OHoZOZ8Cf2TBr6oXIXPwyvUxj9jrq2w8E4poA8dMpacXszcPSPiCQCMuuOh4aWJzfeJE1+TtWxhKMVb2csXyZQ=="],
|
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.217", "", { "os": "linux", "cpu": "arm64" }, "sha512-Bb4AJxqrVPouM4sYIdvX3/AO5womhe70u3Euv+6B5J2OoqcRaWarVvYevX3KRruC5TvlV2Josw14dsL5qVNL+A=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.3.220", "", { "os": "linux", "cpu": "x64" }, "sha512-tkTJFnpR9VifvWX2fmkCAPkT6+8Wk/gVu8B5jsVekKZPiZoWRHmMXO30BnZn+f0TZhgYP+82PSX3S8crH1kn+w=="],
|
"@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.3.217", "", { "os": "linux", "cpu": "x64" }, "sha512-JsAQyfl4n0PR4LX0h1SxMo0raERGb8B8dvbaoNQRRSpb9A2vvcwPEjyKu0eRKHRhTvspvuD6TfNxzxrmnouX9A=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.220", "", { "os": "linux", "cpu": "x64" }, "sha512-K+FWj+LcGhC1Z7wqeWoLxm1iemcba5xKpLLFVwYm4V6HyMx3ruYd/2r2TiQtjT+JWeNFWIys0ScHiItR6vWAiA=="],
|
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.217", "", { "os": "linux", "cpu": "x64" }, "sha512-qhugNZd77vAoPMIGM8vFHlbwTltFyI1POmfyl0ZJSpc6v7RE9+5+nqL2aGbGSDsDQkEHrJasXURxIeTMn9ut2w=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.220", "", { "os": "win32", "cpu": "arm64" }, "sha512-rIwgq0UwQExWl6KrHUyC4w5KwpL9l6nd95aUTx6RitexaAuEw//xtfTVLnuE4hDDQZFkzEwpdKc3nxDWoGcUbA=="],
|
"@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.217", "", { "os": "win32", "cpu": "arm64" }, "sha512-LuaQ+PXZvIToAR81JoiGa6Me9HDma2WH2oiYlAWh43IWaXHyOqgaI1aqSM0BjDhy2UiYWTvGzAopnqPnk+jSBw=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.3.220", "", { "os": "win32", "cpu": "x64" }, "sha512-MuOuXhbr66HlGaWXD2f3w0k2PsvmnbkwcUZ0dAe2poFLdl72GC2dapwwOBefxm9QmoNqk9+jmv/dSKGOVWyvLw=="],
|
"@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.3.217", "", { "os": "win32", "cpu": "x64" }, "sha512-4r/T+ze/S/CLZ58tP4Mw52XPmsc/LOrCOd8jZOqM13FCPWdCMU2osWmszEIKGVMRG2cGsaLVDYcks5cWFqjCjw=="],
|
||||||
|
|
||||||
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.93.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-q9vaSZQVFx6B/gPxetGYfLXSJD5v0sOmh0OpZDq7yCrTSA+Rscvrtyol7JJTW40wEpQB4U1B4JXzxQitbQ3CAA=="],
|
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.93.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-q9vaSZQVFx6B/gPxetGYfLXSJD5v0sOmh0OpZDq7yCrTSA+Rscvrtyol7JJTW40wEpQB4U1B4JXzxQitbQ3CAA=="],
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/github": "^6.0.1",
|
"@actions/github": "^6.0.1",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|
"@anthropic-ai/claude-agent-sdk": "^0.3.217",
|
||||||
"@modelcontextprotocol/sdk": "^1.11.0",
|
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||||
"@octokit/graphql": "^8.2.2",
|
"@octokit/graphql": "^8.2.2",
|
||||||
"@octokit/rest": "^21.1.1",
|
"@octokit/rest": "^21.1.1",
|
||||||
|
|||||||
@ -75,7 +75,7 @@ async function installClaudeCode(): Promise<string> {
|
|||||||
return customExecutable;
|
return customExecutable;
|
||||||
}
|
}
|
||||||
|
|
||||||
const claudeCodeVersion = "2.1.220";
|
const claudeCodeVersion = "2.1.217";
|
||||||
console.log(`Installing Claude Code v${claudeCodeVersion}...`);
|
console.log(`Installing Claude Code v${claudeCodeVersion}...`);
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= 3; attempt++) {
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
||||||
@ -318,8 +318,7 @@ async function run() {
|
|||||||
} finally {
|
} finally {
|
||||||
// Phase 4: Cleanup (always runs)
|
// Phase 4: Cleanup (always runs)
|
||||||
|
|
||||||
// Stop refreshing the workload identity token file and delete the token
|
// Stop refreshing the workload identity token file
|
||||||
// material so it doesn't outlive this step
|
|
||||||
workloadIdentity?.stop();
|
workloadIdentity?.stop();
|
||||||
|
|
||||||
// Update tracking comment
|
// Update tracking comment
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user