#10327: Fix: persist original prompt to transcript, not plugin-modified prompt (#287)
agents
stale
## Summary
Fixes the issue where `prependContext` from `before_agent_start` hooks (used by memory plugins like hindsight-openclaw) appeared as visible user messages in the chat UI.
**Related:** https://github.com/vectorize-io/hindsight/issues/287
## Root Cause
In `attempt.ts`, the modified `effectivePrompt` (containing prepended memory context) was passed to `activeSession.prompt()`, which persists it verbatim to the session transcript.
## Fix
Added `installTranscriptPromptGuard()` — a one-shot `appendMessage` interceptor that:
- Replaces the content of the first user-role message with the **original** `params.prompt` (what the user actually typed)
- Passes all other messages (assistant, toolResult, etc.) through untouched
- Auto-disarms after the first interception
- Only activates when hooks actually modified the prompt
## Changes
- `src/agents/pi-embedded-runner/run/attempt.ts` — Added transcript guard
- `src/agents/pi-embedded-runner/run/attempt.test.ts` — Added 5 tests
## Verification
- All 8 tests pass (3 existing + 5 new)
- Type-check clean
- Formatter ran via pre-commit hook
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
- Adds a one-shot `appendMessage` interceptor to prevent `before_agent_start` hook prepended context from being persisted as a user-visible transcript message.
- Wraps the `activeSession.prompt()` call with a try/finally that installs the guard only when the hook-modified prompt differs from the original.
- Adds unit tests validating that the first user message is replaced with the original prompt and that teardown restores behavior.
- Main concern: the guard’s teardown restores a bound wrapper, which can undo/bypass the existing sessionManager tool-result guard wrapper and potentially break transcript/tool-result consistency after the prompt.
<h3>Confidence Score: 3/5</h3>
- This PR is close, but there’s a real risk of breaking the existing SessionManager tool-result guard due to how appendMessage is restored.
- The functional intent is clear and the tests cover the guard in isolation, but the implementation stacks monkey patches on `sessionManager.appendMessage`. Because teardown restores a bound wrapper rather than the prior function reference, it can bypass `installSessionToolResultGuard()`’s patched appendMessage, potentially breaking tool-result persistence behavior in real runs.
- src/agents/pi-embedded-runner/run/attempt.ts
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#14602: fix(plugins): hook systemPrompt gets collected then thrown away (#1...
by yinghaosang · 2026-02-12
80.6%
#11921: feat(hooks): support systemPrompt injection in before_agent_start hook
by jungdaesuh · 2026-02-08
80.2%
#12296: security: persistence-only secret redaction for session transcripts
by akoscz · 2026-02-09
78.3%
#13104: fix: persist user command message in chat transcript
by mcaxtr · 2026-02-10
77.9%
#8178: feat: allow before_agent_start to override the prompt
by richardfogaca · 2026-02-03
77.8%
#15050: fix: transcript corruption resilience — strip aborted tool_use bloc...
by yashchitneni · 2026-02-12
77.8%
#11732: feat(plugins): add injectMessages to before_agent_start hook
by antra-tess · 2026-02-08
77.5%
#17930: fix: evaluate tool_result_persist hooks lazily to avoid race condition
by TheArkifaneVashtorr · 2026-02-16
77.0%
#22705: fix(agents): merge before_agent_start hook systemPrompt into sessio...
by mushuiyu422 · 2026-02-21
75.8%
#6017: feat(hooks): add systemPrompt and tools to before_agent_start event
by yajatns · 2026-02-01
75.7%