fix(claude-local): pipe print prompt via stdin (#9500)
Fixes #2444. Refs #4947. The `claude_local` adapter launched Claude Code as `claude --print - --output-format stream-json --verbose`. Paperclip writes the rendered task prompt to Claude's stdin, but current Claude Code releases can treat the stale `-` positional marker as the prompt itself, so Claude received the literal string `"-"` instead of the issue body. The customer's task ran against no content at all. The fix keeps `--print` mode and stdin delivery, and removes the stale `-`. Adds regression coverage on both sides of the delivery path: a `claude_local` assertion that `--print` is present, `"-"` is absent and the prompt still reaches stdin, and an adapter-utils case proving the sandbox run-log command wrapper preserves stdin while streaming logs. Authored by @elJayAdvisor, whose commit is included unchanged with their authorship. The branch had gone stale and was showing CONFLICTING; the conflict was in `execution-target-sandbox.test.ts`, where their new test was added at the same point as master's `creates the process session directories only in the launch exec` case and git interleaved the two into one hunk. Resolved by taking master's file and re-inserting their test whole, after checking every helper it needs still exists there. Verified: the bug was still live on master at `execute.ts:838`; the regression test genuinely catches it — restoring the stale `-` fails `expect(captured.argv).not.toContain("-")`; `@paperclipai/adapter-claude-local` and `@paperclipai/adapter-utils` typecheck clean; 67 pass across the two test files. All CI gates green; Greptile 5/5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8cb0ce0de5
commit
a53cc8819b
|
|
@ -237,6 +237,48 @@ describe("sandbox adapter execution targets", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("preserves stdin when wrapping sandbox adapter commands for run-log streaming", async () => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-run-log-stdin-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
const target: AdapterSandboxExecutionTarget = {
|
||||
kind: "remote",
|
||||
transport: "sandbox",
|
||||
providerKey: "local-test",
|
||||
remoteCwd: rootDir,
|
||||
timeoutMs: 30_000,
|
||||
streamRunLogs: true,
|
||||
runner: createLocalSandboxRunner(),
|
||||
};
|
||||
const logsDir = path.posix.join(rootDir, ".paperclip-runtime", "bridge", "logs");
|
||||
const runLogTail = createSandboxRunLogTailFactory({
|
||||
runner: target.runner!,
|
||||
remoteCwd: rootDir,
|
||||
logsDir,
|
||||
shellCommand: "bash",
|
||||
}).create();
|
||||
const events: Array<{ stream: "stdout" | "stderr"; chunk: string }> = [];
|
||||
|
||||
const result = await runAdapterExecutionTargetProcess(
|
||||
"run-log-stdin",
|
||||
target,
|
||||
process.execPath,
|
||||
["-e", "process.stdin.setEncoding('utf8'); let s=''; process.stdin.on('data', c => s += c); process.stdin.on('end', () => process.stdout.write('stdin=' + s));"],
|
||||
{
|
||||
cwd: rootDir,
|
||||
env: {},
|
||||
stdin: "hello-through-wrapper",
|
||||
timeoutSec: 5,
|
||||
graceSec: 1,
|
||||
runLogTail: { create: () => runLogTail },
|
||||
onLog: async (stream, chunk) => { events.push({ stream, chunk }); },
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toBe("stdin=hello-through-wrapper");
|
||||
expect(combinedStream(events, "stdout")).toContain("stdin=hello-through-wrapper");
|
||||
});
|
||||
|
||||
it("creates the process session directories only in the launch exec, not in upfront makeDir execs", async () => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-process-session-makedir-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
|
|
|
|||
|
|
@ -835,7 +835,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||
resumeSessionId: string | null,
|
||||
attemptInstructionsFilePath: string | undefined,
|
||||
) => {
|
||||
const args = ["--print", "-", "--output-format", "stream-json", "--verbose"];
|
||||
const args = ["--print", "--output-format", "stream-json", "--verbose"];
|
||||
if (resumeSessionId) args.push("--resume", resumeSessionId);
|
||||
args.push(...buildClaudeExecutionPermissionArgs({
|
||||
dangerouslySkipPermissions,
|
||||
|
|
|
|||
|
|
@ -434,6 +434,9 @@ describe("claude execute", () => {
|
|||
onMeta: async () => {},
|
||||
});
|
||||
const captured = JSON.parse(await fs.readFile(capturePath, "utf-8"));
|
||||
expect(captured.argv).toContain("--print");
|
||||
expect(captured.argv).not.toContain("-");
|
||||
expect(captured.prompt).toContain("Do work.");
|
||||
expect(captured.argv).toContain("--append-system-prompt-file");
|
||||
} finally {
|
||||
restore();
|
||||
|
|
|
|||
Loading…
Reference in New Issue