Skip to content

fix: strip ANSI escape codes from session persistence hooks#684

Merged
affaan-m merged 1 commit into
mainfrom
fix/strip-ansi-session-hooks
Mar 20, 2026
Merged

fix: strip ANSI escape codes from session persistence hooks#684
affaan-m merged 1 commit into
mainfrom
fix/strip-ansi-session-hooks

Conversation

@affaan-m

@affaan-m affaan-m commented Mar 20, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #642

Windows terminals emit control sequences (\x1b[H, \x1b[2J, \x1b[3J) that leaked into session.tmp files and were injected verbatim into Claude's context on the next session start, making content unreadable.

  • scripts/lib/utils.js: Added stripAnsi() utility that handles all ANSI escape families — CSI sequences (cursor movement, erase, colors), OSC sequences (window titles, hyperlinks), charset selection, and bare ESC sequences
  • scripts/hooks/session-end.js: Apply stripAnsi() when extracting user message text from the JSONL transcript (primary fix — prevents ANSI from being persisted)
  • scripts/hooks/session-start.js: Apply stripAnsi() before injecting session content into Claude's context (safety net for existing contaminated session files)
  • tests/lib/utils.test.js: 11 new tests covering SGR colors, cursor movement, screen clear, erase line, OSC, charset selection, DEC private modes, mixed sequences, non-string input, and clean passthrough

Test plan

  • node tests/lib/utils.test.js — 169/169 pass (11 new stripAnsi tests)
  • node tests/run-all.js — 1430/1432 pass (2 pre-existing failures unrelated to this PR)
  • Manual: verify on Windows that session.tmp files no longer contain escape codes after a session

Summary by cubic

Strip ANSI escape codes from session persistence to stop terminal sequences from polluting saved summaries and Claude context. Fixes garbled text on Windows.

  • Bug Fixes
    • Added stripAnsi() in scripts/lib/utils.js (handles CSI, OSC, charset, bare ESC).
    • Applied in scripts/hooks/session-end.js when extracting user messages.
    • Applied in scripts/hooks/session-start.js before injecting previous session content.
    • Added tests covering common sequences and edge cases.

Written for commit dfd2860. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Session summaries now properly remove terminal formatting codes from user messages and displayed previous session content, ensuring cleaner output and data storage.
  • Tests

    • Added comprehensive test coverage for terminal code sanitization across various formatting sequences.

Windows terminals emit control sequences (cursor movement, screen
clearing) that leaked into session.tmp files and were injected
verbatim into Claude's context on the next session start.

Add a comprehensive stripAnsi() to utils.js that handles CSI, OSC,
charset selection, and bare ESC sequences. Apply it in session-end.js
(when extracting user messages from the transcript) and in
session-start.js (safety net before injecting session content).
@ecc-tools

ecc-tools Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Analyzing 5000 commits...

@coderabbitai

coderabbitai Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8695bba4-dc4e-4cde-981f-881fc2b629f4

📥 Commits

Reviewing files that changed from the base of the PR and between 07f6156 and dfd2860.

📒 Files selected for processing (4)
  • scripts/hooks/session-end.js
  • scripts/hooks/session-start.js
  • scripts/lib/utils.js
  • tests/lib/utils.test.js

📝 Walkthrough

Walkthrough

Adds a new stripAnsi() utility function that removes ANSI escape sequences from strings using regex pattern matching. Applies this function in session-start and session-end hooks to prevent terminal formatting codes from contaminating stored and displayed session summaries.

Changes

Cohort / File(s) Summary
Utility Function Addition
scripts/lib/utils.js
Introduces stripAnsi(str) function that removes ANSI escape codes (CSI, OSC, charset selection, and bare ESC sequences) and exports it as a new string sanitization utility.
Hook Updates
scripts/hooks/session-start.js, scripts/hooks/session-end.js
Applies stripAnsi() to session content in session-start hook output and to user message excerpts in session-end hook summary extraction, ensuring no ANSI codes appear in stored/displayed summaries.
Test Coverage
tests/lib/utils.test.js
Adds comprehensive test suite for stripAnsi() covering SGR colors, cursor movement, erase sequences, OSC sequences, charset selection, mixed ANSI codes, DEC private modes, non-string inputs, and strings without ANSI codes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Hop! The terminal codes must flee,
ANSI sequences wild and free,
With stripAnsi in our toolkit bright,
Sessions now read clear and right!
No more cryptic escape codes in sight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: stripping ANSI escape codes from session persistence hooks to address issue #642.
Linked Issues check ✅ Passed All objectives from issue #642 are met: stripAnsi() utility prevents persistence of ANSI codes, session-end.js sanitizes extracted transcripts, and session-start.js sanitizes loaded content before injection.
Out of Scope Changes check ✅ Passed All changes are directly related to addressing ANSI escape sequence issues in session persistence; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/strip-ansi-session-hooks
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ecc-tools

ecc-tools Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Analysis Complete

Generated ECC bundle from 500 commits | Confidence: 100%

View Pull Request #685

Repository Profile
Attribute Value
Language JavaScript
Framework Not detected
Commit Convention conventional
Test Directory separate
Detected Workflows (8)
Workflow Description
feature-development Standard feature implementation workflow
add-new-skill Adds a new skill to the codebase, following documentation and cross-harness conventions.
add-new-agent Registers a new agent for code review, build, or problem resolution, with documentation and catalog updates.
add-new-command Introduces a new slash command for user workflows, with documentation and agent/skill linkage.
add-new-language-support Adds support for a new programming language, including agents, rules, commands, and tests.
Generated Instincts (16)
Domain Count
git 2
code-style 3
testing 3
workflow 8

After merging, import with:

/instinct-import .claude/homunculus/instincts/inherited/everything-claude-code-instincts.yaml

Files

  • .claude/ecc-tools.json
  • .claude/skills/everything-claude-code/SKILL.md
  • .agents/skills/everything-claude-code/SKILL.md
  • .agents/skills/everything-claude-code/agents/openai.yaml
  • .claude/identity.json
  • .codex/config.toml
  • .codex/AGENTS.md
  • .codex/agents/explorer.toml
  • .codex/agents/reviewer.toml
  • .codex/agents/docs-researcher.toml
  • .claude/homunculus/instincts/inherited/everything-claude-code-instincts.yaml
  • .claude/rules/everything-claude-code-guardrails.md
  • .claude/research/everything-claude-code-research-playbook.md
  • .claude/team/everything-claude-code-team-config.json
  • .claude/enterprise/controls.md
  • .claude/commands/feature-development.md
  • .claude/commands/add-new-skill.md
  • .claude/commands/add-new-agent.md

ECC Tools | Everything Claude Code

@greptile-apps

greptile-apps Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug (#642) where Windows terminal control sequences (\x1b[H, \x1b[2J, \x1b[3J) leaked into session.tmp files and were subsequently injected verbatim into Claude's context on the next session start, rendering the session summary unreadable.

Changes:

  • scripts/lib/utils.js — Adds a new stripAnsi() utility covering CSI sequences (colors, cursor movement, erase), OSC sequences (window titles, hyperlinks), charset selection (\x1b(B), and bare-ESC + uppercase letter sequences. A pre-existing, more limited stripAnsi local to scripts/skill-create-output.js was not consolidated with this new centralised version, leaving duplicate implementations.
  • scripts/hooks/session-end.js — Applies stripAnsi() to user message text at the point of extraction (primary fix, prevents contamination at the source).
  • scripts/hooks/session-start.js — Applies stripAnsi() to session file content before injecting into Claude's context (safety net for already-contaminated files).
  • tests/lib/utils.test.js — 11 new tests covering all documented sequence families including the exact sequences from the issue report.

The fix is well-structured with a defence-in-depth approach (strip at write and at read). The regex is correct for the stated scope, with a minor gap: bare-ESC + lowercase letter sequences (e.g. \x1bc for RIS) are not stripped since the last alternation uses [A-Z] only.

Confidence Score: 4/5

  • Safe to merge — targeted fix for a well-defined bug with defence-in-depth sanitisation and solid test coverage; minor regex gap doesn't affect the reported issue.
  • The logic is correct for all sequences mentioned in the issue. The two minor gaps (bare-ESC lowercase sequences unstripped, duplicate stripAnsi in skill-create-output.js not consolidated) are non-blocking style concerns that don't affect correctness for the reported Windows terminal sequences. Tests pass and cover the primary scenarios.
  • scripts/lib/utils.js — the stripAnsi regex bare-ESC alternation ([A-Z]) and the un-consolidated duplicate in scripts/skill-create-output.js are worth a follow-up.

Important Files Changed

Filename Overview
scripts/lib/utils.js Adds a stripAnsi() utility function with a comprehensive regex covering CSI, OSC, charset selection, and bare-ESC sequences. Minor gap: the bare-ESC alternation only covers uppercase letters, leaving sequences like \x1bc (RIS) unstripped.
scripts/hooks/session-end.js Primary fix: applies stripAnsi() to user message text before persisting it to session.tmp, preventing ANSI escape codes from leaking into saved context. Change is minimal and well-placed.
scripts/hooks/session-start.js Safety-net fix: strips ANSI from session file content before injecting into Claude's context, protecting against pre-existing contaminated files. Clean, single-line change with no side effects.
tests/lib/utils.test.js Adds 11 focused tests covering all documented ANSI sequence families, the exact sequences from issue #642, non-string input guards, and clean passthrough. Coverage is thorough for the stated scope.

Sequence Diagram

sequenceDiagram
    participant Terminal as Windows Terminal
    participant Hook as session-end.js
    participant Utils as utils.js (stripAnsi)
    participant File as session.tmp
    participant Start as session-start.js
    participant Claude as Claude Context

    Terminal->>Hook: JSONL transcript (may contain ANSI in user messages)
    Hook->>Utils: stripAnsi(text)
    Utils-->>Hook: cleaned text (ANSI removed)
    Hook->>File: write cleaned session summary

    Note over File: Previously: ANSI codes persisted here

    File->>Start: readFile(latest.path)
    Start->>Utils: stripAnsi(content) [safety net]
    Utils-->>Start: cleaned content
    Start->>Claude: output("Previous session summary:\n" + cleanedContent)

    Note over Claude: Context is now free of ANSI escape sequences
Loading

Comments Outside Diff (1)

  1. scripts/skill-create-output.js, line 50-53 (link)

    P2 Duplicate stripAnsi not consolidated with the new utility

    A private stripAnsi already existed here (line 50–53) that only strips SGR color codes (\x1b\[[0-9;]*m). Now that a more comprehensive version lives in scripts/lib/utils.js, this local copy could be removed in favour of the centralised one to avoid divergence:

    const { stripAnsi } = require('./lib/utils');

    This is non-blocking but worth a follow-up to prevent the two implementations from drifting.

Last reviewed commit: "fix: strip ANSI esca..."

Comment thread scripts/lib/utils.js
function stripAnsi(str) {
if (typeof str !== 'string') return '';
// eslint-disable-next-line no-control-regex
return str.replace(/\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|\([A-Z]|[A-Z])/g, '');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Bare ESC + lowercase letter sequences not stripped

The last alternation [A-Z] only covers uppercase bare-ESC sequences (e.g. \x1bM reverse index, \x1bD index). Lowercase variants such as \x1bc (RIS — Reset to Initial State, a commonly emitted sequence on some Windows terminal emulators) would not be matched and would remain in the cleaned output. Extending the character class to [A-Za-z] would close this gap:

Suggested change
return str.replace(/\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|\([A-Z]|[A-Z])/g, '');
return str.replace(/\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|\([A-Za-z]|[A-Za-z])/g, '');

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dfd2860c74

ℹ️ 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".

Comment thread scripts/lib/utils.js
function stripAnsi(str) {
if (typeof str !== 'string') return '';
// eslint-disable-next-line no-control-regex
return str.replace(/\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|\([A-Z]|[A-Z])/g, '');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match non-letter CSI terminators in stripAnsi

stripAnsi() only removes CSI sequences whose final byte is [A-Za-z], so valid control sequences like bracketed-paste markers (\x1b[200~ / \x1b[201~) are left intact. In terminals that emit those markers around pasted input, the pasted prompt will still be written into session.tmp and reinjected on the next session-start, which means this fix does not actually eliminate a common class of leaked terminal escape codes.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files

Prompt for AI agents (unresolved 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="scripts/lib/utils.js">

<violation number="1" location="scripts/lib/utils.js:482">
P2: The ANSI regex is too narrow and misses valid escape sequences (e.g. CSI ending in `~` and charset selectors like `ESC(0`), so control codes can still persist.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread scripts/lib/utils.js
function stripAnsi(str) {
if (typeof str !== 'string') return '';
// eslint-disable-next-line no-control-regex
return str.replace(/\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|\([A-Z]|[A-Z])/g, '');

@cubic-dev-ai cubic-dev-ai Bot Mar 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The ANSI regex is too narrow and misses valid escape sequences (e.g. CSI ending in ~ and charset selectors like ESC(0), so control codes can still persist.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/lib/utils.js, line 482:

<comment>The ANSI regex is too narrow and misses valid escape sequences (e.g. CSI ending in `~` and charset selectors like `ESC(0`), so control codes can still persist.</comment>

<file context>
@@ -464,6 +464,24 @@ function countInFile(filePath, pattern) {
+function stripAnsi(str) {
+  if (typeof str !== 'string') return '';
+  // eslint-disable-next-line no-control-regex
+  return str.replace(/\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|\([A-Z]|[A-Z])/g, '');
+}
+
</file context>
Suggested change
return str.replace(/\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|\([A-Z]|[A-Z])/g, '');
return str.replace(/\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\)|\([0-~]|[@-Z\\-_])/g, '');
Fix with Cubic

@affaan-m affaan-m merged commit 28de7cc into main Mar 20, 2026
5 of 40 checks passed
@affaan-m affaan-m deleted the fix/strip-ansi-session-hooks branch March 20, 2026 13:51
FrancescoRosciano pushed a commit to FRosciano-Mambo/everything-claude-code that referenced this pull request Jun 1, 2026
…#642) (affaan-m#684)

Windows terminals emit control sequences (cursor movement, screen
clearing) that leaked into session.tmp files and were injected
verbatim into Claude's context on the next session start.

Add a comprehensive stripAnsi() to utils.js that handles CSI, OSC,
charset selection, and bare ESC sequences. Apply it in session-end.js
(when extracting user messages from the transcript) and in
session-start.js (safety net before injecting session content).
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.

SessionStart hook injects ANSI escape codes from session.tmp on Windows

1 participant