fix: gate instructions file I/O and commandNotes on fresh sessions only

On resumed sessions, skipping --append-system-prompt-file (the original
fix) left two secondary issues:
- commandNotes still claimed the flag was injected, producing misleading
  onMeta logs on every resumed heartbeat
- The instructions file was still read from disk and a combined temp file
  written on every resume, even though effectiveInstructionsFilePath was
  never consumed

Hoist canResumeSession before the I/O block and gate both the disk
operations and commandNotes construction on !canResumeSession / !sessionId.

Adds three regression tests: commandNotes is populated on fresh sessions,
empty on resume; and no agent-instructions.md is written on resume.
This commit is contained in:
lempkey 2026-04-07 22:16:00 +01:00 committed by dotta
parent 3cfbc350a0
commit e3804f792d
2 changed files with 132 additions and 20 deletions

View file

@ -335,12 +335,6 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
const dangerouslySkipPermissions = asBoolean(config.dangerouslySkipPermissions, true);
const instructionsFilePath = asString(config.instructionsFilePath, "").trim();
const instructionsFileDir = instructionsFilePath ? `${path.dirname(instructionsFilePath)}/` : "";
const commandNotes = instructionsFilePath
? [
`Injected agent instructions via --append-system-prompt-file ${instructionsFilePath} (with path directive appended)`,
]
: [];
const runtimeConfig = await buildClaudeRuntimeConfig({
runId,
agent,
@ -369,11 +363,27 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
const billingType = resolveClaudeBillingType(effectiveEnv);
const skillsDir = await buildSkillsDir(config);
const runtimeSessionParams = parseObject(runtime.sessionParams);
const runtimeSessionId = asString(runtimeSessionParams.sessionId, runtime.sessionId ?? "");
const runtimeSessionCwd = asString(runtimeSessionParams.cwd, "");
const canResumeSession =
runtimeSessionId.length > 0 &&
(runtimeSessionCwd.length === 0 || path.resolve(runtimeSessionCwd) === path.resolve(cwd));
const sessionId = canResumeSession ? runtimeSessionId : null;
if (runtimeSessionId && !canResumeSession) {
await onLog(
"stdout",
`[paperclip] Claude session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${cwd}".\n`,
);
}
// When instructionsFilePath is configured, create a combined temp file that
// includes both the file content and the path directive, so we only need
// --append-system-prompt-file (Claude CLI forbids using both flags together).
// Skipped on resumed sessions — the instructions are already baked into the
// session cache and re-injecting them wastes tokens.
let effectiveInstructionsFilePath: string | undefined = instructionsFilePath;
if (instructionsFilePath) {
if (instructionsFilePath && !canResumeSession) {
try {
const instructionsContent = await fs.readFile(instructionsFilePath, "utf-8");
const pathDirective = `\nThe above agent instructions were loaded from ${instructionsFilePath}. Resolve any relative file references from ${instructionsFileDir}.`;
@ -388,21 +398,16 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
);
effectiveInstructionsFilePath = undefined;
}
} else if (canResumeSession) {
effectiveInstructionsFilePath = undefined;
}
const runtimeSessionParams = parseObject(runtime.sessionParams);
const runtimeSessionId = asString(runtimeSessionParams.sessionId, runtime.sessionId ?? "");
const runtimeSessionCwd = asString(runtimeSessionParams.cwd, "");
const canResumeSession =
runtimeSessionId.length > 0 &&
(runtimeSessionCwd.length === 0 || path.resolve(runtimeSessionCwd) === path.resolve(cwd));
const sessionId = canResumeSession ? runtimeSessionId : null;
if (runtimeSessionId && !canResumeSession) {
await onLog(
"stdout",
`[paperclip] Claude session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${cwd}".\n`,
);
}
const commandNotes =
instructionsFilePath && !sessionId
? [
`Injected agent instructions via --append-system-prompt-file ${instructionsFilePath} (with path directive appended)`,
]
: [];
const bootstrapPromptTemplate = asString(config.bootstrapPromptTemplate, "");
const templateData = {
agentId: agent.id,