You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
cc -p fails with "prompt is not a function": a user-defined async method on an object literal is invisible to typed-feedback native dispatch (blocks #9949 rows) #11559
With #11556 applied, claude-code 2.1.112 starts (--help works) but -p "say hi" fails immediately:
prompt is not a function
This blocks every cc runtime measurement that needs a turn, including the four-turn tenuring rows for #9949 — those drive cc through -p, so they cannot run until this is fixed.
Caught in gdb on a symbolized build, breaking on every copy of the throw:
So a typed-feedback native method call by id for the method prompt found a receiver that does not carry it, during microtask drain (hence no Uncaught prefix difference — it is thrown inside a promise job).
What prompt is in this bundle
Not a global. grep finds 48 object-literal method definitions of the form
i.e. each tool descriptor carries its own async prompt(). There is no globalThis.prompt, no window.prompt, and no typeof prompt guard anywhere in the bundle, and perry defines no global prompt either. So the failing call is someToolDescriptor.prompt(), and the defect is that a user-defined async method on a plain object literal is not found by the typed-feedback native-method dispatch.
Reproduce
PERRY_RUNTIME_DIR=$PWD/target/release \
./target/release/perry compile --no-auto-optimize --enable-wasm-runtime cli_2.1.112.js -o cc
HOME=/tmp/cc_home ./cc -p "say hi"# prompt is not a function
HOME=/tmp/cc_home ./cc --help # works
Verified on fix/11499-static-write-instance-accessor (main d57f5139f6 + that fix).
One thing I deliberately did NOT conclude
I read xmm0 at the breakpoint and the runtime's printer dumped what looks like the global object (PerformanceResourceTiming, crypto, navigator, a signal-exit emitter). I am not claiming that is the receiver. I broke on js_throw_type_error_not_a_function, which does not take the receiver in xmm0, so that value may well be a leftover register — the same over-reading that produced a wrong first-bad attribution on #11301. The correct next step is to break on js_native_call_method with the method name filtered to prompt and read its actual receiver argument. I have not done that yet.
If it does turn out to be the global object, this is a receiver-resolution bug rather than a missing-method bug, which would point somewhere quite different — worth establishing before anyone starts on a fix.
Related
Possibly the same family as #10893 (a static accessor reached through the class-id chain was invisible to the native-call tower) and #10848 (patching a built-in method), both of which were dispatch-tower gaps rather than missing implementations.
BIN=/root/rig9831/cc11499_sym
# 1. enumerate EVERY copy of the throw symbol — there are 2, and breaking on the# wrong one never fires and reads exactly like "the bug is somewhere else".
nm $BIN| grep -oE '_RNv[A-Za-z0-9_]*throw_not_callable'| sort -u
# 2. break on all of them, plus the C entry point# break *<mangled> <- the `*` matters: `break fn` stops AFTER the# prologue, with the arg registers clobbered# 3. print any NaN-boxed value with the runtime's own printer:# call (void) js_console_log_dynamic($xmm0.v2_double[0])# it prints class names and live fields, e.g. `VR8 [Error]`# 4. a TUI needs a pty:
script -qec "gdb -batch -x /root/g.gdb $BIN" /dev/null
Two more that cost me runs: gdb cannot take the address of a convenience variable, so to print a reconstructed box, poke it into a real memory slot first; and filtering a breakpoint on a key's length is not enough (errorCode and ToolError are both 9 chars) — compare the first four bytes, e.g. *(unsigned int*)$rsi == 0x6f727265 for "erro".
Rebuilding, if you must
cd<your worktree>
cargo build --release -p perry -p perry-runtime-static -p perry-stdlib-static -p perry-wasm-host \
--features perry-runtime/wasm-host
cd /root/ccbuild # copy the bundle out if you want your own dir
PERRY_HEAVY_NEED_GB=28 PERRY_HEAVY_MAX_WAIT=21600 /root/perry-heavy.sh \
env PERRY_RUNTIME_DIR=<wt>/target/release PERRY_WORKSPACE_ROOT=<wt> \
<wt>/target/release/perry compile --no-auto-optimize --enable-wasm-runtime cli_2.1.112.js -o <out>
PERRY_KEEP_SYMBOLS=1 is not a cache key — a symbolized build is one ~3-8 min relink, not a recompile.
Build compiler and runtime from the same tree. The stamp guard compares version and commit-or-source-hash, and a dirty tree switches it to a source hash — so a cross-tree or cross-version relink is refused. Do not pass a short sha to PERRY_BUILD_COMMIT.
Do not let build-cc.sh run against /root/ccbuild: it does rm -rf node_modules/.cache/perry, destroying the object cache that makes a relink minutes instead of ~46 min.
Disk is tight: 10 GB free right now. A cc compile needs headroom; the heavy wrapper waits for memory but not for disk.
The one open question, and the exact command to settle it
My write-up says the receiver "looks like the global object" and explicitly does not claim it. That matters more than anything else here, because the two possibilities lead to different fixes:
if the receiver really is the global object → a receiver-resolution bug;
if it is the tool descriptor → a method-lookup bug (a user-defined async prompt() on an object literal invisible to typed-feedback dispatch).
I read xmm0 at js_throw_type_error_not_a_function, which does not take the receiver in xmm0, so that dump may be a leftover register. Do not build on it. The clean way is to break one frame earlier, on js_native_call_method, filter to the method name, and read its actual receiver argument:
BIN=/root/rig9831/cc11499_sym
nm $BIN| grep -w js_native_call_method # find the entry point# "prompt" is 6 bytes; first 4 bytes are "prom" == 0x6d6f7270 little-endian# break *js_native_call_method if <name-len reg> == 6 && *(unsigned int*)<name-ptr reg> == 0x6d6f7270# then dump the receiver with the runtime printer:# call (void) js_console_log_dynamic(<receiver reg>.v2_double[0])# call (int) js_object_get_class_id((void*)(<receiver bits> & 0x0000ffffffffffff))
Check js_native_call_method's signature first to learn which register holds the receiver versus the name pointer/length — that is what I skipped, and it is why my read is unusable.
Fixed on main by #11610 (spill store past the buffer's high-water mark), with regression tests in #11614. Acceptance on main 63f6b2a (qb4): cc 2.1.112 compiled with --no-auto-optimize --enable-wasm-runtime runs -p "say hi" against a local mock Anthropic API. It exits 0, prints the mocked reply, and the mock logs a real /v1/messages turn. doctor (#11558, fixed by #11615) renders its full diagnostics with no crash, and --version / --help both exit 0.
Verified fixed on main by #11610 (spill store read the uninitialized tail as the old value). Acceptance: cc 2.1.112 compiled from main be39bbf, -p "say hi" passes 10/10 past the old failure point (plus a PERRY_OBJECT_SPILL=0 control, 3/3), with no "not a function" in any output. #11614 adds the regression tests.
Activity
proggeramlug commentedon Sep 28, 2026
Handover: ready-built assets on perrymaster (saves a ~2.6 h / 28 GB cc compile)
Everything needed to debug this without compiling cc yourself. Verified present today.
/root/rig9831/cc11499_symfix/11499-static-write-instance-accessor, SYMBOLIZED — 370 MB, 792,245 symbols. Use this one./root/rig9831/cc11499/root/rig9831/ccMAIN_symmainbefore #11556 (791,829 symbols) — dies earlier, with the #11499 TypeError/root/ccbuild/cli_2.1.112.js/root/ccbuild/cli_trace.js__t()trace hooks at four module-init sites/tmp/tt_home_9831,/tmp/tt_proj_9831mk_sandbox.sh 9831recreates them)Always run with
HOME=/tmp/tt_home_9831from/tmp/tt_proj_9831. Never the real$HOME— it resets the live API key in~/.claude.json.The gdb recipe that cracked #11301 and #11499
Two more that cost me runs: gdb cannot take the address of a convenience variable, so to print a reconstructed box, poke it into a real memory slot first; and filtering a breakpoint on a key's length is not enough (
errorCodeandToolErrorare both 9 chars) — compare the first four bytes, e.g.*(unsigned int*)$rsi == 0x6f727265for"erro".Rebuilding, if you must
PERRY_KEEP_SYMBOLS=1is not a cache key — a symbolized build is one ~3-8 min relink, not a recompile.PERRY_BUILD_COMMIT.build-cc.shrun against/root/ccbuild: it doesrm -rf node_modules/.cache/perry, destroying the object cache that makes a relink minutes instead of ~46 min.proggeramlug commentedon Sep 28, 2026
The one open question, and the exact command to settle it
My write-up says the receiver "looks like the global object" and explicitly does not claim it. That matters more than anything else here, because the two possibilities lead to different fixes:
async prompt()on an object literal invisible to typed-feedback dispatch).I read
xmm0atjs_throw_type_error_not_a_function, which does not take the receiver inxmm0, so that dump may be a leftover register. Do not build on it. The clean way is to break one frame earlier, onjs_native_call_method, filter to the method name, and read its actual receiver argument:Check
js_native_call_method's signature first to learn which register holds the receiver versus the name pointer/length — that is what I skipped, and it is why my read is unusable.Context worth having
perry_fn_cli_2_1_112_js__aMA. Treat minified frame names as unreliable until matched to a binding in scope at that site: on cc 2.1.112 compiled from main dies at startup: every subcommand but --version throws "value is not a function" #11301 the stack blamedOBY, which turned out to be a render loop, and anOqI trusted was the wrong one of three bindings with that name.pump_protected→run_microtasks→main), so it is reached during async startup, not from the CLI entry.PERRY_GEN_GC=0andPERRY_GC_FORCE_EVACUATE=1are cheap and were decisive on cc 2.1.112 compiled from main dies at startup: every subcommand but --version throws "value is not a function" #11301 (both identical there ⇒ not a lifetime bug).test-files/test_gap_11499_class_object_static_write.tsdoes: hide identity behind a lazy initializer assigned to outervars, and call through a shared, IC-warmed site.test(runtime): regression coverage for #11559 (spill store past the h…
test(runtime): regression coverage for #11559 (spill store past the h…
proggeramlug commentedon Sep 28, 2026
Fixed on main by #11610 (spill store past the buffer's high-water mark), with regression tests in #11614. Acceptance on main 63f6b2a (qb4): cc 2.1.112 compiled with
--no-auto-optimize --enable-wasm-runtimeruns-p "say hi"against a local mock Anthropic API. It exits 0, prints the mocked reply, and the mock logs a real /v1/messages turn.doctor(#11558, fixed by #11615) renders its full diagnostics with no crash, and--version/--helpboth exit 0.proggeramlug commentedon Sep 28, 2026
Verified fixed on main by #11610 (spill store read the uninitialized tail as the old value). Acceptance: cc 2.1.112 compiled from main be39bbf,
-p "say hi"passes 10/10 past the old failure point (plus a PERRY_OBJECT_SPILL=0 control, 3/3), with no "not a function" in any output. #11614 adds the regression tests.