fix: clear stale claude-prompts dir before each write (#1288)

Previously, prompt files at `${RUNNER_TEMP}/claude-prompts/` from a prior
invocation could persist on non-ephemeral self-hosted runners (where the
documented `RUNNER_TEMP` cleanup contract is not reliably honored). In
particular, `claude-user-request.txt` is only written by `create-prompt`
when a user request exists; an `agent`-mode invocation does not overwrite
it, so a stale value left by an earlier mention-mode job in another repo
would leak into a later agent-mode job's effective context on the same
runner agent.

Fix: `rm -rf` the directory before `mkdir` in both write sites
(`src/create-prompt/index.ts`, `src/modes/agent/index.ts`). Idempotent,
safe on hosted runners (where the dir is already empty), and self-heals
on self-hosted runners.

Closes #1287
This commit is contained in:
Kyungil Park 2026-06-12 13:18:57 +09:00 committed by GitHub
parent b371255139
commit 9441a7fe22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 19 additions and 21 deletions

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bun #!/usr/bin/env bun
import * as core from "@actions/core"; import * as core from "@actions/core";
import { writeFile, mkdir } from "fs/promises"; import { writeFile, mkdir, rm } from "fs/promises";
import type { FetchDataResult } from "../github/data/fetcher"; import type { FetchDataResult } from "../github/data/fetcher";
import { import {
formatContext, formatContext,
@ -930,9 +930,14 @@ export async function createPrompt(
claudeBranch, claudeBranch,
); );
await mkdir(`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`, { // Clear any stale prompt files from a prior invocation. RUNNER_TEMP is documented
recursive: true, // to be emptied between jobs, but on non-ephemeral self-hosted runners this is
}); // not reliably honored — a stale claude-user-request.txt left behind by a prior
// mention-mode invocation would not be overwritten by a subsequent agent-mode
// invocation, and would leak into the model's context.
const promptDir = `${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`;
await rm(promptDir, { recursive: true, force: true });
await mkdir(promptDir, { recursive: true });
// Generate the prompt directly // Generate the prompt directly
const promptContent = generatePrompt( const promptContent = generatePrompt(
@ -948,10 +953,7 @@ export async function createPrompt(
console.log("======================="); console.log("=======================");
// Write the prompt file // Write the prompt file
await writeFile( await writeFile(`${promptDir}/claude-prompt.txt`, promptContent);
`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts/claude-prompt.txt`,
promptContent,
);
// Extract and write the user request separately for SDK multi-block messaging // Extract and write the user request separately for SDK multi-block messaging
// This allows the CLI to process slash commands (e.g., "@claude /review-pr") // This allows the CLI to process slash commands (e.g., "@claude /review-pr")
@ -960,10 +962,7 @@ export async function createPrompt(
githubData, githubData,
); );
if (userRequest) { if (userRequest) {
await writeFile( await writeFile(`${promptDir}/${USER_REQUEST_FILENAME}`, userRequest);
`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts/${USER_REQUEST_FILENAME}`,
userRequest,
);
console.log("===== USER REQUEST ====="); console.log("===== USER REQUEST =====");
console.log(userRequest); console.log(userRequest);
console.log("========================"); console.log("========================");

View File

@ -1,4 +1,4 @@
import { mkdir, writeFile } from "fs/promises"; import { mkdir, rm, writeFile } from "fs/promises";
import { prepareMcpConfig } from "../../mcp/install-mcp-server"; import { prepareMcpConfig } from "../../mcp/install-mcp-server";
import { parseAllowedTools } from "./parse-tools"; import { parseAllowedTools } from "./parse-tools";
import { import {
@ -64,20 +64,19 @@ export async function prepareAgentMode({
} }
} }
// Create prompt directory // Create prompt directory. Clear any stale files from a prior invocation first —
await mkdir(`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`, { // see src/create-prompt/index.ts for context (non-ephemeral self-hosted runners
recursive: true, // do not reliably honor the RUNNER_TEMP cleanup contract).
}); const promptDir = `${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`;
await rm(promptDir, { recursive: true, force: true });
await mkdir(promptDir, { recursive: true });
// Write the prompt file - use the user's prompt directly // Write the prompt file - use the user's prompt directly
const promptContent = const promptContent =
context.inputs.prompt || context.inputs.prompt ||
`Repository: ${context.repository.owner}/${context.repository.repo}`; `Repository: ${context.repository.owner}/${context.repository.repo}`;
await writeFile( await writeFile(`${promptDir}/claude-prompt.txt`, promptContent);
`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts/claude-prompt.txt`,
promptContent,
);
// Parse allowed tools from user's claude_args // Parse allowed tools from user's claude_args
const userClaudeArgs = process.env.CLAUDE_ARGS || ""; const userClaudeArgs = process.env.CLAUDE_ARGS || "";