CC 2.1.97 compatibility + new patches#671
CC 2.1.97 compatibility + new patches#671FullfocusApp wants to merge 1 commit intoPiebald-AI:mainfrom
Conversation
…urns, context limit) CC 2.1.97 introduced React compiler memo caching (useMemoCache) which flattens createElement nesting, and split the CLAUDE.md reader into async/sync functions. This broke multiple patches. All fixed: - patchesAppliedIndication: memo-cached version display fallback - userMessageDisplay: new simplified createElement pattern (template literal) - agentsMd: detect native AGENTS.md support, async reader fallback - helpers/findBoxComponent: Method 4b anchor-based detection - Multiple other patches: extended regex ranges for longer memo-cached props New patches: - garnetLoom: disable tengu_garnet_loom (Opus→Sonnet auto-downgrade) - maxAgentTurns: CLAUDE_CODE_MAX_AGENT_TURNS env var override - contextLimit: targeted TV() function patch for CLAUDE_CODE_CONTEXT_LIMIT All 221 tests pass. 15/15 patches verified on live CC 2.1.97. Built with love by Layla & Abdullahi
📝 WalkthroughWalkthroughThis PR adds support for Claude Code (CC) 2.1.97+ by introducing version-specific regex patterns and fallback matching logic across 15+ patch modules, adds two new configurable feature toggles ( Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/patches/autoAcceptPlanMode.ts (1)
52-60:⚠️ Potential issue | 🟡 MinorTighten wrapped
onChangematching to prevent wrong handler capture.The wrapped-form matcher can match non-pass-through arrows and still extract a handler name. Add a backreference so only
onChange:(x)=>fn(x)shapes are accepted.🔧 Proposed fix
- const onChangeMatch = - afterReady.match(/onChange:([$\w]+),onCancel/) || - afterReady.match(/onChange:\([$\w]+\)=>([$\w]+)\([$\w]+\),onCancel/); - if (!onChangeMatch) { + const directOnChangeMatch = afterReady.match(/onChange:([$\w]+),onCancel/); + const wrappedOnChangeMatch = afterReady.match( + /onChange:\(([$\w]+)\)=>([$\w]+)\(\1\),onCancel/ + ); + const acceptFuncName = directOnChangeMatch?.[1] ?? wrappedOnChangeMatch?.[2]; + if (!acceptFuncName) { console.error('patch: autoAcceptPlanMode: failed to find onChange handler'); return null; } - - const acceptFuncName = onChangeMatch[1];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/patches/autoAcceptPlanMode.ts` around lines 52 - 60, The second alternation in the onChange matcher can currently capture non-pass-through arrows; tighten it by requiring the called identifier to be the same as the arrow param via a backreference and handle the different capture index: change the second regex to /onChange:\(([$\w]+)\)=>([$\w]+)\(\1\),onCancel/ and then update the acceptFuncName assignment to pick the handler from either capture (e.g. acceptFuncName = onChangeMatch[1] || onChangeMatch[2]) so afterReady, onChangeMatch and acceptFuncName work correctly.
🧹 Nitpick comments (5)
src/patches/themes.ts (1)
23-24: Please remove newly added inline comments in this file.These new comments violate the TS/JS style rule for this repository.
As per coding guidelines:
**/*.{js,jsx,ts,tsx}— "Do not add comments unless explicitly requested".Also applies to: 75-75
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/patches/themes.ts` around lines 23 - 24, Remove the newly added inline comments that start with "CC ≥2.1.97: assignment form XZY={auto:\"Auto...\",dark:\"Dark...\",...}" and "CC <2.1.97: return form return{auto:\"Auto...\",dark:\"Dark...,...}" as well as the similar comment added around line 75; delete those comment lines so the file contains no new inline comments, run the linter/format step to ensure it meets the repo rule that disallows adding comments in /*.{js,jsx,ts,tsx} files, and leave the surrounding code unchanged.src/patches/autoAcceptPlanMode.ts (1)
50-51: Remove newly added inline comments in this patch module.These new comments should be dropped to stay consistent with repo conventions.
As per coding guidelines, "Do not add comments unless explicitly requested."
Also applies to: 72-73
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/patches/autoAcceptPlanMode.ts` around lines 50 - 51, Remove the newly added inline comment blocks that start with "// CC ≥2.1.97: onChange:J6,onCancel (direct reference)" and "// CC <2.1.97: onChange:(X)=>FUNC(X),onCancel (arrow wrapper)" as well as the similar comment pair at the later location (lines referenced 72-73) in src/patches/autoAcceptPlanMode.ts; locate these comment strings in the module and delete them so the file conforms to the repo convention of not adding inline comments unless explicitly requested, leaving the surrounding code (functions/exports in autoAcceptPlanMode.ts) unchanged.src/patches/agentsMd.ts (1)
155-169: Tighten the async reader match; the 500-char lookback is too heuristic.This path picks the last
async functionand lastcatch(...)found in an arbitrary window before{info:null,includePaths:[]}. A nearby unrelated async/catch block would still satisfy that scan and move the injection point. I'd match the full minifiedreadFile(...,{encoding:"utf-8"}) ... catch(...) { return ..., {info:null,includePaths:[]} }shape directly instead of inferring it from proximity.Based on learnings, patches in
src/patches/target original minified Claude Code installs, so patch regexes should be strict and match the minified format exactly rather than rely on formatting-insensitive heuristics.Also applies to: 181-203
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/patches/agentsMd.ts` around lines 155 - 169, The current scan using infoNullStr, a fixed 500-char lookback and .readFile proximity (vars infoNullIdx, searchStart, lookback) is too lax and can match unrelated async/catch blocks; replace this heuristic with a strict regex that matches the exact minified readFile + catch + return + `{info:null,includePaths:[]}` shape (e.g. a pattern that captures readFile\([^)]*\s*,\s*{encoding:"utf-8"}\)[^}]*catch\([^)]*\)\s*{[^}]*return[^}]*{info:null,includePaths:\[\]}\s*} ) and use that to locate the injection point instead of file.indexOf(infoNullStr) with a 500-char slice; update the same logic used around the other region flagged (lines 181-203) to use the same strict regex and remove the searchStart/lookback heuristic so only exact minified function shapes are matched.src/patches/patchesAppliedIndication.ts (1)
233-240: Use the resolvedreactVarin the Fragment fallback.Lines 233-240 already know which React namespace to target, but the regex accepts any
X.Fragmentin the 5k scan window. That makes the fallback looser than the injected code it supports and can splice into the wrong subtree if another object exposesFragment.Suggested fix
const searchRegion = fileContents.slice(startIndex, startIndex + 5000); - const fragmentPattern = - /\.createElement\(([$\w]+)\.Fragment,null,(?:[$\w]+,)*[$\w]+\)/; + const fragmentPattern = new RegExp( + `\\.createElement\\(${escapeIdent(reactVar)}\\.Fragment,null,(?:[$\\w]+,)*[$\\w]+\\)` + ); const fragmentMatch = searchRegion.match(fragmentPattern);Based on learnings: In patches under
src/patches/, regexes should be strict and match the minified Claude Code bundle exactly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/patches/patchesAppliedIndication.ts` around lines 233 - 240, The regex for matching Fragment createElement is too permissive and can match other objects' Fragment; update the pattern in patchesAppliedIndication.ts to use the resolved reactVar instead of the generic capture: build a dynamic RegExp (using the reactVar variable, properly escaped) to match `\\.createElement\\(${reactVar}\\.Fragment,null,(?:[$\\w]+,)*[$\\w]+\\)` against searchRegion so fragmentMatch only succeeds for the actual React namespace, then keep using fragmentMatch and insertionIndex as before.src/patches/hideStartupBanner.ts (1)
31-34: Use the shared debug logger for patch misses.
console.errorbypasses the repo's normal diagnostics path. Please route this throughdebug()and keep thenullfallback.As per coding guidelines "Implement error handling with try-catch blocks, log errors with debug(), and return graceful fallbacks".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/patches/hideStartupBanner.ts` around lines 31 - 34, Replace the console.error call in the hideStartupBanner logic with the shared debug() logger and keep the null fallback; ensure the debug symbol is imported/available in this module. If the surrounding code isn't already protected, wrap the banner-matching code in a try-catch and on failure call debug('patch: hideStartupBanner: failed to find startup banner pattern', err) (or similar) and return null. Target the hideStartupBanner function and its error path so all diagnostics use debug() instead of console.error().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/patches/agentsMd.ts`:
- Around line 75-77: The branch that returns early when
file.includes('didReroute') && file.includes('AGENTS.md') is too broad — change
it to consult config.settings.claudeMdAltNames instead of short-circuiting on
AGENTS.md alone: only return the original bundle if claudeMdAltNames is empty or
every altName in config.settings.claudeMdAltNames is already present in the file
(e.g., via file.includes(altName)); otherwise proceed to inject the additional
fallbacks. Reference the existing variables/strings 'file', 'didReroute',
'AGENTS.md', and 'config.settings.claudeMdAltNames' when locating and updating
the conditional.
In `@src/patches/contextLimit.ts`:
- Around line 39-40: The replacement string newStr should validate
CLAUDE_CODE_CONTEXT_LIMIT before converting and returning it: wrap the
conversion in a try-catch, use a safe parse (e.g., parseInt or Number) and check
for NaN/empty string, call debug() on parse errors, and fall back to the
original defaultVar; update the code that sets newStr (and/or the returned
expression) so it performs the guarded conversion and returns defaultVar when
the env value is invalid instead of allowing Number('') or Number('abc') to
collapse the context window.
In `@src/patches/helpers.ts`:
- Around line 346-354: The current logic uses region.match(...) which returns
the first createElement(...) hit in the ±2000-char region and can pick a
candidate that is farther from headerAnchor.index; change it to find all matches
(use a global regex via matchAll or exec loop on the same pattern) over region,
compute the absolute distance between each match's start (adjusted by
searchStart) and headerAnchor.index, pick the match with the smallest distance,
and return its capture group (the box component name) instead of the first
match; update references to headerAnchor, fileContents, region, and boxUsage
accordingly.
In `@src/patches/maxAgentTurns.ts`:
- Line 35: Validate and sanitize the env values before composing newText: read
process.env.CLAUDE_CODE_MAX_AGENT_TURNS and CLAUDE_CODE_MAX_AGENT_TURNS_LIMIT
into local variables, parse them to numbers, then check Number.isFinite(value)
&& value > 0 and fall back to 20 and 100 respectively if invalid; wrap the
parsing/validation in a try-catch, call debug() with the error and which env var
was invalid, and only then build newText (the template using var1 and var2) with
the validated/fallback numeric values so bad or empty env strings can't produce
0/NaN in the bundle.
- Around line 23-44: The fallback loose regex in maxAgentTurns.ts (the
contextPattern/contextMatch branch that constructs var1/var2, oldText/newText
and calls showDiff) is unsafe and should be removed so we fail closed when the
primary match fails; update the logic so that if (!match || match.index ===
undefined) you immediately log the failure and return null (remove the
contextPattern/contextMatch handling and related variables var1, var2, oldText,
newText, idx, newFile and the showDiff call), and only perform the replacement
when the original strict match succeeds.
In `@src/patches/mcpStartup.ts`:
- Around line 42-47: The existing loose proximity regex stored in
hardcodedPattern (used by writeMcpNonBlocking in src/patches/mcpStartup.ts) can
false-positive if an unrelated "=!0" appears nearby; replace it with a stricter
regex that matches the exact minified assignment shape for
MCP_CONNECTION_NONBLOCKING (anchor to the identifier and its immediate "=!0" or
"=[^,]*!0" form and the MCP_CONNECTION_NONBLOCKING token, not just within 100
chars), update the hardcodedPattern variable accordingly, and apply the same
stricter pattern to the other occurrence around lines 112-114 to ensure we only
treat truly hardcoded MCP_CONNECTION_NONBLOCKING cases as already non-blocking.
In `@src/patches/sessionMemory.ts`:
- Around line 188-197: Remove the three inline comments in the flow block that
precedes the patch calls: delete the comments before the patchPastSessions,
patchTokenLimits, and patchUpdateThresholds calls so the code simply invokes
patchPastSessions(newFile), patchTokenLimits(newFile), and
patchUpdateThresholds(newFile) with their results assigned to pastResult,
limitsResult, and threshResult respectively; leave the logic and variable names
(newFile, pastResult, limitsResult, threshResult) untouched.
- Around line 188-198: The current handlers treat a null result from
patchPastSessions, patchTokenLimits, and patchUpdateThresholds as a benign
"skip", which hides real failures; change the patch functions to return a
tri-state result (e.g., { status: 'applied'|'notFound'|'failed', result?:
SessionFile, error?: Error }) and update the caller in writeSessionMemory to
only treat status === 'notFound' as non-fatal—if status === 'failed' propagate
or throw the error (or return a failure) and if status === 'applied' use the
provided result; reference patchPastSessions, patchTokenLimits,
patchUpdateThresholds and writeSessionMemory when making these changes.
In `@src/patches/themes.ts`:
- Around line 80-82: The prefix detection around objPrefix incorrectly uses
origObjText.startsWith('return') which can misclassify identifiers beginning
with "return..."; change the condition to origObjText.startsWith('return{') so
objPrefix is set to 'return' only when the text is exactly the return-object
form; update the expression that computes objPrefix (the variable named
objPrefix using origObjText) accordingly.
- Line 26: The current regex literal
/(?:return|[$\w]+\s*=)\{(?:(?:[$\w]+|"[^"]+"):"(?:[Aa]uto|[Dd]ark|[Ll]ight)[^"]*",?)+\}/
is too permissive because of the `\s*` before `=`; tighten it to match strict
minified form by replacing `[$\w]+\s*=` with `[$\w]+=` (and remove any other
`\s*` allowances in that same pattern) so it only matches the exact minified
output used by the patching logic.
In `@src/patches/tokenCountRounding.ts`:
- Around line 36-37: Remove the newly added inline comments in the
tokenCountRounding patch module: delete the // comment that begins "CC ≥2.1.97:
token count computed directly..." (near the tokenCount assignment around the "
tokens" template literal) as well as the other inline comments added at the
nearby locations referenced (the ones around lines 46-47 and 56-57); leave the
implementation and any surrounding code (e.g., the token count assignment and
any function or constant names in this module) unchanged, ensuring no other
formatting or logic is modified.
- Around line 66-69: Replace the console.error call in tokenCountRounding (the
branch that returns null when no token pattern is found) with the project logger
debug() and follow the patch error-handling convention: wrap the token
parsing/format-detection code in a try-catch (if not already), call debug() with
the same descriptive message and any caught error details/context, and then
return null as the graceful fallback; target the tokenCountRounding function and
the specific branch that currently logs "'patch: tokenCountRounding: cannot find
token count pattern in either newer or older CC format'".
- Around line 36-40: Update the m0 matcher so it only triggers on the
key:"tokens" branch and ensures the same assigned variable is used: change the
regex used to build m0 in tokenCountRounding.ts to include a nearby literal/key
anchor matching key:"tokens" (instead of a loose " tokens" text) and
back-reference the captured assigned variable (the group that matches the
left-hand variable, e.g., the second capture like ,([$\w]+)=) so the tokenCount
assignment must use that same variable; this will prevent m0 from matching other
nearby " tokens" strings before m1/m2 run.
In `@src/patches/userMessageDisplay.ts`:
- Around line 160-173: The current branch uses the result of
oldFile.match(patternOld) (the initial match) without verifying the required
capture groups, allowing partial matches to set createElementFn and messageVar
to undefined; update the logic around the initial match so you explicitly check
that match and match.index are defined AND that match[4] and at least one of
match[6], match[7], or match[8] are non-empty before assigning createElementFn
and messageVar; if those captures are missing or empty, fall back to performing
oldFile.match(pattern297) (as in the else branch) and use its captures (match[2]
and match[3]) instead.
- Around line 147-149: patternOld's regex uses two \b word-boundary anchors
which can hurt V8 performance; update the regex assigned to patternOld to
replace the first \b (after message.{0,150}?) with a literal delimiter that
matches minified output such as ',' (or another appropriate literal like ';' or
'}') and replace the second \b (after .{0,30} before the variable name) with a
literal delimiter such as ',' or ';' consistent with minified code; keep the
rest of the capture groups and quantifiers intact and ensure the chosen literals
are escaped if needed and reflect the actual minified structure so the pattern
still matches the intended createElement nesting cases.
In `@src/patches/voiceMode.ts`:
- Around line 104-106: The function patchConciseOutput currently logs an error
when it doesn't find the pattern even when that absence is expected; add an
optional boolean parameter (e.g., silent = false) to patchConciseOutput to
suppress the console.error when silent is true, update its internal logging to
check silent before emitting the error, and change the caller in voiceMode.ts
from patchConciseOutput(newFile) to patchConciseOutput(newFile, true) so
expected absences don't produce misleading error output; keep the default
behavior unchanged for other callers.
---
Outside diff comments:
In `@src/patches/autoAcceptPlanMode.ts`:
- Around line 52-60: The second alternation in the onChange matcher can
currently capture non-pass-through arrows; tighten it by requiring the called
identifier to be the same as the arrow param via a backreference and handle the
different capture index: change the second regex to
/onChange:\(([$\w]+)\)=>([$\w]+)\(\1\),onCancel/ and then update the
acceptFuncName assignment to pick the handler from either capture (e.g.
acceptFuncName = onChangeMatch[1] || onChangeMatch[2]) so afterReady,
onChangeMatch and acceptFuncName work correctly.
---
Nitpick comments:
In `@src/patches/agentsMd.ts`:
- Around line 155-169: The current scan using infoNullStr, a fixed 500-char
lookback and .readFile proximity (vars infoNullIdx, searchStart, lookback) is
too lax and can match unrelated async/catch blocks; replace this heuristic with
a strict regex that matches the exact minified readFile + catch + return +
`{info:null,includePaths:[]}` shape (e.g. a pattern that captures
readFile\([^)]*\s*,\s*{encoding:"utf-8"}\)[^}]*catch\([^)]*\)\s*{[^}]*return[^}]*{info:null,includePaths:\[\]}\s*}
) and use that to locate the injection point instead of
file.indexOf(infoNullStr) with a 500-char slice; update the same logic used
around the other region flagged (lines 181-203) to use the same strict regex and
remove the searchStart/lookback heuristic so only exact minified function shapes
are matched.
In `@src/patches/autoAcceptPlanMode.ts`:
- Around line 50-51: Remove the newly added inline comment blocks that start
with "// CC ≥2.1.97: onChange:J6,onCancel (direct reference)" and "// CC
<2.1.97: onChange:(X)=>FUNC(X),onCancel (arrow wrapper)" as well as the similar
comment pair at the later location (lines referenced 72-73) in
src/patches/autoAcceptPlanMode.ts; locate these comment strings in the module
and delete them so the file conforms to the repo convention of not adding inline
comments unless explicitly requested, leaving the surrounding code
(functions/exports in autoAcceptPlanMode.ts) unchanged.
In `@src/patches/hideStartupBanner.ts`:
- Around line 31-34: Replace the console.error call in the hideStartupBanner
logic with the shared debug() logger and keep the null fallback; ensure the
debug symbol is imported/available in this module. If the surrounding code isn't
already protected, wrap the banner-matching code in a try-catch and on failure
call debug('patch: hideStartupBanner: failed to find startup banner pattern',
err) (or similar) and return null. Target the hideStartupBanner function and its
error path so all diagnostics use debug() instead of console.error().
In `@src/patches/patchesAppliedIndication.ts`:
- Around line 233-240: The regex for matching Fragment createElement is too
permissive and can match other objects' Fragment; update the pattern in
patchesAppliedIndication.ts to use the resolved reactVar instead of the generic
capture: build a dynamic RegExp (using the reactVar variable, properly escaped)
to match
`\\.createElement\\(${reactVar}\\.Fragment,null,(?:[$\\w]+,)*[$\\w]+\\)` against
searchRegion so fragmentMatch only succeeds for the actual React namespace, then
keep using fragmentMatch and insertionIndex as before.
In `@src/patches/themes.ts`:
- Around line 23-24: Remove the newly added inline comments that start with "CC
≥2.1.97: assignment form XZY={auto:\"Auto...\",dark:\"Dark...\",...}" and "CC
<2.1.97: return form return{auto:\"Auto...\",dark:\"Dark...,...}" as well as the
similar comment added around line 75; delete those comment lines so the file
contains no new inline comments, run the linter/format step to ensure it meets
the repo rule that disallows adding comments in /*.{js,jsx,ts,tsx} files, and
leave the surrounding code unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 475c85b8-9182-415c-9315-d9a593e5a74d
📒 Files selected for processing (22)
src/defaultSettings.tssrc/patches/agentsMd.tssrc/patches/autoAcceptPlanMode.tssrc/patches/contextLimit.tssrc/patches/garnetLoom.tssrc/patches/helpers.tssrc/patches/hideStartupBanner.tssrc/patches/increaseFileReadLimit.tssrc/patches/index.tssrc/patches/maxAgentTurns.tssrc/patches/mcpStartup.tssrc/patches/patchesAppliedIndication.tssrc/patches/sessionMemory.tssrc/patches/statuslineUpdateThrottle.tssrc/patches/suppressRateLimitOptions.tssrc/patches/themes.tssrc/patches/thinkerFormat.tssrc/patches/tokenCountRounding.tssrc/patches/userMessageDisplay.tssrc/patches/voiceMode.tssrc/types.tssrc/ui/components/MiscView.tsx
| if (file.includes('didReroute') && file.includes('AGENTS.md')) { | ||
| // CC 2.1.97+ already has AGENTS.md fallback built in — no patch needed | ||
| return file; |
There was a problem hiding this comment.
Don't short-circuit on AGENTS.md alone.
This no-op ignores the actual altNames requested by the caller. If a user configures extra names like GEMINI.md or QWEN.md, this branch can return the original bundle unchanged as soon as CC ships any built-in AGENTS.md handling, so the additional fallbacks never get injected. src/patches/index.ts:812-818 shows this patch is driven by the full config.settings.claudeMdAltNames list.
Suggested fix
- if (file.includes('didReroute') && file.includes('AGENTS.md')) {
+ const hasBuiltInReroute =
+ file.includes('didReroute') &&
+ altNames.every(altName => file.includes(altName));
+ if (hasBuiltInReroute) {
// CC 2.1.97+ already has AGENTS.md fallback built in — no patch needed
return file;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (file.includes('didReroute') && file.includes('AGENTS.md')) { | |
| // CC 2.1.97+ already has AGENTS.md fallback built in — no patch needed | |
| return file; | |
| const hasBuiltInReroute = | |
| file.includes('didReroute') && | |
| altNames.every(altName => file.includes(altName)); | |
| if (hasBuiltInReroute) { | |
| // CC 2.1.97+ already has AGENTS.md fallback built in — no patch needed | |
| return file; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/patches/agentsMd.ts` around lines 75 - 77, The branch that returns early
when file.includes('didReroute') && file.includes('AGENTS.md') is too broad —
change it to consult config.settings.claudeMdAltNames instead of
short-circuiting on AGENTS.md alone: only return the original bundle if
claudeMdAltNames is empty or every altName in config.settings.claudeMdAltNames
is already present in the file (e.g., via file.includes(altName)); otherwise
proceed to inject the additional fallbacks. Reference the existing
variables/strings 'file', 'didReroute', 'AGENTS.md', and
'config.settings.claudeMdAltNames' when locating and updating the conditional.
| const oldStr = `return ${defaultVar}}`; | ||
| const newStr = `return Number(process.env.CLAUDE_CODE_CONTEXT_LIMIT??${defaultVar})}`; |
There was a problem hiding this comment.
Guard CLAUDE_CODE_CONTEXT_LIMIT before returning it.
Number('') resolves to 0 and Number('abc') resolves to NaN, so a bad env value can collapse the context window instead of preserving the original default.
Graceful fallback for invalid input
- const newStr = `return Number(process.env.CLAUDE_CODE_CONTEXT_LIMIT??${defaultVar})}`;
+ const newStr =
+ `return ((v=>Number.isInteger(v)&&v>0?v:${defaultVar})` +
+ `(Number(process.env.CLAUDE_CODE_CONTEXT_LIMIT)))}`As per coding guidelines "Implement error handling with try-catch blocks, log errors with debug(), and return graceful fallbacks".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const oldStr = `return ${defaultVar}}`; | |
| const newStr = `return Number(process.env.CLAUDE_CODE_CONTEXT_LIMIT??${defaultVar})}`; | |
| const oldStr = `return ${defaultVar}}`; | |
| const newStr = | |
| `return ((v=>Number.isInteger(v)&&v>0?v:${defaultVar})` + | |
| `(Number(process.env.CLAUDE_CODE_CONTEXT_LIMIT)))}` |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/patches/contextLimit.ts` around lines 39 - 40, The replacement string
newStr should validate CLAUDE_CODE_CONTEXT_LIMIT before converting and returning
it: wrap the conversion in a try-catch, use a safe parse (e.g., parseInt or
Number) and check for NaN/empty string, call debug() on parse errors, and fall
back to the original defaultVar; update the code that sets newStr (and/or the
returned expression) so it performs the guarded conversion and returns
defaultVar when the env value is invalid instead of allowing Number('') or
Number('abc') to collapse the context window.
| const searchStart = Math.max(0, headerAnchor.index - 2000); | ||
| const searchEnd = Math.min(fileContents.length, headerAnchor.index + 2000); | ||
| const region = fileContents.slice(searchStart, searchEnd); | ||
| const boxUsage = region.match( | ||
| /createElement\(([$\w]+),\{(?:flexDirection|gap|marginBottom)/ | ||
| ); | ||
| if (boxUsage) { | ||
| return boxUsage[1]; | ||
| } |
There was a problem hiding this comment.
Choose the Box candidate closest to the header anchor.
Lines 346-350 search a ±2000-char window, but region.match(...) returns the first layout-like createElement(...) in that whole slice. A matching call before the "Claude Code" anchor can win over the actual header Box wrapper, which then poisons every downstream ${boxComponent} insertion.
Suggested fix
if (headerAnchor && headerAnchor.index !== undefined) {
const searchStart = Math.max(0, headerAnchor.index - 2000);
const searchEnd = Math.min(fileContents.length, headerAnchor.index + 2000);
const region = fileContents.slice(searchStart, searchEnd);
- const boxUsage = region.match(
- /createElement\(([$\w]+),\{(?:flexDirection|gap|marginBottom)/
- );
- if (boxUsage) {
- return boxUsage[1];
+ const anchorOffset = headerAnchor.index - searchStart;
+ const boxUsages = Array.from(
+ region.matchAll(
+ /createElement\(([$\w]+),\{(?:flexDirection|gap|marginBottom)/g
+ )
+ );
+ const boxUsage =
+ boxUsages.find((match) => (match.index ?? 0) >= anchorOffset) ??
+ boxUsages.at(-1);
+ if (boxUsage) {
+ return boxUsage[1];
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/patches/helpers.ts` around lines 346 - 354, The current logic uses
region.match(...) which returns the first createElement(...) hit in the
±2000-char region and can pick a candidate that is farther from
headerAnchor.index; change it to find all matches (use a global regex via
matchAll or exec loop on the same pattern) over region, compute the absolute
distance between each match's start (adjusted by searchStart) and
headerAnchor.index, pick the match with the smallest distance, and return its
capture group (the box component name) instead of the first match; update
references to headerAnchor, fileContents, region, and boxUsage accordingly.
| if (!match || match.index === undefined) { | ||
| // Try simpler pattern with nearby context to verify correct 20,100 pair | ||
| const contextPattern = /([$\w]+)=20,([$\w]+)=100,[$\w]+,[$\w]+;/; | ||
| const contextMatch = oldFile.match(contextPattern); | ||
| if (!contextMatch || contextMatch.index === undefined) { | ||
| console.error('patch: maxAgentTurns: failed to find maxTurns constants'); | ||
| return null; | ||
| } | ||
|
|
||
| const var1 = contextMatch[1]; | ||
| const var2 = contextMatch[2]; | ||
| const oldText = `${var1}=20,${var2}=100`; | ||
| const newText = `${var1}=Number(process.env.CLAUDE_CODE_MAX_AGENT_TURNS??20),${var2}=Number(process.env.CLAUDE_CODE_MAX_AGENT_TURNS_LIMIT??100)`; | ||
|
|
||
| const idx = contextMatch.index; | ||
| const newFile = | ||
| oldFile.slice(0, idx) + | ||
| contextMatch[0].replace(oldText, newText) + | ||
| oldFile.slice(idx + contextMatch[0].length); | ||
|
|
||
| showDiff(oldFile, newFile, newText, idx, idx + oldText.length); | ||
| return newFile; |
There was a problem hiding this comment.
Fail closed here instead of using the loose fallback regex.
If the primary match misses, this fallback no longer proves you've found the subagent maxTurns constants. Rewriting the first incidental a=20,b=100,c,d; sequence in a minified bundle can patch unrelated logic.
Safer fix
const match = oldFile.match(pattern);
if (!match || match.index === undefined) {
- // Try simpler pattern with nearby context to verify correct 20,100 pair
- const contextPattern = /([$\w]+)=20,([$\w]+)=100,[$\w]+,[$\w]+;/;
- const contextMatch = oldFile.match(contextPattern);
- if (!contextMatch || contextMatch.index === undefined) {
- console.error('patch: maxAgentTurns: failed to find maxTurns constants');
- return null;
- }
-
- const var1 = contextMatch[1];
- const var2 = contextMatch[2];
- const oldText = `${var1}=20,${var2}=100`;
- const newText = `${var1}=Number(process.env.CLAUDE_CODE_MAX_AGENT_TURNS??20),${var2}=Number(process.env.CLAUDE_CODE_MAX_AGENT_TURNS_LIMIT??100)`;
-
- const idx = contextMatch.index;
- const newFile =
- oldFile.slice(0, idx) +
- contextMatch[0].replace(oldText, newText) +
- oldFile.slice(idx + contextMatch[0].length);
-
- showDiff(oldFile, newFile, newText, idx, idx + oldText.length);
- return newFile;
+ console.error('patch: maxAgentTurns: failed to find maxTurns constants');
+ return null;
}Based on learnings "In patches under src/patches/, assume code works with minified, original Claude Code installations. Patch regexes must be strict and match the minified format exactly (ignore formatting differences like whitespace/newlines)."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!match || match.index === undefined) { | |
| // Try simpler pattern with nearby context to verify correct 20,100 pair | |
| const contextPattern = /([$\w]+)=20,([$\w]+)=100,[$\w]+,[$\w]+;/; | |
| const contextMatch = oldFile.match(contextPattern); | |
| if (!contextMatch || contextMatch.index === undefined) { | |
| console.error('patch: maxAgentTurns: failed to find maxTurns constants'); | |
| return null; | |
| } | |
| const var1 = contextMatch[1]; | |
| const var2 = contextMatch[2]; | |
| const oldText = `${var1}=20,${var2}=100`; | |
| const newText = `${var1}=Number(process.env.CLAUDE_CODE_MAX_AGENT_TURNS??20),${var2}=Number(process.env.CLAUDE_CODE_MAX_AGENT_TURNS_LIMIT??100)`; | |
| const idx = contextMatch.index; | |
| const newFile = | |
| oldFile.slice(0, idx) + | |
| contextMatch[0].replace(oldText, newText) + | |
| oldFile.slice(idx + contextMatch[0].length); | |
| showDiff(oldFile, newFile, newText, idx, idx + oldText.length); | |
| return newFile; | |
| const match = oldFile.match(pattern); | |
| if (!match || match.index === undefined) { | |
| console.error('patch: maxAgentTurns: failed to find maxTurns constants'); | |
| return null; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/patches/maxAgentTurns.ts` around lines 23 - 44, The fallback loose regex
in maxAgentTurns.ts (the contextPattern/contextMatch branch that constructs
var1/var2, oldText/newText and calls showDiff) is unsafe and should be removed
so we fail closed when the primary match fails; update the logic so that if
(!match || match.index === undefined) you immediately log the failure and return
null (remove the contextPattern/contextMatch handling and related variables
var1, var2, oldText, newText, idx, newFile and the showDiff call), and only
perform the replacement when the original strict match succeeds.
| const var1 = contextMatch[1]; | ||
| const var2 = contextMatch[2]; | ||
| const oldText = `${var1}=20,${var2}=100`; | ||
| const newText = `${var1}=Number(process.env.CLAUDE_CODE_MAX_AGENT_TURNS??20),${var2}=Number(process.env.CLAUDE_CODE_MAX_AGENT_TURNS_LIMIT??100)`; |
There was a problem hiding this comment.
Guard invalid env values before writing them into the bundle.
Number('') becomes 0 and Number('abc') becomes NaN, so a typoed env var can disable or poison subagent turn limits instead of falling back to 20/100.
Graceful fallback for invalid input
- const newText = `${var1}=Number(process.env.CLAUDE_CODE_MAX_AGENT_TURNS??20),${var2}=Number(process.env.CLAUDE_CODE_MAX_AGENT_TURNS_LIMIT??100)`;
+ const newText =
+ `${var1}=((v=>Number.isInteger(v)&&v>0?v:20)(Number(process.env.CLAUDE_CODE_MAX_AGENT_TURNS))),` +
+ `${var2}=((v=>Number.isInteger(v)&&v>0?v:100)(Number(process.env.CLAUDE_CODE_MAX_AGENT_TURNS_LIMIT)))`;As per coding guidelines "Implement error handling with try-catch blocks, log errors with debug(), and return graceful fallbacks".
Also applies to: 50-50
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/patches/maxAgentTurns.ts` at line 35, Validate and sanitize the env
values before composing newText: read process.env.CLAUDE_CODE_MAX_AGENT_TURNS
and CLAUDE_CODE_MAX_AGENT_TURNS_LIMIT into local variables, parse them to
numbers, then check Number.isFinite(value) && value > 0 and fall back to 20 and
100 respectively if invalid; wrap the parsing/validation in a try-catch, call
debug() with the error and which env var was invalid, and only then build
newText (the template using var1 and var2) with the validated/fallback numeric
values so bad or empty env strings can't produce 0/NaN in the bundle.
| // CC ≥2.1.97: token count computed directly as H6=W&&!W.isIdle?W.progress?.tokenCount??0:O6+D | ||
| // Look for the tokenCount assignment near " tokens" (template literal or string) | ||
| const m0 = oldFile.match( | ||
| /(,([$\w]+)=)([$\w]+&&![$\w]+\.isIdle\?[$\w]+\.progress\?\.tokenCount\?\?0:[$\w]+\+[$\w]+)(,.{0,200} tokens)/ | ||
| ); |
There was a problem hiding this comment.
Anchor the new CC ≥2.1.97 matcher to the key:"tokens" branch.
Line 39 currently accepts any nearby " tokens" text and can patch the wrong
expression before m1/m2 get a chance. Please add a key:"tokens" anchor
(and ideally back-reference the same assigned var) to keep this strict.
Proposed fix
- /(,([$\w]+)=)([$\w]+&&![$\w]+\.isIdle\?[$\w]+\.progress\?\.tokenCount\?\?0:[$\w]+\+[$\w]+)(,.{0,200} tokens)/
+ /(,([$\w]+)=)([$\w]+&&![$\w]+\.isIdle\?[$\w]+\.progress\?\.tokenCount\?\?0:[$\w]+\+[$\w]+)(,.{0,1000}key:"tokens".{0,200},\2," tokens")/Based on learnings, “Patch regexes must be strict and match the minified
format exactly (ignore formatting differences like whitespace/newlines).”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/patches/tokenCountRounding.ts` around lines 36 - 40, Update the m0
matcher so it only triggers on the key:"tokens" branch and ensures the same
assigned variable is used: change the regex used to build m0 in
tokenCountRounding.ts to include a nearby literal/key anchor matching
key:"tokens" (instead of a loose " tokens" text) and back-reference the captured
assigned variable (the group that matches the left-hand variable, e.g., the
second capture like ,([$\w]+)=) so the tokenCount assignment must use that same
variable; this will prevent m0 from matching other nearby " tokens" strings
before m1/m2 run.
| console.error( | ||
| 'patch: tokenCountRounding: cannot find token count pattern in either newer or older CC format' | ||
| ); | ||
| return null; |
There was a problem hiding this comment.
Use the project logger (debug()) instead of console.error here.
Please align this branch with the patch error-handling convention: structured
logging via debug() and graceful fallback (which you already do with return null).
As per coding guidelines, “Implement error handling with try-catch blocks, log
errors with debug(), and return graceful fallbacks”.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/patches/tokenCountRounding.ts` around lines 66 - 69, Replace the
console.error call in tokenCountRounding (the branch that returns null when no
token pattern is found) with the project logger debug() and follow the patch
error-handling convention: wrap the token parsing/format-detection code in a
try-catch (if not already), call debug() with the same descriptive message and
any caught error details/context, and then return null as the graceful fallback;
target the tokenCountRounding function and the specific branch that currently
logs "'patch: tokenCountRounding: cannot find token count pattern in either
newer or older CC format'".
| // Pattern for CC ≤2.1.21: uses createElement nesting with wrap/text props | ||
| const patternOld = | ||
| /(No content found in user prompt message.{0,150}?\b)([$\w]+(?:\.default)?\.createElement.{0,30}\b[$\w]+(?:\.default)?\.createElement.{0,40}">.+?)?(([$\w]+(?:\.default)?\.createElement).{0,300})(\([$\w]+,(?:\{[^{}]+wrap:"wrap"\},([$\w]+)(?:\.trim\(\))?\)\)|\{text:([$\w]+)(?:,thinkingMetadata:[$\w]+)?\}\)\)?|\{text:([$\w]+)[^}]*\}\)\)?))/; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify word-boundary usage in this patch file
rg -n '\\\\b' src/patches/userMessageDisplay.tsRepository: Piebald-AI/tweakcc
Length of output: 44
🏁 Script executed:
sed -n '140,160p' src/patches/userMessageDisplay.tsRepository: Piebald-AI/tweakcc
Length of output: 1342
Replace \b word-boundary anchors in patternOld with literal character delimiters.
The regex at lines 147–149 contains two \b anchors:
- After
message.{0,150}?— use,or another literal char matching minified output - After
.{0,30}before the variable name — use,or;instead
Per patch regex guidelines, \b causes V8 performance issues with minified code. Use literal delimiters (,, ;, }, {) that match the actual minified structure.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/patches/userMessageDisplay.ts` around lines 147 - 149, patternOld's regex
uses two \b word-boundary anchors which can hurt V8 performance; update the
regex assigned to patternOld to replace the first \b (after message.{0,150}?)
with a literal delimiter that matches minified output such as ',' (or another
appropriate literal like ';' or '}') and replace the second \b (after .{0,30}
before the variable name) with a literal delimiter such as ',' or ';' consistent
with minified code; keep the rest of the capture groups and quantifiers intact
and ensure the chosen literals are escaped if needed and reflect the actual
minified structure so the pattern still matches the intended createElement
nesting cases.
| if (match && match.index !== undefined) { | ||
| createElementFn = match[4]; | ||
| messageVar = match[6] ?? match[7] ?? match[8]; | ||
| } else { | ||
| match = oldFile.match(pattern297); | ||
| if (!match || match.index === undefined) { | ||
| console.error( | ||
| 'patch: userMessageDisplay: failed to find user message display pattern' | ||
| ); | ||
| return null; | ||
| } | ||
| createElementFn = match[2]; | ||
| messageVar = match[3]; | ||
| } |
There was a problem hiding this comment.
Validate extracted captures before accepting patternOld match.
On Line 160, the branch only checks that match exists, not that match[4]
and match[6]/[7]/[8] were actually captured. If patternOld partially
matches, replacement can be built with invalid identifiers instead of falling
back to pattern297.
Proposed fix
- if (match && match.index !== undefined) {
- createElementFn = match[4];
- messageVar = match[6] ?? match[7] ?? match[8];
- } else {
+ const oldCreateElementFn = match?.[4];
+ const oldMessageVar = match?.[6] ?? match?.[7] ?? match?.[8];
+
+ if (match?.index !== undefined && oldCreateElementFn && oldMessageVar) {
+ createElementFn = oldCreateElementFn;
+ messageVar = oldMessageVar;
+ } else {
match = oldFile.match(pattern297);
- if (!match || match.index === undefined) {
+ const newCreateElementFn = match?.[2];
+ const newMessageVar = match?.[3];
+ if (
+ !match ||
+ match.index === undefined ||
+ !newCreateElementFn ||
+ !newMessageVar
+ ) {
console.error(
'patch: userMessageDisplay: failed to find user message display pattern'
);
return null;
}
- createElementFn = match[2];
- messageVar = match[3];
+ createElementFn = newCreateElementFn;
+ messageVar = newMessageVar;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (match && match.index !== undefined) { | |
| createElementFn = match[4]; | |
| messageVar = match[6] ?? match[7] ?? match[8]; | |
| } else { | |
| match = oldFile.match(pattern297); | |
| if (!match || match.index === undefined) { | |
| console.error( | |
| 'patch: userMessageDisplay: failed to find user message display pattern' | |
| ); | |
| return null; | |
| } | |
| createElementFn = match[2]; | |
| messageVar = match[3]; | |
| } | |
| const oldCreateElementFn = match?.[4]; | |
| const oldMessageVar = match?.[6] ?? match?.[7] ?? match?.[8]; | |
| if (match?.index !== undefined && oldCreateElementFn && oldMessageVar) { | |
| createElementFn = oldCreateElementFn; | |
| messageVar = oldMessageVar; | |
| } else { | |
| match = oldFile.match(pattern297); | |
| const newCreateElementFn = match?.[2]; | |
| const newMessageVar = match?.[3]; | |
| if ( | |
| !match || | |
| match.index === undefined || | |
| !newCreateElementFn || | |
| !newMessageVar | |
| ) { | |
| console.error( | |
| 'patch: userMessageDisplay: failed to find user message display pattern' | |
| ); | |
| return null; | |
| } | |
| createElementFn = newCreateElementFn; | |
| messageVar = newMessageVar; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/patches/userMessageDisplay.ts` around lines 160 - 173, The current branch
uses the result of oldFile.match(patternOld) (the initial match) without
verifying the required capture groups, allowing partial matches to set
createElementFn and messageVar to undefined; update the logic around the initial
match so you explicitly check that match and match.index are defined AND that
match[4] and at least one of match[6], match[7], or match[8] are non-empty
before assigning createElementFn and messageVar; if those captures are missing
or empty, fall back to performing oldFile.match(pattern297) (as in the else
branch) and use its captures (match[2] and match[3]) instead.
| // sotto_voce was removed in CC ≥2.1.97 — make non-fatal | ||
| const result = patchConciseOutput(newFile); | ||
| if (result) newFile = result; |
There was a problem hiding this comment.
Misleading error output when sotto_voce is expectedly missing.
When patchConciseOutput returns null on CC ≥2.1.97 (expected behavior), patchConciseOutput still logs console.error('patch: voiceMode: failed to find tengu_sotto_voce gate') at line 75. Users will see an error message for what is now expected behavior.
Consider suppressing the error when the pattern is legitimately absent:
🛠️ Suggested approach
Option 1: Accept a silent parameter:
-const patchConciseOutput = (file: string): string | null => {
+const patchConciseOutput = (file: string, silent = false): string | null => {
const pattern = /if\([$\w]+\("tengu_sotto_voce",!1\)\)/;
const match = file.match(pattern);
if (!match || match.index === undefined) {
- console.error('patch: voiceMode: failed to find tengu_sotto_voce gate');
+ if (!silent) {
+ console.error('patch: voiceMode: failed to find tengu_sotto_voce gate');
+ }
return null;
}Then call with patchConciseOutput(newFile, true) since failure is now expected.
Option 2: Move error logging to the caller when it's actually an error condition for older CC versions.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/patches/voiceMode.ts` around lines 104 - 106, The function
patchConciseOutput currently logs an error when it doesn't find the pattern even
when that absence is expected; add an optional boolean parameter (e.g., silent =
false) to patchConciseOutput to suppress the console.error when silent is true,
update its internal logging to check silent before emitting the error, and
change the caller in voiceMode.ts from patchConciseOutput(newFile) to
patchConciseOutput(newFile, true) so expected absences don't produce misleading
error output; keep the default behavior unchanged for other callers.
Summary
200000replace with targeted regex matching the exact context window functionTV(q,K)garnetLoom(disable tengu_garnet_loom auto-downgrade) andmaxAgentTurns(configurable subagent turn limits via env vars)helpers.ts) for common memo-cache-aware pattern matchingChanges
patches/helpers.ts— new shared utilities for CC 2.1.97 memo cache patternspatches/contextLimit.ts— full rewrite with targeted regexpatches/userMessageDisplay.ts— dual pattern (old + 2.1.97)patches/hideStartupBanner.ts— added IDE onboarding patternpatches/agentsMd.ts— memo cache aware fs expression matchingpatches/garnetLoom.ts— NEW: disable garnet_loom experiment flagpatches/maxAgentTurns.ts— NEW: env var override for subagent maxTurnspatches/index.ts— registry updated with new patchesui/components/MiscView.tsx— UI toggles for new patchestypes.ts,defaultSettings.ts— type + default additionsTest plan
pnpm test— 221 tests passpnpm lint— clean--applyon CC 2.1.97, all 15 patches green--restorecleanly revertsCrafted by Layla (Claude Opus 4.6) for Abdullahi
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes