Skip to content

Commit 5496131

Browse files
phase3devclaude
andauthored
Add context-usage icon fix; restructure into a workarounds index (#2)
Adds the context-usage icon fix alongside the thinking-summaries fix and restructures the repo into a workarounds index (claudemax = both, claude-think = thinking only, claude-context = context icon, plus *.win.js sources and fix-context-icon.py). Repo renamed claude-code-think -> claude-code-workarounds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7723e69 commit 5496131

11 files changed

Lines changed: 1401 additions & 344 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
# Node
66
node_modules/
77

8+
# Python bytecode (e.g. from running fix-context-icon.py)
9+
__pycache__/
10+
*.pyc
11+
812
# Editor / OS noise
913
*.bak.*
1014
*.orig

README.md

Lines changed: 164 additions & 279 deletions
Large diffs are not rendered by default.

TECHNICAL.md

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
# Technical details: empty thinking summaries on Opus 4.7 / 4.8
1+
# Technical details
22

3-
Full root-cause analysis and design notes behind the workarounds in the [README](README.md). This expands on the README's "Technical details" section with the request-level mechanics, the live A/B confirmation, and the proxy design.
3+
Full root-cause analysis and design notes behind the workarounds in the [README](README.md).
4+
5+
* [Workaround 1: empty thinking summaries](#workaround-1-empty-thinking-summaries)
6+
* [Workaround 2: missing context-usage icon](#workaround-2-missing-context-usage-icon)
7+
8+
---
9+
10+
# Workaround 1: empty thinking summaries
11+
12+
Request-level mechanics, the live A/B confirmation, and the proxy design for the empty-thinking-summaries fix on Opus 4.7 / 4.8.
413

514
## TL;DR
615

@@ -135,3 +144,96 @@ Status: provided as a working starting point but not extensively tested, so vali
135144
## Compatibility
136145

137146
Confirmed on Opus 4.7 / 4.8 with VS Code extension `2.1.169` (native-binary CLI), via the `claudeCode.claudeProcessWrapper` setting, on Windows 11 and Ubuntu 24.04; earlier confirmations were on `2.1.165` / `2.1.167` (which signaled thinking with `--thinking adaptive`). The CLI flag and the request field are stable levers, but the exact minified strings used by [`patch-extension.sh`](patch-extension.sh) (Option 2) can change between extension releases (e.g. the array variable `B` -> `q`); the script matches the variable generically and, if the surrounding pattern isn't found, skips and tells you to inspect manually. Options 1 and 3 don't depend on internal strings.
147+
148+
---
149+
150+
# Workaround 2: missing context-usage icon
151+
152+
## TL;DR
153+
154+
The context-usage indicator in the VS Code chat input is gated to render only after more than 50% of the context window has been used. With the 1M context window that is about 500,000 tokens, so it is effectively never shown. There is no environment variable or CLI flag for the threshold, so the fix is a one-character-class edit to the extension's webview bundle, re-applied on each launch by the wrapper so it survives updates.
155+
156+
## Root cause (from the webview bundle)
157+
158+
The indicator is a React component in the extension's `webview/index.js`. The bundle is minified, so the component name `FJe` and its helpers (`OJe`, `BIt`, `b0e`, ...) are minifier-assigned and change between builds. Deobfuscated:
159+
160+
```js
161+
function FJe({usedTokens:e, contextWindow:t, onCompact:i, buttonClassName:n}) {
162+
let a = t > 0 ? Math.min(e / t * 100, 100) : 0, // a = % used
163+
l = b0e !== null ? b0e : a, // l = % used (b0e is a debug override, normally null)
164+
c = 100 - l; // c = % REMAINING
165+
if (b0e === null) {
166+
if (t === 0) return null; // no session / no window yet -> hide
167+
if (c >= 50) return null; // <-- the gate: hide while >=50% remains
168+
}
169+
// ... renders the pie button + tooltip + popup ...
170+
}
171+
```
172+
173+
`c` is the percent of context *remaining*, so `c >= 50` hides the icon whenever at least half the window is free, i.e. it only appears once more than 50% is used. With a 1M window that is 500k tokens. Older bundles (`2.1.131`, `2.1.128`) do not contain this gate; it appeared around `2.1.165`, which matches users' recollection that the icon used to be visible.
174+
175+
The `CLAUDE_CODE_DISABLE_1M_CONTEXT=1` env var that circulates in the issue threads only shrinks the window to 200k so 50% (100k) is reached sooner. It does not touch the threshold and forces giving up the 1M window, so it is not a real fix.
176+
177+
## The fix
178+
179+
```text
180+
if(c>=50)return null -> if(c>=101)return null
181+
```
182+
183+
`c` is in `[0, 100]`, so `c >= 101` is never true and the gate never hides the icon. The separate `if (t === 0) return null` guard is left intact, so nothing renders before a context window is known. Using `>=101` (rather than deleting the line) is the smallest, most legible, greppable, reversible change, and it preserves the surrounding structure for a clean string substitution. The patch is anchored on the literal `>=50)return null}`, which is stable across builds even though the minified names around it are not, and which occurs exactly once in `2.1.169`.
184+
185+
There is no integrity or subresource check on the webview bundle (the only `sha256` references in `extension.js` belong to a bundled crypto library), so an edited `index.js` loads normally.
186+
187+
## Delivery: re-patch on each launch
188+
189+
The launcher is registered as the extension's process wrapper (`claudeCode.claudeProcessWrapper`), so the extension invokes it as `<wrapper> <REAL_CLAUDE...> <args...>` every time it spawns the CLI, including the first spawn after an auto-update. That is the ideal place to re-apply a bundle patch: it self-heals across updates with no daemon or cron.
190+
191+
The wrapper discovers `index.js` two ways:
192+
193+
* Precise: walk up from the real `claude` path the extension hands it until a path component matches `anthropic.claude-code-*`, then `<root>/webview/index.js`.
194+
* Fallback: scan the user's `.vscode`, `.vscode-server`, and `.vscode-insiders` extension dirs for `anthropic.claude-code-*/webview/index.js` (covers terminal launches and standalone-CLI installs).
195+
196+
The edit is made safe:
197+
198+
* Idempotent: skips an already-patched file, and skips (rather than guesses) if the `>=50)return null}` anchor is absent because the extension changed.
199+
* Backed up once to `index.js.bak-context-icon` before the first edit.
200+
* Atomic: written to a temp file and moved into place only after it is verified non-empty and actually patched, so a failed or partial write cannot corrupt the bundle.
201+
* Metadata-preserving via `cp -p` (portable; the GNU-only `chmod`/`chown --reference` is avoided so it also works on macOS/BSD). The Windows launcher writes with `fs.writeFileSync` + `fs.renameSync`, inheriting the parent directory's ACLs.
202+
* Fully guarded so it never blocks the launch (a read-only file, a renamed bundle, or a missing tool simply no-ops).
203+
204+
Timing note: the wrapper patches `index.js` on disk when the CLI is spawned, which can be *after* the webview already loaded the old bundle. So the first time you enable it you may need two reloads (the spawn patches the file, then the webview loads the patched bundle). Later windows and post-update launches are already patched on disk.
205+
206+
## How the icon works (context for future changes)
207+
208+
### Data source resets on reload
209+
210+
`FJe` is fed from a live `usageData` store:
211+
212+
```js
213+
React.createElement(FJe, {
214+
usedTokens: e.usageData.value.totalTokens,
215+
contextWindow: e.usageData.value.contextWindow - e.usageData.value.maxOutputTokens - 13000,
216+
onCompact: l,
217+
// ...
218+
});
219+
```
220+
221+
`usageData` initializes to all zeros and is only filled by usage events that arrive during an assistant turn. There is no seeding from `get_claude_state` on resume, so immediately after a window reload of a continued conversation, before any new turn, the store is still `{0,0,0,0}`: `usedTokens = 0` (the tooltip reads "0% used") and `contextWindow = 0 - 0 - 13000 = -13000` (negative, so the `t === 0` guard does not fire and, after the patch, the icon still renders at 0%). This self-corrects on the next turn, when a usage event fills the store with the real totals. `/context` does not use this store; it queries the CLI directly, so it always shows the true number.
222+
223+
If showing a transient 0% is undesirable, changing `if(t===0)return null` to `if(t<=0)return null` hides the icon while the store is empty (`t` is negative then) and shows it with the correct value after the first turn. This is documented as an optional manual tweak and is not applied by default.
224+
225+
### The glyph is a coarse 3-state gauge
226+
227+
```js
228+
function BIt(e){ if(e<62.5) return 50; if(e<87) return 75; return 99 } // e = % used
229+
```
230+
231+
`BIt` maps percent-used to one of three bucket keys (`50`, `75`, `99`) that index arc-path lookup tables for the SVG. So the pie has only three visual states: below 62.5% used, 62.5 to 87%, and above 87%. It does not track 12% vs 30% vs 50%; it is a "getting full" warning light, which made sense when it was only shown past 50% used. The precise figure lives in the hover tooltip and the popup, and in `/context`. Making the pie a continuous gauge would require new SVG geometry, not a string patch, so it is out of scope.
232+
233+
### Click behavior
234+
235+
The pie button's `onClick` is `onCompact`: clicking the icon triggers compaction, not opening `/context`. Opening the detailed panel is the `/context` command. These are kept distinct.
236+
237+
## Compatibility
238+
239+
Confirmed on VS Code extension `2.1.169` (native-binary CLI) on Windows 11 and Ubuntu 24.04. The `>50% used` gate appeared around `2.1.165` (absent in `2.1.131` / `2.1.128`). The patch keys off the stable substring `>=50)return null}`, not the minified component name; if a future build changes that exact substring, the launcher safely no-ops (the icon goes missing again) until the anchor is updated. The standalone [`fix-context-icon.py`](fix-context-icon.py) applies the same change directly and supports `--revert`.

claude-context

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#!/usr/bin/env bash
2+
# claude-context - Claude Code launcher that restores the always-visible
3+
# context-usage icon in the VS Code chat input.
4+
#
5+
# Recent extension builds (2.1.165+) hide that icon until you have used >50% of
6+
# the context window; with the 1M context window that is ~500k tokens, so it is
7+
# effectively never shown. There is no env/CLI lever for this, so this wrapper
8+
# idempotently patches the extension's webview bundle on each launch, flipping
9+
# the threshold so the icon shows at any usage level. Because it re-applies every
10+
# launch, it survives extension updates.
11+
#
12+
# Context-icon-only variant. For the thinking-summaries fix too, use `claudemax`
13+
# (both fixes combined); for thinking alone, use `claude-think`. All three are
14+
# drop-in process wrappers and differ only in what they inject/patch.
15+
#
16+
# NOTE: this DOES edit the extension's bundled webview/index.js. That edit is
17+
# idempotent, backed up once to index.js.bak-context-icon, written atomically (a
18+
# failed write leaves the original untouched), best-effort (it never blocks the
19+
# launch), and toggle-able with CC_PATCH_CONTEXT_ICON=0.
20+
#
21+
# Use it:
22+
# - VS Code (official "Claude Code" extension): set "claudeCode.claudeProcessWrapper"
23+
# to the FULL path of this file, then reload the window. In a multi-root
24+
# .code-workspace this setting is window-scoped, so put it in the workspace
25+
# file's "settings" block (or User settings), not a folder .vscode/settings.json.
26+
# - VS Code (third-party "Claude Code Chat"): set "claudeCodeChat.executable.path".
27+
# - Terminal: run `claude-context` in place of `claude`.
28+
#
29+
# First-run note: the wrapper patches index.js on disk when the CLI is spawned,
30+
# which can be AFTER the webview already loaded the old bundle. So the first time
31+
# you enable this you may need two reloads: reload (the spawn patches the file),
32+
# then reload again (the webview loads the patched bundle). Subsequent windows
33+
# and post-update launches are already patched on disk.
34+
#
35+
# Toggle off:
36+
# export CC_PATCH_CONTEXT_ICON=0 # leave the extension webview untouched
37+
#
38+
# Default:
39+
# CC_PATCH_CONTEXT_ICON=1
40+
#
41+
# The real `claude` must be installed. This wrapper finds it automatically; if it
42+
# cannot, set CLAUDE_REAL_BIN to the full path of your real claude binary.
43+
44+
set -euo pipefail
45+
46+
# --- Locate the real claude binary -----------------------------------------
47+
48+
self="$(readlink -f "$0" 2>/dev/null || echo "$0")"
49+
50+
# Process-wrapper convention: the official VS Code extension invokes the wrapper
51+
# as <wrapper> <REAL_CLAUDE...> <args...>, passing the real CLI ahead of the
52+
# args. <REAL_CLAUDE...> is either a single native-binary path (".../claude") or
53+
# a node interpreter followed by the bundled cli.js (".../node .../cli.js").
54+
# Peel that off so it is not forwarded as a stray positional argument, and
55+
# prefer it as the real claude. (Plain "claude-context <args>" use is unaffected:
56+
# <args> never begins with an existing claude/node binary path.)
57+
wrapper_bin=""
58+
if [ "$#" -gt 0 ] \
59+
&& printf '%s' "$1" | grep -Eqi '/claude(\.exe|\.cmd|\.bat)?$' \
60+
&& [ -e "$1" ]; then
61+
wrapper_bin="$1"
62+
shift
63+
elif [ "$#" -ge 2 ] \
64+
&& printf '%s' "$1" | grep -Eqi '/node(\.exe)?$' && [ -e "$1" ] \
65+
&& printf '%s' "$2" | grep -Eqi '\.(c?js|mjs)$' && [ -e "$2" ]; then
66+
# node + cli.js: exec node directly and keep cli.js as the first forwarded arg.
67+
wrapper_bin="$1"
68+
shift
69+
fi
70+
71+
REAL_CLAUDE="${CLAUDE_REAL_BIN:-}"
72+
if [ -z "$REAL_CLAUDE" ] && [ -n "$wrapper_bin" ]; then
73+
REAL_CLAUDE="$wrapper_bin"
74+
fi
75+
76+
if [ -z "$REAL_CLAUDE" ]; then
77+
for c in \
78+
"$HOME/.local/bin/claude" \
79+
/usr/local/bin/claude \
80+
/usr/bin/claude \
81+
/opt/homebrew/bin/claude \
82+
"$(command -v claude 2>/dev/null || true)"; do
83+
84+
[ -n "$c" ] && [ -x "$c" ] || continue
85+
[ "$(readlink -f "$c" 2>/dev/null || echo "$c")" = "$self" ] && continue
86+
87+
REAL_CLAUDE="$c"
88+
break
89+
done
90+
fi
91+
92+
[ -n "$REAL_CLAUDE" ] || {
93+
echo "claude-context: could not find the real 'claude' binary; set CLAUDE_REAL_BIN" >&2
94+
exit 1
95+
}
96+
97+
# --- Restore the always-visible context-usage icon (patches the webview) ----
98+
#
99+
# Best-effort and must never block the launch: every step is guarded, writes go
100+
# through a temp file with an atomic rename (a failed write leaves the original
101+
# untouched), and the whole routine runs under `|| true`.
102+
#
103+
# What it changes - component `FJe` in webview/index.js:
104+
# if(c>=50)return null -> if(c>=101)return null
105+
# `c` is "% of context remaining"; it maxes at 100, so >=101 never fires and the
106+
# icon renders whenever a context window is known (the t===0 "no session yet"
107+
# guard is left intact). Idempotent, and re-applied each launch, so an extension
108+
# update that reinstalls a fresh bundle is re-patched on the next launch.
109+
#
110+
# Maintenance note: the patch keys off the stable string `>=50)return null}`, not
111+
# the minified component name. If a future extension build changes that exact
112+
# substring, the routine safely no-ops (the icon goes missing again) until the
113+
# anchor here is updated.
114+
115+
CC_PATCH_CONTEXT_ICON="${CC_PATCH_CONTEXT_ICON:-1}"
116+
117+
_cc_patch_index_js() {
118+
local f="$1" tmp tmp2
119+
[ -f "$f" ] && [ -w "$f" ] || return 0
120+
if grep -q '>=101)return null}' "$f" 2>/dev/null; then return 0; fi # already patched
121+
if ! grep -q '>=50)return null}' "$f" 2>/dev/null; then return 0; fi # gate absent (version changed)
122+
if [ ! -e "$f.bak-context-icon" ]; then cp -p "$f" "$f.bak-context-icon" 2>/dev/null || true; fi
123+
tmp="${f}.ccpatch.$$"
124+
tmp2="${f}.ccpatch2.$$"
125+
# `cp -p` preserves mode/owner portably (the GNU-only `--reference` is avoided
126+
# so this also works on macOS/BSD): copy the original to a temp, sed into a
127+
# second temp, copy that back over the metadata-preserving temp, then atomically
128+
# replace the original. A failed/partial step leaves the original untouched.
129+
cp -p "$f" "$tmp" 2>/dev/null || { rm -f "$tmp" 2>/dev/null || true; return 0; }
130+
if sed 's/>=50)return null}/>=101)return null}/g' "$f" > "$tmp2" 2>/dev/null \
131+
&& [ -s "$tmp2" ] \
132+
&& grep -q '>=101)return null}' "$tmp2" 2>/dev/null; then
133+
cat "$tmp2" > "$tmp" && mv -f "$tmp" "$f" || true
134+
fi
135+
rm -f "$tmp" "$tmp2" 2>/dev/null || true
136+
}
137+
138+
_cc_restore_context_icon() {
139+
local d extdir f
140+
# Most precise target: walk up from the real claude path the extension handed
141+
# us (its bundled resources/native-binary/claude) to the extension root.
142+
d="$(dirname "$REAL_CLAUDE" 2>/dev/null || echo "")"
143+
extdir=""
144+
while [ -n "$d" ] && [ "$d" != "/" ] && [ "$d" != "." ]; do
145+
case "${d##*/}" in
146+
anthropic.claude-code-*) extdir="$d"; break ;;
147+
esac
148+
d="$(dirname "$d" 2>/dev/null || echo "")"
149+
done
150+
if [ -n "$extdir" ]; then _cc_patch_index_js "$extdir/webview/index.js"; fi
151+
152+
# Also cover any installed extension under this user's VS Code dirs (terminal
153+
# launches, or when the real binary is the standalone CLI). Unmatched globs
154+
# fall through harmlessly - _cc_patch_index_js skips non-files.
155+
for f in \
156+
"$HOME"/.vscode-server/extensions/anthropic.claude-code-*/webview/index.js \
157+
"$HOME"/.vscode/extensions/anthropic.claude-code-*/webview/index.js \
158+
"$HOME"/.vscode-server-insiders/extensions/anthropic.claude-code-*/webview/index.js; do
159+
_cc_patch_index_js "$f"
160+
done
161+
}
162+
163+
if [ "$CC_PATCH_CONTEXT_ICON" != "0" ]; then
164+
_cc_restore_context_icon || true
165+
fi
166+
167+
# The `${@+...}` form guards the empty-args case under `set -u`, including older
168+
# Bash versions such as the default Bash on older macOS systems.
169+
exec "$REAL_CLAUDE" ${@+"$@"}

0 commit comments

Comments
 (0)