Skip to content

fix(runtime): don't persist session_start hook output as a session message#2533

Merged
dgageot merged 1 commit intodocker:mainfrom
dgageot:board/builtin-hooks-displaying-unwanted-env-in-0fb6e572
Apr 27, 2026
Merged

fix(runtime): don't persist session_start hook output as a session message#2533
dgageot merged 1 commit intodocker:mainfrom
dgageot:board/builtin-hooks-displaying-unwanted-env-in-0fb6e572

Conversation

@dgageot
Copy link
Copy Markdown
Member

@dgageot dgageot commented Apr 27, 2026

Problem

When an agent has add_environment_info: true (or any other agent flag that
gets wired to session_start), the env block leaked into the visible
transcript on the user's first question, looking like the user had typed it:

Here is useful information about the environment you are running in:
<env>
Working directory: /Users/.../infra-terraform
Is directory a git repo: Yes
Operating System: MacOS
CPU Architecture: arm64
</env>

Root cause

executeSessionStartHooks was persisting the hook's AdditionalContext to
the session via sess.AddMessage(session.SystemMessage(...)). Because
session_start fires inside RunStreamafter the caller has already
appended the user's message — the system message landed AFTER the user's
first turn. The user-message emitter at the top of RunStream reads
messages[len-1] and surfaces it as the UserMessageEvent, so the env
block was relayed verbatim to the UI.

Fix

Hold session_start AdditionalContext as transient extras for the
duration of RunStream and thread it into sess.GetMessages alongside
turn_start output on every iteration. Same-shape contract as
turn_start, which was already transient. The model still sees the env
info on every model call; the persisted transcript and the user message
tail stay clean.

  • pkg/runtime/hooks.go: executeSessionStartHooks now returns
    []chat.Message instead of mutating the session. Both start-hook
    helpers share a small contextMessages converter so the bodies
    collapse to a single dispatchHook call each.
  • pkg/runtime/loop.go: capture session_start extras once at the top
    of RunStream; combine with per-iteration turn_start extras via
    slices.Concat when calling sess.GetMessages.
  • pkg/runtime/runtime_test.go: pin the regression with
    TestRunStream_AddEnvironmentInfo_DoesNotPolluteSession — asserts the
    session contains only user+assistant roles and that
    UserMessageEvent.Message is "hello", not the <env> block.

Verification

Verified the regression test fails on the unfixed tree with exactly the
user-reported diff (<env> block in UserMessageEvent.Message) and passes
on the fixed tree. mise lint is clean (0 issues, no offenses); mise test is green end-to-end.

…ssage

session_start hook output (typically the AddEnvironmentInfo env block)
was added to the session as a chat.MessageRoleSystem item via
sess.AddMessage. Because session_start fires inside RunStream — after
the caller has already appended the user's message — the system
message landed AFTER the user's first turn. The user-message emitter
at the top of RunStream reads messages[len-1] and surfaces it as the
UserMessageEvent, so the env info leaked verbatim into the visible
transcript on the user's first question.

Fix: hold session_start AdditionalContext as transient extras for the
duration of RunStream and thread it into sess.GetMessages alongside
turn_start output on every iteration. The model still sees the env
info on every model call; the persisted transcript and the user
message tail stay clean. Same-shape contract as turn_start, which was
already transient.

- pkg/runtime/hooks.go: executeSessionStartHooks now returns
  []chat.Message instead of mutating the session. Both start-hook
  helpers share a small contextMessages converter so the bodies
  collapse to a single dispatchHook call each.
- pkg/runtime/loop.go: capture session_start extras once at the top
  of RunStream; combine with per-iteration turn_start extras via
  slices.Concat when calling sess.GetMessages.
- pkg/runtime/runtime_test.go: pin the regression with
  TestRunStream_AddEnvironmentInfo_DoesNotPolluteSession — asserts
  the session contains only user+assistant roles and that
  UserMessageEvent.Message is "hello", not the <env> block.

Assisted-By: docker-agent
@dgageot dgageot requested a review from a team as a code owner April 27, 2026 14:06
@dgageot dgageot merged commit 437a06b into docker:main Apr 27, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants