Skip to content

fix(transcript): don't capture Codex's injected AGENTS.md and environment context as prompts - #412

Open
jacklau04 wants to merge 1 commit into
TheWeiHu:mainfrom
jacklau04:fix/codex-skip-injected-instructions
Open

jacklau04 wants to merge 1 commit into
TheWeiHu:mainfrom
jacklau04:fix/codex-skip-injected-instructions

Conversation

@jacklau04

Copy link
Copy Markdown
Contributor

In plain words

Every Codex session log in a brain opens with a copy of the user's AGENTS.md, as if they had typed it. Codex inserts its instructions and an environment block as "user" messages when a thread starts, and devbrain treated every user message as a prompt. A brain fold then reads the global rules as per-project owner instructions and re-learns them in every project.

Problem

Codex rollouts carry the injected context as response_item / message / role: user events:

  • At thread start: text beginning # AGENTS.md instructions for <dir> followed by <INSTRUCTIONS>…</INSTRUCTIONS>, usually with an <environment_context>…</environment_context> block appended in the same message (cwd, shell, date, permissions).
  • After a cwd change: a bare <environment_context>…</environment_context> message.
  • Older builds used <user_instructions>…</user_instructions>.

isCodexUserPrompt accepted any role=user response_item and codexPromptText returned its text, so the injected message became the first prompt of every Codex turn log. In one brain that is 1,358 logs since September, each opening with a ~10 KB copy of AGENTS.md, plus 491 logs whose prompt is nothing but an environment block.

Fix

  • stripCodexInjected removes leading injected blocks. It is anchored at the start of the text and requires the closing tag, so a prompt that merely mentions a tag, or an unclosed block, is left alone; a message that is only injected context strips to "".
  • codexPromptText returns the stripped text, so an injected-only message has no prompt text and opens no turn (turn boundaries already require non-empty prompt text).
  • isCodexUserPrompt returns false for injected-only text; an image-only message (no text at all) still counts, so the last-user segmentation fallback is unchanged.
  • The content-joining code shared by both functions moved into codexUserText.

Raw logs are append-only, so existing logs keep their echoes; only new captures change.

Tests

  • TestCodexInjectedContextIsNotAPrompt: a rollout with the injected opener, a mid-session environment block and two typed prompts yields exactly the two typed turns in both synthetic-filter modes, and no prompt contains the injected text.
  • TestStripCodexInjected: table of strip rules (instructions-only, instructions then environment, environment-only, legacy <user_instructions>, environment then typed text, typed text, prose mention, unclosed block).
  • go test ./internal/transcript ./internal/sweep ./internal/hooks, go vet, gofmt.

🤖 Generated with Claude Code

…nment context as prompts

Codex opens a thread (and re-opens it after a cwd change) by inserting its own
context as role=user response_items: the AGENTS.md chain wrapped in
<INSTRUCTIONS> with an <environment_context> block appended, and bare
<environment_context> messages later on. Nobody typed those, but the capture
treated every role=user response_item as a prompt, so every Codex log opened
with a ~10 KB copy of AGENTS.md as the first "prompt" (1,358 such logs in one
brain since September) and a brain fold then read the global rules as owner
instructions and re-learned them in every project.

codexPromptText now strips leading injected blocks (anchored at the start and
closed by the end tag, so prose that mentions a tag or an unclosed block is left
alone); a message that is only injected context has no prompt text and opens no
turn. isCodexUserPrompt matches, while an image-only message still counts so
the last-user segmentation fallback is unchanged. Older builds'
<user_instructions> wrapper is covered the same way.

Tests: a rollout with the injected opener, a mid-session environment block and
two typed prompts yields exactly two turns with the typed prompts for both
synthetic-filter modes and never leaks the injected text; a table test pins
the strip rules. go test ./internal/transcript ./internal/sweep ./internal/hooks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

1 participant