diff --git a/.changeset/goal-replay-leak.md b/.changeset/goal-replay-leak.md
new file mode 100644
index 0000000000..20bd8f2e7e
--- /dev/null
+++ b/.changeset/goal-replay-leak.md
@@ -0,0 +1,5 @@
+---
+"@moonshot-ai/kimi-code": patch
+---
+
+Fix goal mode continuation prompts leaking into the transcript when resuming a session.
diff --git a/apps/kimi-code/src/tui/controllers/session-replay.ts b/apps/kimi-code/src/tui/controllers/session-replay.ts
index ad6e82358b..a20e8a513a 100644
--- a/apps/kimi-code/src/tui/controllers/session-replay.ts
+++ b/apps/kimi-code/src/tui/controllers/session-replay.ts
@@ -301,17 +301,10 @@ export class SessionReplayRenderer {
this.renderCronMissed(context, message);
return;
}
- if (isGoalForkClearedSystemReminder(message)) {
- return;
- }
- const goalReminder = goalOutcomeReminderFromSystemMessage(message);
- if (goalReminder !== null) {
- if (goalReminder !== undefined) {
- this.flushAssistant(context);
- this.host.appendTranscriptEntry(
- replayEntry(context, 'assistant', goalReminder, 'markdown'),
- );
- }
+ // System-trigger messages (goal continuation prompts, goal outcome
+ // reminders, stop-hook reasons, …) are model-facing only: the live event
+ // stream never renders them, so replay must not leak them either.
+ if (message.origin?.kind === 'system_trigger') {
return;
}
@@ -741,18 +734,6 @@ function isModelBlockedGoalLifecycle(change: GoalReplayLifecycleChange): boolean
return change.status === 'blocked' && change.actor === 'model';
}
-function goalOutcomeReminderFromSystemMessage(message: ContextMessage): string | undefined | null {
- if (message.origin?.kind !== 'system_trigger') return null;
- if (message.origin.name !== 'goal_completion' && message.origin.name !== 'goal_blocked') {
- return null;
- }
- return undefined;
-}
-
-function isGoalForkClearedSystemReminder(message: ContextMessage): boolean {
- return message.origin?.kind === 'system_trigger' && message.origin.name === 'goal_fork_cleared';
-}
-
function extractCronPrompt(text: string): string {
const open = '\n';
const close = '\n';
diff --git a/apps/kimi-code/test/tui/message-replay.test.ts b/apps/kimi-code/test/tui/message-replay.test.ts
index f3e5faa253..26d8ddc450 100644
--- a/apps/kimi-code/test/tui/message-replay.test.ts
+++ b/apps/kimi-code/test/tui/message-replay.test.ts
@@ -501,6 +501,38 @@ describe('KimiTUI resume message replay', () => {
expect(content).not.toContain('Write a concise final message for the user');
});
+ it('does not replay system-trigger prompts such as goal continuation as user messages', async () => {
+ const driver = await replayIntoDriver([
+ message(
+ 'user',
+ [
+ {
+ type: 'text',
+ text: 'Continue working toward the active goal. Keep the self-audit brief.',
+ },
+ ],
+ { origin: { kind: 'system_trigger', name: 'goal_continuation' } },
+ ),
+ message(
+ 'user',
+ [
+ {
+ type: 'text',
+ text: '\nThe goal was cancelled.\n',
+ },
+ ],
+ { origin: { kind: 'system_trigger', name: 'goal_cancelled' } },
+ ),
+ message('assistant', [{ type: 'text', text: 'Working on it.' }]),
+ ]);
+
+ expect(driver.state.transcriptEntries.filter((entry) => entry.kind === 'user')).toEqual([]);
+ const transcript = stripAnsi(driver.state.transcriptContainer.render(140).join('\n'));
+ expect(transcript).not.toContain('Continue working toward the active goal');
+ expect(transcript).not.toContain('The goal was cancelled');
+ expect(transcript).toContain('Working on it.');
+ });
+
it('does not replay the model-blocked lifecycle marker when the follow-up is replayed', async () => {
const driver = await replayIntoDriver([
goalReplay(