fix(agents): stabilize cli creds cache + bash cwd

This commit is contained in:
Peter Steinberger
2026-01-10 18:02:21 +01:00
parent 843ff5f2d4
commit ef08c3f038
4 changed files with 37 additions and 12 deletions

View File

@@ -114,6 +114,7 @@ export type BashToolDetails =
sessionId: string;
pid?: number;
startedAt: number;
cwd?: string;
tail?: string;
}
| {
@@ -121,6 +122,7 @@ export type BashToolDetails =
exitCode: number | null;
durationMs: number;
aggregated: string;
cwd?: string;
};
export function createBashTool(
@@ -316,6 +318,7 @@ export function createBashTool(
sessionId,
pid: session.pid ?? undefined,
startedAt,
cwd: session.cwd,
tail: session.tail,
},
});
@@ -356,6 +359,7 @@ export function createBashTool(
sessionId,
pid: session.pid ?? undefined,
startedAt,
cwd: session.cwd,
tail: session.tail,
},
}),
@@ -431,6 +435,7 @@ export function createBashTool(
exitCode: code ?? 0,
durationMs,
aggregated,
cwd: session.cwd,
},
}),
);

View File

@@ -136,10 +136,12 @@ describe("cli credentials", () => {
const first = readClaudeCliCredentialsCached({
allowKeychainPrompt: true,
ttlMs: 15 * 60 * 1000,
platform: "darwin",
});
const second = readClaudeCliCredentialsCached({
allowKeychainPrompt: false,
ttlMs: 15 * 60 * 1000,
platform: "darwin",
});
expect(first).toBeTruthy();
@@ -167,6 +169,7 @@ describe("cli credentials", () => {
const first = readClaudeCliCredentialsCached({
allowKeychainPrompt: true,
ttlMs: 15 * 60 * 1000,
platform: "darwin",
});
vi.advanceTimersByTime(15 * 60 * 1000 + 1);
@@ -174,6 +177,7 @@ describe("cli credentials", () => {
const second = readClaudeCliCredentialsCached({
allowKeychainPrompt: true,
ttlMs: 15 * 60 * 1000,
platform: "darwin",
});
expect(first).toBeTruthy();

View File

@@ -111,8 +111,11 @@ function readClaudeCliKeychainCredentials(): ClaudeCliCredential | null {
export function readClaudeCliCredentials(options?: {
allowKeychainPrompt?: boolean;
platform?: NodeJS.Platform;
homeDir?: string;
}): ClaudeCliCredential | null {
if (process.platform === "darwin" && options?.allowKeychainPrompt !== false) {
const platform = options?.platform ?? process.platform;
if (platform === "darwin" && options?.allowKeychainPrompt !== false) {
const keychainCreds = readClaudeCliKeychainCredentials();
if (keychainCreds) {
log.info("read anthropic credentials from claude cli keychain", {
@@ -122,7 +125,7 @@ export function readClaudeCliCredentials(options?: {
}
}
const credPath = resolveClaudeCliCredentialsPath();
const credPath = resolveClaudeCliCredentialsPath(options?.homeDir);
const raw = loadJsonFile(credPath);
if (!raw || typeof raw !== "object") return null;
@@ -158,10 +161,12 @@ export function readClaudeCliCredentials(options?: {
export function readClaudeCliCredentialsCached(options?: {
allowKeychainPrompt?: boolean;
ttlMs?: number;
platform?: NodeJS.Platform;
homeDir?: string;
}): ClaudeCliCredential | null {
const ttlMs = options?.ttlMs ?? 0;
const now = Date.now();
const cacheKey = resolveClaudeCliCredentialsPath();
const cacheKey = resolveClaudeCliCredentialsPath(options?.homeDir);
if (
ttlMs > 0 &&
claudeCliCache &&
@@ -172,6 +177,8 @@ export function readClaudeCliCredentialsCached(options?: {
}
const value = readClaudeCliCredentials({
allowKeychainPrompt: options?.allowKeychainPrompt,
platform: options?.platform,
homeDir: options?.homeDir,
});
if (ttlMs > 0) {
claudeCliCache = { value, readAt: now, cacheKey };

View File

@@ -5,9 +5,6 @@ import path from "node:path";
import { describe, expect, it } from "vitest";
import { createClawdbotCodingTools } from "./pi-tools.js";
const normalizeText = (value?: string) =>
(value ?? "").replace(/\r\n/g, "\n").trim();
async function withTempDir<T>(prefix: string, fn: (dir: string) => Promise<T>) {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
try {
@@ -120,11 +117,17 @@ describe("workspace path resolution", () => {
expect(bashTool).toBeDefined();
const result = await bashTool?.execute("ws-bash", {
command: 'node -e "console.log(process.cwd())"',
command: "echo ok",
});
const output = normalizeText(getTextContent(result));
const cwd =
result?.details &&
typeof result.details === "object" &&
"cwd" in result.details
? (result.details as { cwd?: string }).cwd
: undefined;
expect(cwd).toBeTruthy();
const [resolvedOutput, resolvedWorkspace] = await Promise.all([
fs.realpath(output),
fs.realpath(String(cwd)),
fs.realpath(workspaceDir),
]);
expect(resolvedOutput).toBe(resolvedWorkspace);
@@ -139,12 +142,18 @@ describe("workspace path resolution", () => {
expect(bashTool).toBeDefined();
const result = await bashTool?.execute("ws-bash-override", {
command: 'node -e "console.log(process.cwd())"',
command: "echo ok",
workdir: overrideDir,
});
const output = normalizeText(getTextContent(result));
const cwd =
result?.details &&
typeof result.details === "object" &&
"cwd" in result.details
? (result.details as { cwd?: string }).cwd
: undefined;
expect(cwd).toBeTruthy();
const [resolvedOutput, resolvedOverride] = await Promise.all([
fs.realpath(output),
fs.realpath(String(cwd)),
fs.realpath(overrideDir),
]);
expect(resolvedOutput).toBe(resolvedOverride);