Skip to content
Closed
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
207 changes: 206 additions & 1 deletion src/integrations/claude-code/__tests__/run.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, test, expect, vi, beforeEach } from "vitest"
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest"

// Mock vscode workspace
vi.mock("vscode", () => ({
Expand All @@ -10,6 +10,13 @@ vi.mock("vscode", () => ({
},
},
],
fs: {
writeFile: vi.fn().mockResolvedValue(undefined),
delete: vi.fn().mockResolvedValue(undefined),
},
},
Uri: {
file: vi.fn((path: string) => ({ fsPath: path })),
},
}))

Expand Down Expand Up @@ -86,6 +93,15 @@ vi.mock("readline", () => ({
},
}))

// Mock path and os modules
vi.mock("path", () => ({
join: vi.fn((...args: string[]) => args.join("/")),
}))

vi.mock("os", () => ({
tmpdir: vi.fn(() => "/tmp"),
}))

describe("runClaudeCode", () => {
beforeEach(() => {
vi.clearAllMocks()
Expand Down Expand Up @@ -287,4 +303,193 @@ describe("runClaudeCode", () => {
consoleErrorSpy.mockRestore()
await generator.return(undefined)
})

test("should use command line argument for short system prompts", async () => {
const { runClaudeCode } = await import("../run")
const shortSystemPrompt = "You are a helpful assistant"
const options = {
systemPrompt: shortSystemPrompt,
messages: [{ role: "user" as const, content: "Hello" }],
}

const generator = runClaudeCode(options)

// Consume at least one item to trigger process spawn
await generator.next()

// Clean up the generator
await generator.return(undefined)

// Verify execa was called with system prompt as command line argument
const [, args, execaOptions] = mockExeca.mock.calls[0]
expect(args).toContain("--system-prompt")
expect(args).toContain(shortSystemPrompt)

// Verify no environment variable was set for short prompt
expect(execaOptions.env?.CLAUDE_CODE_SYSTEM_PROMPT).toBeUndefined()
})

test("should use temporary file for long system prompts to avoid Windows ENAMETOOLONG error", async () => {
const { runClaudeCode } = await import("../run")
// Create a system prompt longer than MAX_COMMAND_LINE_LENGTH (7000 chars)
const longSystemPrompt = "You are a helpful assistant. " + "A".repeat(7000)
const options = {
systemPrompt: longSystemPrompt,
messages: [{ role: "user" as const, content: "Hello" }],
}

const generator = runClaudeCode(options)

// Consume at least one item to trigger process spawn
await generator.next()

// Clean up the generator
await generator.return(undefined)

// Verify execa was called with --system-prompt @filepath pattern
const [, args, execaOptions] = mockExeca.mock.calls[0]
expect(args).toContain("--system-prompt")

// Find the system prompt argument
const systemPromptIndex = args.indexOf("--system-prompt")
expect(systemPromptIndex).toBeGreaterThan(-1)
const systemPromptArg = args[systemPromptIndex + 1]

// Verify it uses the @filepath pattern for temp files
expect(systemPromptArg).toMatch(/^@.*claude-system-prompt-.*\.txt$/)

// Verify the long system prompt is not directly in the arguments
expect(args).not.toContain(longSystemPrompt)

// Verify no environment variable was set for system prompt
expect(execaOptions.env?.CLAUDE_CODE_SYSTEM_PROMPT).toBeUndefined()
})

test("should handle exactly MAX_COMMAND_LINE_LENGTH system prompt using command line", async () => {
const { runClaudeCode } = await import("../run")
// Create a system prompt exactly at the threshold (7000 chars)
const exactLengthPrompt = "A".repeat(7000)
const options = {
systemPrompt: exactLengthPrompt,
messages: [{ role: "user" as const, content: "Hello" }],
}

const generator = runClaudeCode(options)

// Consume at least one item to trigger process spawn
await generator.next()

// Clean up the generator
await generator.return(undefined)

// Verify execa was called with system prompt as command line argument (at threshold)
const [, args, execaOptions] = mockExeca.mock.calls[0]
expect(args).toContain("--system-prompt")
expect(args).toContain(exactLengthPrompt)

// Verify no temporary file was used (no @ prefix)
const systemPromptIndex = args.indexOf("--system-prompt")
const systemPromptArg = args[systemPromptIndex + 1]
expect(systemPromptArg).not.toMatch(/^@/)
})

test("should handle system prompt one character over threshold using temporary file", async () => {
const { runClaudeCode } = await import("../run")
// Create a system prompt one character over the threshold (7001 chars)
const overThresholdPrompt = "A".repeat(7001)
const options = {
systemPrompt: overThresholdPrompt,
messages: [{ role: "user" as const, content: "Hello" }],
}

const generator = runClaudeCode(options)

// Consume at least one item to trigger process spawn
await generator.next()

// Clean up the generator
await generator.return(undefined)

// Verify execa was called with --system-prompt @filepath pattern
const [, args, execaOptions] = mockExeca.mock.calls[0]
expect(args).toContain("--system-prompt")

// Find the system prompt argument
const systemPromptIndex = args.indexOf("--system-prompt")
const systemPromptArg = args[systemPromptIndex + 1]

// Verify it uses the @filepath pattern for temp files
expect(systemPromptArg).toMatch(/^@.*claude-system-prompt-.*\.txt$/)

// Verify the long system prompt is not directly in the arguments
expect(args).not.toContain(overThresholdPrompt)

// Verify no environment variable was set
expect(execaOptions.env?.CLAUDE_CODE_SYSTEM_PROMPT).toBeUndefined()
})

test("should preserve existing environment variables when using temporary files", async () => {
const { runClaudeCode } = await import("../run")

// Mock process.env to have some existing variables
const originalEnv = process.env
process.env = {
...originalEnv,
EXISTING_VAR: "existing_value",
PATH: "/usr/bin:/bin",
}

const longSystemPrompt = "You are a helpful assistant. " + "A".repeat(7000)
const options = {
systemPrompt: longSystemPrompt,
messages: [{ role: "user" as const, content: "Hello" }],
}

const generator = runClaudeCode(options)

// Consume at least one item to trigger process spawn
await generator.next()

// Clean up the generator
await generator.return(undefined)

// Verify environment variables include existing ones but no CLAUDE_CODE_SYSTEM_PROMPT
const [, , execaOptions] = mockExeca.mock.calls[0]
expect(execaOptions.env).toEqual({
...process.env,
CLAUDE_CODE_MAX_OUTPUT_TOKENS: expect.any(String), // Always set by the implementation
})

// Verify no system prompt environment variable was set
expect(execaOptions.env?.CLAUDE_CODE_SYSTEM_PROMPT).toBeUndefined()

// Restore original environment
process.env = originalEnv
})

test("should work with empty system prompt", async () => {
const { runClaudeCode } = await import("../run")
const options = {
systemPrompt: "",
messages: [{ role: "user" as const, content: "Hello" }],
}

const generator = runClaudeCode(options)

// Consume at least one item to trigger process spawn
await generator.next()

// Clean up the generator
await generator.return(undefined)

// Verify execa was called with empty system prompt as command line argument
const [, args, execaOptions] = mockExeca.mock.calls[0]
expect(args).toContain("--system-prompt")
expect(args).toContain("")

// Verify no temporary file was used (no @ prefix)
const systemPromptIndex = args.indexOf("--system-prompt")
const systemPromptArg = args[systemPromptIndex + 1]
expect(systemPromptArg).not.toMatch(/^@/)
})
})
Loading