Context
The local Claude Code plan-scroll patch was reverted after it crashed plan mode:
ERROR oe.getFreshScrollHeight is not a function. (In 'oe.getFreshScrollHeight()', 'oe.getFreshScrollHeight' is undefined)
/$bunfs/root/src/entrypoints/cli.js:9789:15675
Revert commit in dotfiles: b745cafd.
The installed Claude binary was also restored from claude.unpatched and re-patched with only the existing non-plan-scroll local patches.
Cause
The reverted patch replaced the plan approval render call from Claude's internal scroll component pW to a normal Ink Box ($) while keeping the same ref path.
That was unsafe. pW is not just layout; it exposes an imperative scroll-ref interface. In Claude Code 2.1.202, pW defines methods including:
scrollTo
scrollBy
scrollToBottom
getScrollHeight
getFreshScrollHeight
getViewportHeight
getViewportTop
subscribe
A nearby scroll manager then calls r?.current.getFreshScrollHeight() and other scroll-ref methods. A plain Ink Box ref does not implement that interface, causing the crash.
Evidence from local reverse-engineering:
- Plan dialog call:
ky.jsx(pW,{ref:ht,flexDirection:"column",height:At,stickyScroll:!1,children:zt})
pW implementation includes getFreshScrollHeight(){return i.current?.childNodes[0]?.yogaNode?.getComputedHeight()??i.current?.scrollHeight??0}
- Consumer calls
let Z=oe.getFreshScrollHeight() and later let te=Z.getFreshScrollHeight() through the scroll ref.
Safer follow-up options
- Leave Claude unpatched until upstream fixes the plan-mode scroll regression.
- Try a narrower height-only patch that preserves
pW and its ref contract, e.g. change At=Ce?Le:void 0 to At=void 0 as a same-length binary edit.
- Patch only the
pW call's sizing/sticky props while still rendering pW, so downstream scroll-ref consumers keep working.
- Avoid Box replacement unless also patching every consumer that expects the
pW scroll-ref API. That is probably too brittle for a local binary patch.
Acceptance criteria for any retry
- Do not replace
pW with Box at this call site unless the scroll-ref contract is preserved.
claude --version and codesign --verify are not enough; run a real plan-mode approval screen with a long plan.
- Include a tracked patch wrapper and Bats needle tests only after manual plan-mode verification.
--reapply must fail open with a stale marker on missing needles, matching the existing Claude patch lane.
Context
The local Claude Code plan-scroll patch was reverted after it crashed plan mode:
Revert commit in dotfiles:
b745cafd.The installed Claude binary was also restored from
claude.unpatchedand re-patched with only the existing non-plan-scroll local patches.Cause
The reverted patch replaced the plan approval render call from Claude's internal scroll component
pWto a normal InkBox($) while keeping the samerefpath.That was unsafe.
pWis not just layout; it exposes an imperative scroll-ref interface. In Claude Code2.1.202,pWdefines methods including:scrollToscrollByscrollToBottomgetScrollHeightgetFreshScrollHeightgetViewportHeightgetViewportTopsubscribeA nearby scroll manager then calls
r?.current.getFreshScrollHeight()and other scroll-ref methods. A plain InkBoxref does not implement that interface, causing the crash.Evidence from local reverse-engineering:
ky.jsx(pW,{ref:ht,flexDirection:"column",height:At,stickyScroll:!1,children:zt})pWimplementation includesgetFreshScrollHeight(){return i.current?.childNodes[0]?.yogaNode?.getComputedHeight()??i.current?.scrollHeight??0}let Z=oe.getFreshScrollHeight()and laterlet te=Z.getFreshScrollHeight()through the scroll ref.Safer follow-up options
pWand its ref contract, e.g. changeAt=Ce?Le:void 0toAt=void 0as a same-length binary edit.pWcall's sizing/sticky props while still renderingpW, so downstream scroll-ref consumers keep working.pWscroll-ref API. That is probably too brittle for a local binary patch.Acceptance criteria for any retry
pWwithBoxat this call site unless the scroll-ref contract is preserved.claude --versionandcodesign --verifyare not enough; run a real plan-mode approval screen with a long plan.--reapplymust fail open with a stale marker on missing needles, matching the existing Claude patch lane.