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
regression: perry.define of process.env.NODE_ENV no longer applied (ignored since process became a real object) → React/react-reconciler load dev builds → 'value is not a function' crash (blocks ink) #5009
perry.define of process.env.* keys (e.g. process.env.NODE_ENV) is no longer applied — neither as a compile-time constant substitution nor at runtime. process.env.NODE_ENV reads undefined regardless of the define. As a consequence, compiled packages that branch on process.env.NODE_ENV (React, react-reconciler, scheduler, …) select their development builds, and react-reconciler's dev build then throws TypeError: value is not a function during render — blocking ink (#348) at the final step.
This worked in the 2026-05-29 recheck (define folded React to its production build and tree-shaking pruned the dev build). It is a regression, almost certainly from #4993 — making process.env a real runtime object means process.env.NODE_ENV is now a live property read that bypasses the define's static substitution.
With process.env.NODE_ENV unresolved (undefined), the else (development) branch is taken. ink's render() → reconciler.updateContainerSync(...) then throws TypeError: value is not a function.
Native backtrace (--debug-symbols, breakpoint on throw_not_callable):
The frames are react_reconciler_*DEVELOPMENT*_js — confirming the dev build is what executes.
Setting NODE_ENV=production in the runtime environment does not fix it (react-reconciler's branch is resolved at module-init, before the fix would matter, and the define — not the runtime env — is the intended mechanism for build selection).
Two issues, one root
Primary (this issue):perry.define must apply to process.env.* again — fold process.env.NODE_ENV to the configured constant at compile time so === 'production' resolves and the production branch is selected (and the dev build tree-shakes away). The fix should make the define authoritative even though process.env is now a real object (e.g. substitute defined process.env.X reads with the literal before the runtime lookup, and/or seed the runtime process.env from perry.define at startup).
Secondary (separate): react-reconciler's development build hits a value is not a function codegen gap under Perry (frames __655/__656/__573/__194/__202). Not the target build, but a real latent gap — worth a follow-up if dev-build support is ever desired.
Impact
Every compilePackages project relying on define for NODE_ENV — i.e. essentially all React/Preact/Vue/etc. apps, which ship separate dev/prod builds gated on process.env.NODE_ENV. They silently get dev builds (slower, dev-only asserts) or crash on dev-only code paths.
Summary
perry.defineofprocess.env.*keys (e.g.process.env.NODE_ENV) is no longer applied — neither as a compile-time constant substitution nor at runtime.process.env.NODE_ENVreadsundefinedregardless of the define. As a consequence, compiled packages that branch onprocess.env.NODE_ENV(React, react-reconciler, scheduler, …) select their development builds, and react-reconciler's dev build then throwsTypeError: value is not a functionduring render — blocking ink (#348) at the final step.This worked in the 2026-05-29 recheck (define folded React to its production build and tree-shaking pruned the dev build). It is a regression, almost certainly from #4993 — making
process.enva real runtime object meansprocess.env.NODE_ENVis now a live property read that bypasses the define's static substitution.Evidence
package.json→perry.define:{ "process.env.NODE_ENV": "production", "process.env.DEV": "false" }(also tested the quoted form
"\"production\""— same result)So
process.envfaithfully reflects the real environment (good, #4993), but the build-timedefineno longer interceptsprocess.env.*.Consequence — react-reconciler runs its dev build and crashes
node_modules/react-reconciler/index.js:With
process.env.NODE_ENVunresolved (undefined), theelse(development) branch is taken. ink'srender()→reconciler.updateContainerSync(...)then throwsTypeError: value is not a function.Native backtrace (
--debug-symbols, breakpoint onthrow_not_callable):The frames are
react_reconciler_*DEVELOPMENT*_js— confirming the dev build is what executes.Setting
NODE_ENV=productionin the runtime environment does not fix it (react-reconciler's branch is resolved at module-init, before the fix would matter, and the define — not the runtime env — is the intended mechanism for build selection).Two issues, one root
perry.definemust apply toprocess.env.*again — foldprocess.env.NODE_ENVto the configured constant at compile time so=== 'production'resolves and the production branch is selected (and the dev build tree-shakes away). The fix should make the define authoritative even thoughprocess.envis now a real object (e.g. substitute definedprocess.env.Xreads with the literal before the runtime lookup, and/or seed the runtimeprocess.envfromperry.defineat startup).value is not a functioncodegen gap under Perry (frames__655/__656/__573/__194/__202). Not the target build, but a real latent gap — worth a follow-up if dev-build support is ever desired.Impact
compilePackagesproject relying ondefineforNODE_ENV— i.e. essentially all React/Preact/Vue/etc. apps, which ship separate dev/prod builds gated onprocess.env.NODE_ENV. They silently get dev builds (slower, dev-only asserts) or crash on dev-only code paths.ink(React-based TUI framework) end-to-end viaperry.compilePackages#348): this is the last wall before yoga — with the production reconciler,render()should proceed into layout (then the out-of-scope yoga-WASM terminal).Related
ink(React-based TUI framework) end-to-end viaperry.compilePackages#348 (ink end-to-end), fix(runtime): node:process default import & globalThis.process expose the full process object (#4987) #4993 (processbecame a real object — suspected regression point)new MessageChannel()global constructor unlinked/non-constructible — routes to stdlib symbol, breaks React scheduler init #4873/globals/Intl: implementIntl.Segmenter(grapheme segmentation) —new Intl.Segmenter()throws, blocks string-width/wrap-ansi/ink #4877/regex:\p{Surrogate}Unicode property rejected ('invalid pattern') — Rust regex crate has no surrogate scalar values; blocks string-width/ink #4884/regex:\p{RGI_Emoji}(property of strings, /v flag) rejected — Rust regex crate has no string-properties; blocks string-width/ink #4889/react-reconciler 0.33 init crashes under Perry — SIGBUS on import (standalone) / 'undefined is not iterable' in createReconciler (in-ink); blocks all custom React renderers #4898/compilePackages:module.exports = classloses the class method table — static AND prototype methods readundefined(breaks stack-utils → ink) #4933/compilePackages: cross-module mutable object loses identity — react-reconciler'sReactSharedInternals.Hdispatcher invisible to react → 'Invalid hook call' null dispatcher (blocks all React renderers) #4950/compilePackages: inlineexport default class Name { … }(ESM) drops the prototype method table — onlyconstructorsurvives (ESM sibling of #4933; blocks ink render) #4976/node:process: imported default export & globalThis.process are an incomplete stub —.env/.stdout/.platformundefined (only bareprocessis wired); blocks terminal-size → ink #4987/core modules: bare specifiereventsresolves to a stub EventEmitter (empty prototype, no setMaxListeners/on/emit) —node:eventsworks; blocks signal-exit → ink #4995/compilePackages: reassigning arequire()-initialized top-levelvarcorrupts the binding (reads undefined / 'not defined') — breaks signal-exit → ink #5006 (all merged)