feat(hooks): strip ANSI escape sequences from tool results in messages.transform#1990
feat(hooks): strip ANSI escape sequences from tool results in messages.transform#1990MoerAI wants to merge 6 commits intocode-yeongyu:devfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 734b41901b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| export function containsAnsi(text: string): boolean { | ||
| return ANSI_REGEX.test(text) |
There was a problem hiding this comment.
Avoid stateful global regex in containsAnsi
containsAnsi calls .test() on ANSI_REGEX, which is declared with the global (g) flag; this makes lastIndex stateful across calls and can produce false negatives when checking multiple strings sequentially (or the same string repeatedly). Any caller that relies on containsAnsi in a loop may intermittently miss ANSI-containing text.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
3 issues found across 9 files
Confidence score: 3/5
- Intermittent false negatives are possible in
containsAnsibecauseANSI_REGEXis stateful with/gandlastIndexcan skip matches across calls, which could cause user-visible detection errors. - Use of a custom
SDKToolPartwithunknowncasting insrc/hooks/ansi-stripper/hook.tsloosens type safety and may mask SDK incompatibilities at runtime, adding uncertainty to behavior. - Given the medium severity of these issues, there’s some risk of regressions, though the problems look fixable and localized.
- Pay close attention to
src/hooks/ansi-stripper/strip-ansi.ts,src/hooks/ansi-stripper/hook.ts,src/tools/hashline-edit/tools.ts- regex statefulness and SDK type workarounds.
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/hooks/ansi-stripper/strip-ansi.ts">
<violation number="1" location="src/hooks/ansi-stripper/strip-ansi.ts:1">
P1: Stateful global regular expression causes intermittent false negatives in `containsAnsi`. The `/g` flag makes the regex stateful via `lastIndex`. When `ANSI_REGEX.test(text)` finds a match, `lastIndex` is updated, causing subsequent calls to skip past matches, resulting in false negatives.</violation>
</file>
<file name="src/tools/hashline-edit/tools.ts">
<violation number="1" location="src/tools/hashline-edit/tools.ts:182">
P2: Custom agent: **Opencode Compatibility**
The defensive runtime check for `context.metadata` is redundant. The `anomalyco/opencode` SDK strictly defines and guarantees `metadata` as a required function on `ToolContext`.</violation>
</file>
<file name="src/hooks/ansi-stripper/hook.ts">
<violation number="1" location="src/hooks/ansi-stripper/hook.ts:10">
P1: Custom agent: **Opencode Compatibility**
The PR defines a custom `SDKToolPart` interface to bypass the official SDK types, casting with `unknown` and loosely accessing `state.output` and `state.error`. According to the `@opencode-ai/sdk` types, you should use `ToolPart` (or `Extract<Part, { type: "tool" }>`) and narrow the type using the `state.status` discriminant (e.g., `status === "completed"` for `output`, or `status === "error"` for `error`).</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
src/tools/hashline-edit/tools.ts
Outdated
| if ("metadata" in context && typeof context.metadata === "function") { | ||
| context.metadata(meta) | ||
| } |
There was a problem hiding this comment.
P2: Custom agent: Opencode Compatibility
The defensive runtime check for context.metadata is redundant. The anomalyco/opencode SDK strictly defines and guarantees metadata as a required function on ToolContext.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tools/hashline-edit/tools.ts, line 182:
<comment>The defensive runtime check for `context.metadata` is redundant. The `anomalyco/opencode` SDK strictly defines and guarantees `metadata` as a required function on `ToolContext`.</comment>
<file context>
@@ -179,7 +179,9 @@ Use \\n in text to represent literal newlines.`,
}
- context.metadata(meta)
+ if ("metadata" in context && typeof context.metadata === "function") {
+ context.metadata(meta)
+ }
</file context>
| if ("metadata" in context && typeof context.metadata === "function") { | |
| context.metadata(meta) | |
| } | |
| context.metadata(meta) |
734b419 to
a44db68
Compare
…quences from tool results Strip ANSI escape codes from tool_result parts in messages.transform to prevent garbled output in the LLM context window. The hook processes all tool parts and cleans their state.output and state.error fields. Closes code-yeongyu#1796
…e SDK ToolPart types Address review feedback (identified by cubic): - Use non-global regex in containsAnsi to avoid lastIndex statefulness - Replace custom SDKToolPart interface with SDK ToolPart type - Use state.status discriminant for type-safe field access
… spread EventState interface gained new required fields; the inline literal in the session.status test was missing them, causing type errors and runtime failures.
d892195 to
604a1d3
Compare
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/config/schema/hooks.ts">
<violation number="1" location="src/config/schema/hooks.ts:52">
P1: Accidental deletion of 'hashline-edit-diff-enhancer' from HookNameSchema enum will break user configurations that disable this hook and create inconsistency with existing documentation and schema files.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| "write-existing-file-guard", | ||
| "anthropic-effort", | ||
| "hashline-read-enhancer", | ||
| "ansi-stripper", |
There was a problem hiding this comment.
P1: Accidental deletion of 'hashline-edit-diff-enhancer' from HookNameSchema enum will break user configurations that disable this hook and create inconsistency with existing documentation and schema files.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/config/schema/hooks.ts, line 52:
<comment>Accidental deletion of 'hashline-edit-diff-enhancer' from HookNameSchema enum will break user configurations that disable this hook and create inconsistency with existing documentation and schema files.</comment>
<file context>
@@ -49,7 +49,6 @@ export const HookNameSchema = z.enum([
"anthropic-effort",
"hashline-read-enhancer",
- "hashline-edit-diff-enhancer",
"ansi-stripper",
])
</file context>
Summary
Closes #1796
Add a new
ansi-strippertransform hook that strips ANSI escape sequences (color codes, cursor movement, etc.) from tool result outputs before they enter the LLM context window.Problem
Tool outputs (e.g., from Bash, test runners, build tools) often contain ANSI escape sequences for terminal coloring. These raw escape codes waste context window tokens and can confuse the model's understanding of the output.
Solution
src/hooks/ansi-stripper/strip-ansi.ts— Pure function to strip ANSI escape sequences using a comprehensive regex covering SGR, cursor movement, 256-color, and RGB codessrc/hooks/ansi-stripper/hook.ts— Transform hook that iterates over alltool/tool_useparts in messages and strips ANSI fromstate.outputandstate.errorfieldssrc/hooks/ansi-stripper/index.ts— Barrel exportsrc/hooks/ansi-stripper/strip-ansi.test.ts— 11 tests covering basic colors, bold/underline, 256-color, RGB, cursor movement, multi-sequence lines, and edge casesRegistration
"ansi-stripper"toHookNameSchemainsrc/config/schema/hooks.tssrc/plugin/hooks/create-transform-hooks.tswithsafeCreateHook+isHookEnabledgatesrc/plugin/messages-transform.tsafterthinkingBlockValidatorsrc/hooks/index.tsThe hook is enabled by default and can be disabled via
disabled_hooks: ["ansi-stripper"]in config.Testing
Summary by cubic
Strips ANSI escape sequences from tool outputs during messages.transform to reduce token waste and prevent garbled text in the LLM context.
New Features
Bug Fixes
Written for commit e1c56ca. Summary will update on new commits.