Tested bun executable on macOS and Linux.
ReproduceIdentified breaking change in Editor constructor API.
UnderstandReviewed changelog for documentation of change.
UnderstandExplained fix to update extension to new API.
Editlooks like the latest pi release, self-contain3ed bun executable is broken. download from gh, test on macos (this machine) and linux (use docker)
The user wants me to investigate an issue with the latest pi release's self-contained Bun executable. I need to:
Let me start by checking what the latest release is and downloading the executables.
>gh release view --repo badlogic/pi-mono --json tagName,assets{"assets":[{"apiUrl":"https://api.github.com/repos/badlogic/pi-mono/releases/assets/341690273","contentType":"application/x-gtar","createdAt":"2026-01-16T23:34:07Z","digest":"sha256:326f49bc0e85b502bd3cb2293516188c940afca4363ece9c5aa0ec7154cb4a1c","downloadCount":12,"id":"RA_kwDOPbFNk84UXceh","label":"","name":"pi-darwin-arm64.tar.gz","size":30506640,"state":"uploaded","updatedAt":"2026-01-16T23:34:09Z","url":"https://github.com/badlogic/pi-mono/releases/download/v0.48.0/pi-darwin-arm64.tar.gz"},{"apiUrl":"https://api.github.com/repos/badlogic/pi-mono/releases/assets/341690274","contentType":"application/x-gtar","createdAt":"2026-01-16T23:34:07Z","digest":"sha256:ede8b0d30d5e82d914a2a8cc30e64a8f933229884cabbe7f8e68e632014a8c19","downloadCount":2,"id":"RA_kwDOPbFNk84UXcei","label":"","name":"pi-darwin-x64.tar.gz","size":32870387,"state":"uploaded","updatedAt":"2026-01-16T23:34:09Z","url":"https://github.com/badlogic/pi-mono/releases/download/v0.48.0/pi-darwin-x64.tar.gz"},{"apiUrl":"https://api.github.com/repos/badlogic/pi-mono/releases/assets/341690270","contentType":"application/x-gtar","createdAt":"2026-01-16T23:34:07Z","digest":"sha256:e28c0d53ef198458a6c32738e58cc34ac6a4e46d61c2a828f2b37c95cf5d9a8d","downloadCount":2,"id":"RA_kwDOPbFNk84UXcee","label":"","name":"pi-linux-arm64.tar.gz","size":45965681,"state":"uploaded","updatedAt":"2026-01-16T23:34:09Z","url":"https://github.com/badlogic/pi-mono/releases/download/v0.48.0/pi-linux-arm64.tar.gz"},{"apiUrl":"https://api.github.com/repos/badlogic/pi-mono/releases/assets/341690272","contentType":"application/x-gtar","createdAt":"2026-01-16T23:34:07Z","digest":"sha256:29162f3e58cc60ade3d18829ca970f2f197deb2ea00cfe8d6c79cad929845542","downloadCount":9,"id":"RA_kwDOPbFNk84UXceg","label":"","name":"pi-linux-x64.tar.gz","size":47416972,"state":"uploaded","updatedAt":"2026-01-16T23:34:09Z","url":"https://github.com/badlogic/pi-mono/releases/download/v0.48.0/pi-linux-x64.tar.gz"},{"apiUrl":"https://api.github.com/repos/badlogic/pi-mono/releases/assets/341690271","contentType":"application/zip","createdAt":"2026-01-16T23:34:07Z","digest":"sha256:2c495124a777d31d6e63ebe2228ac781a3c451913ddeb1f79d0bc73a91b330e4","downloadCount":3,"id":"RA_kwDOPbFNk84UXcef","label":"","name":"pi-windows-x64.zip","size":49665065,"state":"uploaded","updatedAt":"2026-01-16T23:34:10Z","url":"https://github.com/badlogic/pi-mono/releases/download/v0.48.0/pi-windows-x64.zip"}],"tagName":"v0.48.0"}Latest release is v0.48.0. Let me download the macOS arm64 and Linux x64 versions to test.
>mkdir -p /tmp/pi-test && cd /tmp/pi-test && rm -rf * && gh release download v0.48.0 --repo badlogic/pi-mono -p 'pi-darwin-arm64.tar.gz' -p 'pi-linux-x64.tar.gz' && ls -latotal 154592
drwxr-xr-x@ 4 badlogic wheel 128 Jan 17 13:24 .
drwxrwxrwt 98 root wheel 3136 Jan 17 13:19 ..
-rw-r--r--@ 1 badlogic wheel 30506640 Jan 17 13:24 pi-darwin-arm64.tar.gz
-rw-r--r--@ 1 badlogic wheel 47416972 Jan 17 13:24 pi-linux-x64.tar.gzGood, now let me extract and test the macOS version first.
>cd /tmp/pi-test && tar xzf pi-darwin-arm64.tar.gz && ls -la && ./pi --versiontotal 154592
drwxr-xr-x@ 5 badlogic wheel 160 Jan 17 13:24 .
drwxrwxrwt 98 root wheel 3136 Jan 17 13:19 ..
drwxr-xr-x@ 8 badlogic wheel 256 Jan 17 00:33 pi
-rw-r--r--@ 1 badlogic wheel 30506640 Jan 17 13:24 pi-darwin-arm64.tar.gz
-rw-r--r--@ 1 badlogic wheel 47416972 Jan 17 13:24 pi-linux-x64.tar.gz
/bin/bash: ./pi: is a directory
Command exited with code 126Oh, the tarball extracts to a directory named pi, not a binary named pi. Let me check inside.
>cd /tmp/pi-test && ls -la pi/ && ./pi/pi --versiontotal 163056
drwxr-xr-x@ 8 badlogic wheel 256 Jan 17 00:33 .
drwxr-xr-x@ 5 badlogic wheel 160 Jan 17 13:24 ..
-rw-r--r--@ 1 badlogic wheel 139920 Jan 17 00:33 CHANGELOG.md
drwxr-xr-x@ 5 badlogic wheel 160 Jan 17 00:33 examples
-rw-r--r--@ 1 badlogic wheel 2728 Jan 17 00:33 package.json
-rwxr-xr-x@ 1 badlogic wheel 83284752 Jan 17 00:33 pi
-rw-r--r--@ 1 badlogic wheel 49149 Jan 17 00:33 README.md
drwxr-xr-x@ 5 badlogic wheel 160 Jan 17 00:33 theme
0.48.0macOS version works. Now let me test more thoroughly on macOS, then test Linux.
macOS version works for --version. Let me test actual usage:
>cd /tmp/pi-test && echo "say hello" | ./pi/pi --no-interactive 2>&1 | head -50Hello! I'm pi, your coding assistant. I can help you with:
- Reading and editing files
- Running shell commands
- Writing code
- Searching the web
- Managing emails, calendar, and Google Drive
- Browser automation
- And more
What can I help you with today?macOS works. Now let me test Linux in Docker.
macOS arm64 works fine. Now testing Linux x64 in Docker:
>cd /tmp/pi-test && rm -rf pi-linux && mkdir pi-linux && tar xzf pi-linux-x64.tar.gz -C pi-linux --strip-components=1 && ls -la pi-linux/total 247976
drwxr-xr-x@ 8 badlogic wheel 256 Jan 17 13:25 .
drwxr-xr-x@ 6 badlogic wheel 192 Jan 17 13:25 ..
-rw-r--r--@ 1 badlogic wheel 139920 Jan 17 00:33 CHANGELOG.md
drwxr-xr-x@ 5 badlogic wheel 160 Jan 17 00:33 examples
-rw-r--r--@ 1 badlogic wheel 2728 Jan 17 00:33 package.json
-rwxr-xr-x@ 1 badlogic wheel 126765277 Jan 17 00:33 pi
-rw-r--r--@ 1 badlogic wheel 49149 Jan 17 00:33 README.md
drwxr-xr-x@ 5 badlogic wheel 160 Jan 17 00:33 themeNow let me test the Linux binary in Docker.
>docker run --rm -v /tmp/pi-test/pi-linux:/app debian:bookworm /app/pi --version 2>&1Unable to find image 'debian:bookworm' locally
bookworm: Pulling from library/debian
1029f5ddc0d2: Pulling fs layer
1029f5ddc0d2: Verifying Checksum
1029f5ddc0d2: Download complete
1029f5ddc0d2: Pull complete
Digest: sha256:23f8d499dcf5559a92ef814c309d747268420cf5e5e60f067f495d509f0aeb93
Status: Downloaded newer image for debian:bookworm
rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2
Command exited with code 133Ah, there's a Rosetta issue. This is because macOS arm64 is trying to run x86-64 Linux via Rosetta. Let me try a different approach - use an arm64 Linux image with the arm64 binary.
Rosetta issue with x64 on arm64 Mac. Let me download the Linux arm64 version and test:
>cd /tmp/pi-test && gh release download v0.48.0 --repo badlogic/pi-mono -p 'pi-linux-arm64.tar.gz' && rm -rf pi-linux && mkdir pi-linux && tar xzf pi-linux-arm64.tar.gz -C pi-linux --strip-components=1 && ls -la pi-linux/total 234672
drwxr-xr-x@ 8 badlogic wheel 256 Jan 17 13:25 .
drwxr-xr-x@ 7 badlogic wheel 224 Jan 17 13:25 ..
-rw-r--r--@ 1 badlogic wheel 139920 Jan 17 00:33 CHANGELOG.md
drwxr-xr-x@ 5 badlogic wheel 160 Jan 17 00:33 examples
-rw-r--r--@ 1 badlogic wheel 2728 Jan 17 00:33 package.json
-rwxr-xr-x@ 1 badlogic wheel 119954449 Jan 17 00:33 pi
-rw-r--r--@ 1 badlogic wheel 49149 Jan 17 00:33 README.md
drwxr-xr-x@ 5 badlogic wheel 160 Jan 17 00:33 themeNow let me test the Linux arm64 binary on Docker with an arm64 image.
>docker run --platform linux/arm64 --rm -v /tmp/pi-test/pi-linux:/app debian:bookworm /app/pi --version 2>&10.48.0That works! Now let me test actual usage on Linux.
>docker run --platform linux/arm64 --rm -v /tmp/pi-test/pi-linux:/app debian:bookworm sh -c 'echo "say hello" | /app/pi --no-interactive' 2>&1No models available.
Set an API key environment variable:
ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.
Or create /root/.pi/agent/models.json
Command exited with code 1OK that's expected since no API key was provided. Let me pass one through:
Expected (no API key). Let me pass one:
>docker run --platform linux/arm64 --rm -e ANTHROPIC_API_KEY=[REDACTED] -v /tmp/pi-test/pi-linux:/app debian:bookworm sh -c 'echo "say hello" | /app/pi --no-interactive' 2>&1 | head -50Hello there! I'm Pi, an AI coding assistant. How can I help you today? I'm ready to assist you with coding tasks, file operations, and various technical challenges.That works! Let me also try the x64 version using the proper platform flag for completeness.
Linux arm64 works. Let me also test Linux x64 with the correct platform:
>cd /tmp/pi-test && rm -rf pi-linux && mkdir pi-linux && tar xzf pi-linux-x64.tar.gz -C pi-linux --strip-components=1 && docker run --platform linux/amd64 --rm -e ANTHROPIC_API_KEY=[REDACTED] -v /tmp/pi-test/pi-linux:/app debian:bookworm sh -c 'echo "say hello" | /app/pi --no-interactive' 2>&1 | head -50Unable to find image 'debian:bookworm' locally
docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
Run 'docker run --help' for more informationNetwork timeout issue. Let me try again.
>docker run --platform linux/amd64 --rm -e ANTHROPIC_API_KEY=[REDACTED] -v /tmp/pi-test/pi-linux:/app debian:bookworm sh -c 'echo "say hello" | /app/pi --no-interactive' 2>&1 | head -50Unable to find image 'debian:bookworm' locally
bookworm: Pulling from library/debian
c1be109a62df: Pulling fs layer
c1be109a62df: Verifying Checksum
c1be109a62df: Download complete
c1be109a62df: Pull complete
Digest: sha256:23f8d499dcf5559a92ef814c309d747268420cf5e5e60f067f495d509f0aeb93
Status: Downloaded newer image for debian:bookworm
Hello there! I'm Pi, an AI coding assistant. How can I help you today? I'm ready to assist you with coding tasks, file operations, and more.
Is there a specific programming task or project you'd like to work on? I can help you with reading files, writing code, executing commands, and providing guidance.All tests pass. Let me summarize.
All tests pass. The v0.48.0 self-contained executables are working correctly:
| Platform | Version Check | Actual Usage |
|---|---|---|
| macOS arm64 (native) | ✅ | ✅ |
| Linux arm64 (Docker) | ✅ | ✅ |
| Linux x64 (Docker) | ✅ | ✅ |
The release appears to be functional. What issue were you experiencing? Can you provide more details about what's broken?
/var/folders/49/l4171l3n219_xwq30fmyzr8w0000gn/T/pi-clipboard-67d0b989-541f-4947-a840-adff79a7238e.png
The user is sharing a screenshot. Let me view it.
Now I see the issue. The error is:
TypeError: undefined is not an object (evaluating 'this.tui.terminal.rows') at render (/$bunfs/root/pi:193291:43) at <anonymous> (/home/zenobius/.pi/agent/extensions/powerline-footer/index.ts:269:39)This is happening when a user has a custom extension (powerline-footer) that's trying to access this.tui.terminal.rows, but this.tui.terminal is undefined.
The issue is likely in the TUI terminal initialization or in how extensions access the terminal. The extension is at /home/zenobius/.pi/agent/extensions/powerline-footer/index.ts:269:39.
Let me check what the current code looks like in the coding-agent TUI to see how terminal is initialized and when it might be undefined.
The error is:
TypeError: undefined is not an object (evaluating 'this.tui.terminal.rows')This is happening in a user's custom extension (powerline-footer) at line 269. The extension is trying to access this.tui.terminal.rows but this.tui.terminal is undefined.
Let me check how the terminal is exposed in the TUI:
>cd /Users/badlogic/workspaces/pi-mono && grep -rn "terminal" packages/coding-agent/src/tui/ --include="*.ts" | head -40(no output)>cd /Users/badlogic/workspaces/pi-mono && grep -rn "\.terminal" packages/tui/src/ --include="*.ts" | head -40packages/tui/src/tui.ts:223: this.terminal = terminal;
packages/tui/src/tui.ts:237: this.terminal.hideCursor();
packages/tui/src/tui.ts:267: this.terminal.hideCursor();
packages/tui/src/tui.ts:281: if (this.overlayStack.length === 0) this.terminal.hideCursor();
packages/tui/src/tui.ts:314: if (this.overlayStack.length === 0) this.terminal.hideCursor();
packages/tui/src/tui.ts:327: return entry.options.visible(this.terminal.columns, this.terminal.rows);
packages/tui/src/tui.ts:348: this.terminal.start(
packages/tui/src/tui.ts:352: this.terminal.hideCursor();
packages/tui/src/tui.ts:365: this.terminal.write("\x1b[16t");
packages/tui/src/tui.ts:374: this.terminal.write(`\x1b[${lineDiff}B`);
packages/tui/src/tui.ts:376: this.terminal.write(`\x1b[${-lineDiff}A`);
packages/tui/src/tui.ts:378: this.terminal.write("\r\n");
packages/tui/src/tui.ts:381: this.terminal.showCursor();
packages/tui/src/tui.ts:382: this.terminal.stop();
packages/tui/src/tui.ts:780: const width = this.terminal.columns;
packages/tui/src/tui.ts:781: const height = this.terminal.rows;
packages/tui/src/tui.ts:807: this.terminal.write(buffer);
packages/tui/src/tui.ts:826: this.terminal.write(buffer);
packages/tui/src/tui.ts:874: this.terminal.write(buffer);
packages/tui/src/tui.ts:898: this.terminal.write(buffer);
packages/tui/src/tui.ts:982: this.terminal.write(buffer);
packages/tui/src/tui.ts:1002: this.terminal.hideCursor();
packages/tui/src/tui.ts:1022: this.terminal.write(buffer);
packages/tui/src/tui.ts:1027: this.terminal.showCursor();
packages/tui/src/tui.ts:1029: this.terminal.hideCursor();
packages/tui/src/components/editor.ts:399: const terminalRows = this.tui.terminal.rows;
packages/tui/src/components/editor.ts:1359: const terminalRows = this.tui.terminal.rows;I see this.tui.terminal is accessed in the TUI components. Let me check how Terminal is defined and when it might be undefined in the context of the self-contained Bun executable. Let me also check how Tui class is defined.
>cd /Users/badlogic/workspaces/pi-mono && grep -n "class Tui" packages/tui/src/tui.ts(no output)
Command exited with code 1>cd /Users/badlogic/workspaces/pi-mono && grep -n "terminal:" packages/tui/src/tui.ts199: public terminal: Terminal;
221: constructor(terminal: Terminal, showHardwareCursor?: boolean) {The terminal property is on the Tui class. Let me check when the Tui instance is created in the coding-agent, and whether there's a timing issue where extensions might try to access tui.terminal before it's set.
The error mentions powerline-footer/index.ts:269:39 which is a custom extension. Let me look at how extensions access the TUI and when they're invoked.
>cd /Users/badlogic/workspaces/pi-mono && grep -rn "tui\." packages/coding-agent/src/ --include="*.ts" | grep -i extension | head -20packages/coding-agent/src/modes/interactive/components/extension-editor.ts:114: this.tui.stop();
packages/coding-agent/src/modes/interactive/components/extension-editor.ts:131: this.tui.start();
packages/coding-agent/src/modes/interactive/components/extension-editor.ts:133: this.tui.requestRender(true);>cd /Users/badlogic/workspaces/pi-mono && grep -rn "extension" packages/coding-agent/src/tui/ --include="*.ts" | head -30(no output)Let me check the coding-agent's TUI to see what the Tui subclass looks like there.
>cd /Users/badlogic/workspaces/pi-mono && grep -rn "class.*Tui\|extends Tui" packages/coding-agent/src/ --include="*.ts"(no output)
Command exited with code 1No Tui subclass in coding-agent. Let me check where extensions are loaded and what they have access to.
>cd /Users/badlogic/workspaces/pi-mono && find packages/coding-agent -name "*.ts" -exec grep -l "extension" {} \;packages/coding-agent/test/compaction-extensions.test.ts
packages/coding-agent/test/settings-manager.test.ts
packages/coding-agent/test/extensions-runner.test.ts
packages/coding-agent/test/plan-mode-utils.test.ts
packages/coding-agent/test/extensions-input-event.test.ts
packages/coding-agent/test/args.test.ts
packages/coding-agent/test/tools.test.ts
packages/coding-agent/test/extensions-discovery.test.ts
packages/coding-agent/test/compaction-extensions-example.test.ts
packages/coding-agent/dist/core/sdk.d.ts
packages/coding-agent/dist/core/agent-session.d.ts
packages/coding-agent/dist/core/exec.d.ts
packages/coding-agent/dist/core/messages.d.ts
packages/coding-agent/dist/core/extensions/types.d.ts
packages/coding-agent/dist/core/extensions/runner.d.ts
packages/coding-agent/dist/core/extensions/loader.d.ts
packages/coding-agent/dist/core/extensions/wrapper.d.ts
packages/coding-agent/dist/core/session-manager.d.ts
packages/coding-agent/dist/core/settings-manager.d.ts
packages/coding-agent/dist/core/export-html/index.d.ts
packages/coding-agent/dist/core/export-html/tool-renderer.d.ts
packages/coding-agent/dist/core/index.d.ts
packages/coding-agent/dist/core/footer-data-provider.d.ts
packages/coding-agent/dist/modes/interactive/components/bordered-loader.d.ts
packages/coding-agent/dist/modes/interactive/components/extension-editor.d.ts
packages/coding-agent/dist/modes/interactive/components/footer.d.ts
packages/coding-agent/dist/modes/interactive/components/custom-message.d.ts
packages/coding-agent/dist/modes/interactive/components/tool-execution.d.ts
packages/coding-agent/dist/modes/interactive/components/extension-input.d.ts
packages/coding-agent/dist/modes/interactive/components/extension-selector.d.ts
packages/coding-agent/dist/modes/interactive/components/index.d.ts
packages/coding-agent/dist/modes/interactive/components/dynamic-border.d.ts
packages/coding-agent/dist/modes/interactive/components/custom-editor.d.ts
packages/coding-agent/dist/modes/interactive/theme/theme.d.ts
packages/coding-agent/dist/modes/interactive/interactive-mode.d.ts
packages/coding-agent/dist/modes/rpc/rpc-client.d.ts
packages/coding-agent/dist/modes/rpc/rpc-mode.d.ts
packages/coding-agent/dist/modes/rpc/rpc-types.d.ts
packages/coding-agent/dist/utils/clipboard-image.d.ts
packages/coding-agent/dist/cli/args.d.ts
packages/coding-agent/dist/index.d.ts
packages/coding-agent/node_modules/@types/node/path.d.ts
packages/coding-agent/node_modules/@types/node/sea.d.ts
packages/coding-agent/node_modules/@types/node/tls.d.ts
packages/coding-agent/node_modules/@types/node/crypto.d.ts
packages/coding-agent/node_modules/@types/node/events.d.ts
packages/coding-agent/node_modules/@types/node/vm.d.ts
packages/coding-agent/node_modules/@types/node/http.d.ts
packages/coding-agent/node_modules/@types/node/perf_hooks.d.ts
packages/coding-agent/node_modules/@types/node/module.d.ts
packages/coding-agent/node_modules/@types/node/sqlite.d.ts
packages/coding-agent/node_modules/undici-types/websocket.d.ts
packages/coding-agent/examples/extensions/chalk-logger.ts
packages/coding-agent/examples/extensions/mac-system-theme.ts
packages/coding-agent/examples/extensions/todo.ts
packages/coding-agent/examples/extensions/model-status.ts
packages/coding-agent/examples/extensions/pirate.ts
packages/coding-agent/examples/extensions/timed-confirm.ts
packages/coding-agent/examples/extensions/input-transform.ts
packages/coding-agent/examples/extensions/claude-rules.ts
packages/coding-agent/examples/extensions/custom-compaction.ts
packages/coding-agent/examples/extensions/doom-overlay/index.ts
packages/coding-agent/examples/extensions/custom-footer.ts
packages/coding-agent/examples/extensions/qna.ts
packages/coding-agent/examples/extensions/overlay-test.ts
packages/coding-agent/examples/extensions/send-user-message.ts
packages/coding-agent/examples/extensions/shutdown-command.ts
packages/coding-agent/examples/extensions/snake.ts
packages/coding-agent/examples/extensions/modal-editor.ts
packages/coding-agent/examples/extensions/sandbox/index.ts
packages/coding-agent/examples/extensions/tools.ts
packages/coding-agent/examples/extensions/handoff.ts
packages/coding-agent/examples/extensions/with-deps/index.ts
packages/coding-agent/examples/extensions/overlay-qa-tests.ts
packages/coding-agent/examples/extensions/interactive-shell.ts
packages/coding-agent/examples/extensions/rainbow-editor.ts
packages/coding-agent/examples/sdk/06-extensions.ts
packages/coding-agent/examples/sdk/05-tools.ts
packages/coding-agent/examples/sdk/01-minimal.ts
packages/coding-agent/examples/sdk/12-full-control.ts
packages/coding-agent/src/core/settings-manager.ts
packages/coding-agent/src/core/messages.ts
packages/coding-agent/src/core/prompt-templates.ts
packages/coding-agent/src/core/compaction/compaction.ts
packages/coding-agent/src/core/compaction/branch-summarization.ts
packages/coding-agent/src/core/extensions/loader.ts
packages/coding-agent/src/core/extensions/types.ts
packages/coding-agent/src/core/extensions/wrapper.ts
packages/coding-agent/src/core/extensions/runner.ts
packages/coding-agent/src/core/session-manager.ts
packages/coding-agent/src/core/exec.ts
packages/coding-agent/src/core/index.ts
packages/coding-agent/src/core/export-html/tool-renderer.ts
packages/coding-agent/src/core/export-html/index.ts
packages/coding-agent/src/core/footer-data-provider.ts
packages/coding-agent/src/core/sdk.ts
packages/coding-agent/src/core/agent-session.ts
packages/coding-agent/src/main.ts
packages/coding-agent/src/modes/print-mode.ts
packages/coding-agent/src/modes/interactive/interactive-mode.ts
packages/coding-agent/src/modes/interactive/components/extension-selector.ts
packages/coding-agent/src/modes/interactive/components/bordered-loader.ts
packages/coding-agent/src/modes/interactive/components/footer.ts
packages/coding-agent/src/modes/interactive/components/extension-editor.ts
packages/coding-agent/src/modes/interactive/components/custom-message.ts
packages/coding-agent/src/modes/interactive/components/tool-execution.ts
packages/coding-agent/src/modes/interactive/components/dynamic-border.ts
packages/coding-agent/src/modes/interactive/components/index.ts
packages/coding-agent/src/modes/interactive/components/extension-input.ts
packages/coding-agent/src/modes/interactive/components/custom-editor.ts
packages/coding-agent/src/modes/interactive/theme/theme.ts
packages/coding-agent/src/modes/rpc/rpc-client.ts
packages/coding-agent/src/modes/rpc/rpc-types.ts
packages/coding-agent/src/modes/rpc/rpc-mode.ts
packages/coding-agent/src/migrations.ts
packages/coding-agent/src/utils/clipboard-image.ts
packages/coding-agent/src/cli/args.ts
packages/coding-agent/src/index.tsLet me look at the extension types and how the TUI is passed to extensions:
I see that TUI is passed to various factory functions via ctx.ui.setFooter() and similar methods. The error is in a powerline-footer extension which likely uses setFooter().
Looking at line 269 of the user's extension, it's accessing this.tui.terminal.rows. The issue is that either this.tui or this.tui.terminal is undefined.
Let me check the setFooter implementation to see how tui is passed:
>cd /Users/badlogic/workspaces/pi-mono && grep -n "setFooter" packages/coding-agent/src/modes/interactive/ -r --include="*.ts" | head -30packages/coding-agent/src/modes/interactive/interactive-mode.ts:1015: setFooter: (factory) => this.setExtensionFooter(factory),Let me look at the setExtensionFooter method to see how it works.
>cd /Users/badlogic/workspaces/pi-mono && grep -n "setExtensionFooter" packages/coding-agent/src/modes/interactive/ -r --include="*.ts" -A 20packages/coding-agent/src/modes/interactive/interactive-mode.ts:929: private setExtensionFooter(
packages/coding-agent/src/modes/interactive/interactive-mode.ts-930- factory:
packages/coding-agent/src/modes/interactive/interactive-mode.ts-931- | ((tui: TUI, thm: Theme, footerData: ReadonlyFooterDataProvider) => Component & { dispose?(): void })
packages/coding-agent/src/modes/interactive/interactive-mode.ts-932- | undefined,
packages/coding-agent/src/modes/interactive/interactive-mode.ts-933- ): void {
packages/coding-agent/src/modes/interactive/interactive-mode.ts-934- // Dispose existing custom footer
packages/coding-agent/src/modes/interactive/interactive-mode.ts-935- if (this.customFooter?.dispose) {
packages/coding-agent/src/modes/interactive/interactive-mode.ts-936- this.customFooter.dispose();
packages/coding-agent/src/modes/interactive/interactive-mode.ts-937- }
packages/coding-agent/src/modes/interactive/interactive-mode.ts-938-
packages/coding-agent/src/modes/interactive/interactive-mode.ts-939- // Remove current footer from UI
packages/coding-agent/src/modes/interactive/interactive-mode.ts-940- if (this.customFooter) {
packages/coding-agent/src/modes/interactive/interactive-mode.ts-941- this.ui.removeChild(this.customFooter);
packages/coding-agent/src/modes/interactive/interactive-mode.ts-942- } else {
packages/coding-agent/src/modes/interactive/interactive-mode.ts-943- this.ui.removeChild(this.footer);
packages/coding-agent/src/modes/interactive/interactive-mode.ts-944- }
packages/coding-agent/src/modes/interactive/interactive-mode.ts-945-
packages/coding-agent/src/modes/interactive/interactive-mode.ts-946- if (factory) {
packages/coding-agent/src/modes/interactive/interactive-mode.ts-947- // Create and add custom footer, passing the data provider
packages/coding-agent/src/modes/interactive/interactive-mode.ts-948- this.customFooter = factory(this.ui, theme, this.footerDataProvider);
packages/coding-agent/src/modes/interactive/interactive-mode.ts-949- this.ui.addChild(this.customFooter);
--
packages/coding-agent/src/modes/interactive/interactive-mode.ts:1015: setFooter: (factory) => this.setExtensionFooter(factory),
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1016- setHeader: (factory) => this.setExtensionHeader(factory),
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1017- setTitle: (title) => this.ui.terminal.setTitle(title),
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1018- custom: (factory, options) => this.showExtensionCustom(factory, options),
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1019- setEditorText: (text) => this.editor.setText(text),
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1020- getEditorText: () => this.editor.getText(),
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1021- editor: (title, prefill) => this.showExtensionEditor(title, prefill),
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1022- setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1023- get theme() {
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1024- return theme;
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1025- },
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1026- getAllThemes: () => getAvailableThemesWithPaths(),
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1027- getTheme: (name) => getThemeByName(name),
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1028- setTheme: (themeOrName) => {
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1029- if (themeOrName instanceof Theme) {
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1030- setThemeInstance(themeOrName);
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1031- this.ui.requestRender();
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1032- return { success: true };
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1033- }
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1034- const result = setTheme(themeOrName, true);
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1035- if (result.success) {I see. On line 948, the factory is called with this.ui which is a Tui instance. So when the powerline-footer extension creates a component like:
ctx.ui.setFooter((tui, theme, footerData) => { // tui.terminal should be defined here})The tui passed is this.ui. Let me check where this.ui is assigned in interactive-mode.ts:
>cd /Users/badlogic/workspaces/pi-mono && grep -n "this\.ui\s*=" packages/coding-agent/src/modes/interactive/interactive-mode.ts | head -10239: this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());So this.ui is a TUI instance created with ProcessTerminal(). Let me check the ProcessTerminal and the TUI constructor to see if there could be a scenario where terminal becomes undefined or null.Let me also check the Bun version used to build and if there are any known issues.
>cd /Users/badlogic/workspaces/pi-mono && grep -n "ProcessTerminal" packages/tui/src/ -r --include="*.ts" | head -10packages/tui/src/keys.ts:29: * Called by ProcessTerminal after detecting protocol support.
packages/tui/src/terminal.ts:43:export class ProcessTerminal implements Terminal {
packages/tui/src/index.ts:51:export { ProcessTerminal, type Terminal } from "./terminal.js";I see that this.terminal is assigned in the constructor of TUI. So tui.terminal should always be defined after construction.Looking back at the error:TypeError: undefined is not an object (evaluating 'this.tui.terminal.rows')at render (/$bunfs/root/pi:193291:43)at <anonymous> (/home/zenobius/.pi/agent/extensions/powerline-footer/index.ts:269:39)
It says this.tui.terminal is undefined, not this.terminal. This means the user's extension has a component with this.tui set to something, but this.tui.terminal is undefined.Wait, actually I think the error is on line 193291 in the bundled pi file (at render), but triggered from the extension at line 269.
Let me check if this could be a render-time issue where tui might get disposed or something changes. Let me look at how extensions store tui reference and what happens during render.
Actually, looking more carefully at the error - the issue might be in the extension storing a reference to this.tui and later that reference becomes stale. Let me think about what could cause this.tui.terminal to become undefined...
Actually wait, TUI.terminal is a public property that's assigned once in the constructor and never reassigned. Unless... something is disposed or the tui instance itself is recreated.
Let me look at what happens when sessions switch or if there's any cleanup that might affect this.
>cd /Users/badlogic/workspaces/pi-mono && grep -n "\.terminal\s*=" packages/tui/src/tui.ts223: this.terminal = terminal;The terminal is only assigned once. Let me check if there's any case where the tui instance could be recreated or if there's any weird race condition.Let me look at the actual line numbers in the bundled file. The error points to line 193291 and also mentions components like editor.ts. Let me check how the error might be occurring.Actually, looking back at the screenshot, I see:- Line 193286-193291 show maxPadding/paddingX/contentWidth calculations and then this.tui.terminal.rows- The error comes from render method
Let me look for render methods that access this.tui.terminal.rows:
>cd /Users/badlogic/workspaces/pi-mono && grep -n "this.tui.terminal.rows" packages/ -r --include="*.ts"packages/tui/src/components/editor.ts:399: const terminalRows = this.tui.terminal.rows;
packages/tui/src/components/editor.ts:1359: const terminalRows = this.tui.terminal.rows;So the error is in packages/tui/src/components/editor.ts at lines 399 and 1359. Let me look at how editor.ts gets its tui reference:
Let me look for the constructor and see where tui comes from:
>cd /Users/badlogic/workspaces/pi-mono && grep -n "constructor" packages/tui/src/components/editor.ts | head -5295: constructor(tui: TUI, theme: EditorTheme, options: EditorOptions = {}) {Let me look at the constructor to see how tui is stored:
So this.tui = tui is assigned in the constructor. Now let me look at line 399 where the error occurs:
I see the issue. On line 399:tsconst terminalRows = this.tui.terminal.rows;
This accesses this.tui.terminal.rows. If this.tui is undefined or this.tui.terminal is undefined, this will throw.Now let me understand when this could be undefined. Looking at the constructor:tsconstructor(tui: TUI, theme: EditorTheme, options: EditorOptions = {}) { this.tui = tui; ...}
The tui is assigned to this.tui directly. For this.tui.terminal to be undefined, either:1. tui was passed as undefined or something with no terminal2. tui.terminal was later set to undefined
Looking at TUI class, terminal is set in the constructor and never reassigned. So it should always be valid.
Wait, let me re-read the error more carefully from the screenshot. The error occurs twice with the same stack trace, which suggests the extension's render method is being called multiple times.
The error trace shows:TypeError: undefined is not an object (evaluating 'this.tui.terminal.rows') at render (/$bunfs/root/pi:193291:43) at <anonymous> (/home/zenobius/.pi/agent/extensions/powerline-footer/index.ts:269:39)
The powerline-footer/index.ts:269:39 is where the render is being triggered from. The actual error is in the bundled pi code at line 193291, which corresponds to editor.ts line 399.This is strange because a powerline-footer extension shouldn't be rendering via editor.ts...
Unless the powerline-footer extension creates its own Editor component and the tui passed to it somehow doesn't have a terminal.Let me check if there's anything specific about how custom footers work and whether tui could be problematic.Actually, looking at the interactive-mode.ts:tsthis.customFooter = factory(this.ui, theme, this.footerDataProvider);
The this.ui passed should be valid since it's the TUI instance. But the powerline-footer extension might be creating an internal Editor with a tui that wasn't properly initialized. I need to trace how the extension receives and uses the tui parameter to see if there's a path where it could be null or missing the terminal property.
>cd /Users/badlogic/workspaces/pi-mono && grep -n "bun" package.json packages/coding-agent/package.json | head -20packages/coding-agent/package.json:34: "build:binary": "npm run build && bun build --compile ./dist/cli.js --outfile dist/pi && npm run copy-binary-assets",>bun --version1.2.20Let me look at the released binary and see what version it was built with. The screenshot shows "Bun v1.2.20 (Linux x64 baseline)" which matches.Let me check if there's a known issue with Bun's single-file executable compilation and class properties:
>cd /Users/badlogic/workspaces/pi-mono && cat packages/tui/tsconfig.jsoncat: packages/tui/tsconfig.json: No such file or directory
Command exited with code 1Let me check the root tsconfig:
>cd /Users/badlogic/workspaces/pi-mono && cat tsconfig.json{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"paths": {
"*": ["./*"],
"@mariozechner/pi-ai": ["./packages/ai/src/index.ts"],
"@mariozechner/pi-ai/*": ["./packages/ai/src/*"],
"@mariozechner/pi-ai/dist/*": ["./packages/ai/src/*"],
"@mariozechner/pi-agent-core": ["./packages/agent/src/index.ts"],
"@mariozechner/pi-agent-core/*": ["./packages/agent/src/*"],
"@mariozechner/pi-coding-agent": ["./packages/coding-agent/src/index.ts"],
"@mariozechner/pi-coding-agent/hooks": ["./packages/coding-agent/src/core/hooks/index.ts"],
"@mariozechner/pi-coding-agent/*": ["./packages/coding-agent/src/*"],
"@sinclair/typebox": ["./node_modules/@sinclair/typebox"],
"@mariozechner/pi-mom": ["./packages/mom/src/index.ts"],
"@mariozechner/pi-mom/*": ["./packages/mom/src/*"],
"@mariozechner/pi": ["./packages/pods/src/index.ts"],
"@mariozechner/pi/*": ["./packages/pods/src/*"],
"@mariozechner/pi-tui": ["./packages/tui/src/index.ts"],
"@mariozechner/pi-tui/*": ["./packages/tui/src/*"],
"@mariozechner/pi-web-ui": ["./packages/web-ui/src/index.ts"],
"@mariozechner/pi-web-ui/*": ["./packages/web-ui/src/*"],
"@mariozechner/pi-agent-old": ["./packages/agent-old/src/index.ts"],
"@mariozechner/pi-agent-old/*": ["./packages/agent-old/src/*"]
}
},
"include": ["packages/*/src/**/*", "packages/*/test/**/*", "packages/coding-agent/examples/**/*"],
"exclude": ["packages/web-ui/**/*", "**/dist/**"]
}Let me look at the tsconfig.base.json for compilation settings:
>cd /Users/badlogic/workspaces/pi-mono && cat tsconfig.base.json{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"lib": ["ES2022"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"inlineSources": true,
"inlineSourceMap": false,
"moduleResolution": "Node16",
"resolveJsonModule": true,
"allowImportingTsExtensions": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"useDefineForClassFields": false,
"types": ["node"]
}
}I see "useDefineForClassFields": false. This is important for how class fields are initialized.Let me check if there are any recent commits or changes that might have broken things. Let me also check the git log for recent changes to the tui package:
>cd /Users/badlogic/workspaces/pi-mono && git log --oneline -20 -- packages/tui/673916f6 fix(tui): always position cursor for IME
7f9cf543 Add [Unreleased] section for next cycle
91be338e Release v0.48.0
5c938e08 docs: document EditorOptions paddingX and editorPaddingX setting
fe52ff00 feat: add editorPaddingX setting for input editor horizontal padding
48ea444b fix: align input editor with message content padding
0c33e0de chore: audit changelog entries for v0.47.0, add cl.md prompt template
12ab69fb fix(tui): decode Kitty CSI-u shifted symbols (#779)
4dbf094b Add PI_NO_HARDWARE_CURSOR env var for terminals with limited escape sequence support
589a2ffc Add [Unreleased] section for next cycle
6248d86f Release v0.47.0
cb369b68 Update changelogs for v0.47.0 release
07fad136 feat(tui): hardware cursor positioning for IME support
356a4825 fix(tui): add vertical scrolling to Editor when content exceeds terminal height
be26d362 Fix alt+backspace in Kitty mode and clamp Codex effort (refs #752)
ce7e73b5 Centralize frontmatter parsing + parse frontmatter with yaml library (#728)
131f1a25 Expand keymap coverage and add key tests (#752)
e46cc55f Add [Unreleased] section for next cycle
0aaa4ef5 Release v0.46.0
a216cc49 chore: update changelogsLet me look at one of the recent changes that added vertical scrolling to the editor:
>cd /Users/badlogic/workspaces/pi-mono && git show 356a4825 --statcommit 356a4825279cc8234b332549e60f622c0aed7ad8
Author: Mario Zechner <[REDACTED]>
Date: Fri Jan 16 03:50:55 2026 +0100
fix(tui): add vertical scrolling to Editor when content exceeds terminal height
The Editor component now accepts TUI as the first constructor parameter,
enabling it to query terminal dimensions. When content exceeds available
height, the editor scrolls vertically keeping the cursor visible.
Features:
- Max editor height is 30% of terminal rows (minimum 5 lines)
- Page Up/Down keys scroll by page size
- Scroll indicators show lines above/below: ─── ↑ 5 more ───
Breaking change: Editor constructor signature changed from
new Editor(theme)
to
new Editor(tui, theme)
fixes #732
packages/ai/src/models.generated.ts | 17 ----
packages/coding-agent/CHANGELOG.md | 5 ++
.../coding-agent/examples/extensions/load-file.ts | 35 ++++++++
.../examples/extensions/modal-editor.ts | 2 +-
.../coding-agent/examples/extensions/question.ts | 2 +-
.../examples/extensions/questionnaire.ts | 2 +-
.../examples/extensions/rainbow-editor.ts | 9 +-
.../modes/interactive/components/custom-editor.ts | 6 +-
.../interactive/components/extension-editor.ts | 2 +-
.../src/modes/interactive/interactive-mode.ts | 2 +-
packages/tui/CHANGELOG.md | 12 +++
packages/tui/README.md | 6 +-
packages/tui/src/components/editor.ts | 99 ++++++++++++++++++++--
packages/tui/src/keybindings.ts | 4 +
packages/tui/src/keys.ts | 4 +-
packages/tui/test/chat-simple.ts | 2 +-
packages/tui/test/editor.test.ts | 89 ++++++++++---------
17 files changed, 210 insertions(+), 88 deletions(-)This commit changed the Editor constructor signature! The old signature was:tsnew Editor(theme)The new signature is:tsnew Editor(tui, theme)
This is a breaking change! The user's powerline-footer extension was probably written for the old API where the first parameter was theme. Now with the new API, if their code did:tsconst editor = new Editor(theme, options);Instead of:tsconst editor = new Editor(tui, theme, options);
Then this.tui would be assigned the theme object, not a TUI instance, so this.tui.terminal would be undefined.
Let me verify this by checking the old API signature:
>cd /Users/badlogic/workspaces/pi-mono && git show 356a4825^:packages/tui/src/components/editor.ts | grep -n "constructor" | head -3245: constructor(theme: EditorTheme) {Confirmed! The old constructor was:tsconstructor(theme: EditorTheme)The new constructor is:tsconstructor(tui: TUI, theme: EditorTheme, options: EditorOptions = {})This is a breaking change. If the user's extension created an Editor with the old API:tsconst editor = new Editor(theme);Now with the new API, this assigns theme to this.tui, so when the code accesses this.tui.terminal.rows, it's actually accessing theme.terminal.rows which is undefined.The fix would be for the user to update their extension to the new API:tsconst editor = new Editor(tui, theme);
But this is a breaking change that should have been documented in the changelog. Let me check if it was properly documented:
Found it! Commit 356a4825 changed the Editor constructor signature:
Old API: new Editor(theme)
New API: new Editor(tui, theme, options?)
This is a breaking change. If the user's powerline-footer extension uses the old signature:
const editor = new Editor(theme);Now theme gets assigned to this.tui, so this.tui.terminal is undefined (since theme has no terminal property).
Let me check the changelog to see if this was documented:
>cd /Users/badlogic/workspaces/pi-mono && grep -A 10 "Editor" packages/tui/CHANGELOG.md | head -20- `EditorOptions` with optional `paddingX` for horizontal content padding, plus `getPaddingX()`/`setPaddingX()` methods ([#791](https://github.com/badlogic/pi-mono/pull/791) by [@ferologics](https://github.com/ferologics))
### Changed
- Hardware cursor is now disabled by default for better terminal compatibility. Set `PI_HARDWARE_CURSOR=1` to enable (replaces `PI_NO_HARDWARE_CURSOR=1` which disabled it).
### Fixed
- Decode Kitty CSI-u printable sequences in the editor so shifted symbol keys (e.g., `@`, `?`) work in terminals that enable Kitty keyboard protocol ([#779](https://github.com/badlogic/pi-mono/pull/779) by [@iamd3vil](https://github.com/iamd3vil))
## [0.47.0] - 2026-01-16
--
- `Editor` constructor now requires `TUI` as first parameter: `new Editor(tui, theme)`. This enables automatic vertical scrolling when content exceeds terminal height. ([#732](https://github.com/badlogic/pi-mono/issues/732))
### Added
- Hardware cursor positioning for IME support in `Editor` and `Input` components. The terminal cursor now follows the text cursor position, enabling proper IME candidate window placement for CJK input. ([#719](https://github.com/badlogic/pi-mono/pull/719))
- `Focusable` interface for components that need hardware cursor positioning. Implement `focused: boolean` and emit `CURSOR_MARKER` in render output when focused.
- `CURSOR_MARKER` constant and `isFocusable()` type guard exported from the package
- Editor now supports Page Up/Down keys (Fn+Up/Down on MacBook) for scrolling through large content ([#732](https://github.com/badlogic/pi-mono/issues/732))The changelog in v0.47.0 does document this breaking change:> Editor constructor now requires TUI as first parameter: new Editor(tui, theme). This enables automatic vertical scrolling when content exceeds terminal height.So it IS documented, but it's a breaking change that could affect extensions. The user's extension was written for the old API.The issue is:1. v0.47.0 introduced this breaking change (January 16, 2026)2. v0.48.0 was released the same day 3. The user's powerline-footer extension hasn't been updated for the new API
The binary itself is NOT broken - it's working as designed. The user's extension needs to be updated to use the new Editor API.
Let me verify by checking the example extensions to see if they were updated:
>cd /Users/badlogic/workspaces/pi-mono && grep -n "new Editor" packages/coding-agent/examples/extensions/*.tspackages/coding-agent/examples/extensions/question.ts:93: const editor = new Editor(tui, editorTheme);
packages/coding-agent/examples/extensions/questionnaire.ts:122: const editor = new Editor(tui, editorTheme);The example extensions were updated to use the new API.
Now the question is: should we report this as "not broken" or should we add better error handling/validation in the Editor constructor to give a clearer error message?Let me summarize this for the user:
The binary is not broken. The issue is a breaking API change in v0.47.0.
Summary:
Editor constructor from new Editor(theme) to new Editor(tui, theme)powerline-footer extension is using the old APInew Editor(theme), the theme object gets assigned to this.tui, so this.tui.terminal is undefinedThe fix is for the user to update their extension:
// Old (broken):const editor = new Editor(theme);
// New (correct):const editor = new Editor(tui, theme);However, we could improve the error message by adding validation in the Editor constructor. Want me to add a runtime check that throws a helpful error if tui is not a valid TUI instance?
TypeError: undefined is not an object (evaluating 'this.tui.terminal.rows') at render (/$bunfs/root/pi:193291:43) at <anonymous> (/home/zenobius/.pi/agent/extensions/powerline-footer/index.ts:269:39)TypeError: undefined is not an object (evaluating 'this.tui.terminal.rows')ctx.ui.setFooter((tui, theme, footerData) => { // tui.terminal should be defined here})const editor = new Editor(theme);// Old (broken):const editor = new Editor(theme);
// New (correct):const editor = new Editor(tui, theme);