Reviewed PR details, comments, commits, and diffs.
UnderstandAdded missing changelog entry.
EditPushed changes and left contributor comment.
CleanupYou are given one or more GitHub PR URLs: https://github.com/badlogic/pi-mono/pull/1039 im not convinced about moving the renderInitialMessages shit
For each PR URL, do the following in order:
packages/*/CHANGELOG.md files. Report whether an entry exists. If missing, state that a changelog entry is required before merge and that you will add it if the user decides to merge. Follow the changelog format rules in AGENTS.md. Verify:
### Breaking Changes, ### Added, ### Fixed, etc.)Fixed foo ([#123](https://github.com/badlogic/pi-mono/pull/123) by [@user](https://github.com/user))### Breaking Changes, not just ### FixedOutput format per PR: PR: Changelog:
If no issues are found, say so under Bad and Ugly.
The user wants me to review a GitHub PR. Let me start by reading the PR details using the GitHub CLI.
>gh pr view 1039 --json title,body,comments,labels,state,commits,files,head,baseUnknown JSON field: "head"
Available fields:
additions
assignees
author
autoMergeRequest
baseRefName
baseRefOid
body
changedFiles
closed
closedAt
closingIssuesReferences
comments
commits
createdAt
deletions
files
fullDatabaseId
headRefName
headRefOid
headRepository
headRepositoryOwner
id
isCrossRepository
isDraft
labels
latestReviews
maintainerCanModify
mergeCommit
mergeStateStatus
mergeable
mergedAt
mergedBy
milestone
number
potentialMergeCommit
projectCards
projectItems
reactionGroups
reviewDecision
reviewRequests
reviews
state
statusCheckRollup
title
updatedAt
url
Command exited with code 1<|tool_calls_section_begin|> <|tool_call_begin|> bash:1 <|tool_call_argument_begin|> {"command": "gh pr view 1039 --json title,body,comments,labels,state,commits,files,changedFiles,headRefName,headRefOid,baseRefName,baseRefOid,closingIssuesReferences"} <|tool_call_end|> <|tool_calls_section_end|>
still there?
The user is asking if I'm still there. I need to fix my command and re-run it with the correct JSON fields. Let me run the command again with the correct fields.
>gh pr view 1039 --json title,body,comments,labels,state,commits,files,headRefName,headRefOid,baseRefName,closingIssuesReferences{"baseRefName":"main","body":"I recently noticed that the custom headers introduced in #500 do not play nice with the `quietStartup` setting. Specifically, the custom header remains stuck to the text input, even after exchanging multiple messages (see below)\r\n<img width=\"422\" height=\"516\" alt=\"Screenshot 2026-01-28 at 15 32 54\" src=\"https://github.com/user-attachments/assets/cb13803a-72b8-4b30-91d6-c2de447dad61\" />\r\n\r\nFixed this by wrapping this custom header in a dedicated `headerContainer` (see [pi session fixing it](https://buildwithpi.ai/session/#e54f828f680c23f9371a231e49505db7))","closingIssuesReferences":[],"comments":[],"commits":[{"authoredDate":"2026-01-28T14:10:44Z","authors":[{"email":"[REDACTED]","id":"MDQ6VXNlcjYxODAwNjkx","login":"tudoroancea","name":"Tudor Oancea"}],"committedDate":"2026-01-28T14:10:44Z","messageBody":"","messageHeadline":"create headerContainer for custom header","oid":"a030fc160ad60f2596f7d19b027151746e0da146"}],"files":[{"path":"packages/coding-agent/src/modes/interactive/interactive-mode.ts","additions":39,"deletions":24}],"headRefName":"quiet-header","headRefOid":"a030fc160ad60f2596f7d19b027151746e0da146","labels":[],"state":"OPEN","title":"create headerContainer for custom header"}Now let me get the PR diff and read the issue #500 that is referenced:
Let me check the AGENTS.md location again. I see it was in /Users/badlogic/.pi/agent/AGENTS.md per the context. Let me fetch the diff for PR 1039.
>github.com/badlogic/pi-mono/pull/1039/bin/bash: github.com/badlogic/pi-mono/pull/1039: No such file or directory
Command exited with code 127I need to find the issue referenced in the PR body (#500) and read all relevant code. Let me get the diff and read the current file.
>: (no output)I apologize for the delay. Let me analyze this PR properly.
>:gh pr diff 1039/bin/bash: :gh: command not found
Command exited with code 127Hmm, let me proceed with the proper PR review. I've already gathered the PR metadata. Let me now fetch the diff and read the relevant files.
>:{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "bash", "parameters": { "command": "gh pr diff 1039" } } }/bin/bash: :{: command not found
Command exited with code 127You are given one or more GitHub PR URLs: https://github.com/badlogic/pi-mono/pull/1039 im not convinced about moving the renderInitialMessages shit
For each PR URL, do the following in order:
packages/*/CHANGELOG.md files. Report whether an entry exists. If missing, state that a changelog entry is required before merge and that you will add it if the user decides to merge. Follow the changelog format rules in AGENTS.md. Verify:
### Breaking Changes, ### Added, ### Fixed, etc.)Fixed foo ([#123](https://github.com/badlogic/pi-mono/pull/123) by [@user](https://github.com/user))### Breaking Changes, not just ### FixedOutput format per PR: PR: Changelog:
If no issues are found, say so under Bad and Ugly.
The user wants me to review a GitHub PR. Let me start by fetching the PR details.
>gh pr view 1039 --repo badlogic/pi-mono --json title,body,comments,commits,files,author,state,labels,baseRefName,headRefName{"author":{"id":"MDQ6VXNlcjYxODAwNjkx","is_bot":false,"login":"tudoroancea","name":"Ted"},"baseRefName":"main","body":"I recently noticed that the custom headers introduced in #500 do not play nice with the `quietStartup` setting. Specifically, the custom header remains stuck to the text input, even after exchanging multiple messages (see below)\r\n<img width=\"422\" height=\"516\" alt=\"Screenshot 2026-01-28 at 15 32 54\" src=\"https://github.com/user-attachments/assets/cb13803a-72b8-4b30-91d6-c2de447dad61\" />\r\n\r\nFixed this by wrapping this custom header in a dedicated `headerContainer` (see [pi session fixing it](https://buildwithpi.ai/session/#e54f828f680c23f9371a231e49505db7))","comments":[],"commits":[{"authoredDate":"2026-01-28T14:10:44Z","authors":[{"email":"[REDACTED]","id":"MDQ6VXNlcjYxODAwNjkx","login":"tudoroancea","name":"Tudor Oancea"}],"committedDate":"2026-01-28T14:10:44Z","messageBody":"","messageHeadline":"create headerContainer for custom header","oid":"a030fc160ad60f2596f7d19b027151746e0da146"}],"files":[{"path":"packages/coding-agent/src/modes/interactive/interactive-mode.ts","additions":39,"deletions":24}],"headRefName":"quiet-header","labels":[],"state":"OPEN","title":"create headerContainer for custom header"}>gh pr diff 1039 --repo badlogic/pi-monodiff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts
index b1bd9dfa6..227c80368 100644
--- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts
+++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts
@@ -230,6 +230,9 @@ export class InteractiveMode {
// Custom footer from extension (undefined = use built-in footer)
private customFooter: (Component & { dispose?(): void }) | undefined = undefined;
+ // Header container that holds the built-in or custom header
+ private headerContainer: Container;
+
// Built-in header (logo + keybinding hints + changelog)
private builtInHeader: Component | undefined = undefined;
@@ -254,6 +257,7 @@ export class InteractiveMode {
this.session = session;
this.version = VERSION;
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
+ this.headerContainer = new Container();
this.chatContainer = new Container();
this.pendingMessagesContainer = new Container();
this.statusContainer = new Container();
@@ -379,6 +383,9 @@ export class InteractiveMode {
this.fdPath = await ensureTool("fd");
this.setupAutocomplete(this.fdPath);
+ // Add header container as first child
+ this.ui.addChild(this.headerContainer);
+
// Add header with keybindings from config (unless silenced)
if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
@@ -411,36 +418,39 @@ export class InteractiveMode {
this.builtInHeader = new Text(`${logo}\n${instructions}`, 1, 0);
// Setup UI layout
- this.ui.addChild(new Spacer(1));
- this.ui.addChild(this.builtInHeader);
- this.ui.addChild(new Spacer(1));
+ this.headerContainer.addChild(new Spacer(1));
+ this.headerContainer.addChild(this.builtInHeader);
+ this.headerContainer.addChild(new Spacer(1));
// Add changelog if provided
if (this.changelogMarkdown) {
- this.ui.addChild(new DynamicBorder());
+ this.headerContainer.addChild(new DynamicBorder());
if (this.settingsManager.getCollapseChangelog()) {
const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
const latestVersion = versionMatch ? versionMatch[1] : this.version;
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
- this.ui.addChild(new Text(condensedText, 1, 0));
+ this.headerContainer.addChild(new Text(condensedText, 1, 0));
} else {
- this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
- this.ui.addChild(new Spacer(1));
- this.ui.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0, this.getMarkdownThemeWithSettings()));
- this.ui.addChild(new Spacer(1));
+ this.headerContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
+ this.headerContainer.addChild(new Spacer(1));
+ this.headerContainer.addChild(
+ new Markdown(this.changelogMarkdown.trim(), 1, 0, this.getMarkdownThemeWithSettings()),
+ );
+ this.headerContainer.addChild(new Spacer(1));
}
- this.ui.addChild(new DynamicBorder());
+ this.headerContainer.addChild(new DynamicBorder());
}
} else {
// Minimal header when silenced
this.builtInHeader = new Text("", 0, 0);
+ this.headerContainer.addChild(this.builtInHeader);
if (this.changelogMarkdown) {
// Still show changelog notification even in silent mode
- this.ui.addChild(new Spacer(1));
+ this.headerContainer.addChild(new Spacer(1));
const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
const latestVersion = versionMatch ? versionMatch[1] : this.version;
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
- this.ui.addChild(new Text(condensedText, 1, 0));
+ this.headerContainer.addChild(new Text(condensedText, 1, 0));
}
}
@@ -457,6 +467,9 @@ export class InteractiveMode {
this.setupKeyHandlers();
this.setupEditorSubmitHandler();
+ // Render initial messages before starting the UI to avoid layout jump
+ this.renderInitialMessages();
+
// Start the UI
this.ui.start();
this.isInitialized = true;
@@ -513,8 +526,6 @@ export class InteractiveMode {
}
});
- this.renderInitialMessages();
-
// Show startup warnings
const { migratedProviders, modelFallbackMessage, initialMessage, initialImages, initialMessages } = this.options;
@@ -1296,21 +1307,25 @@ export class InteractiveMode {
this.customHeader.dispose();
}
- // Remove current header from UI
- if (this.customHeader) {
- this.ui.removeChild(this.customHeader);
- } else {
- this.ui.removeChild(this.builtInHeader);
- }
+ // Find the index of the current header in the header container
+ const currentHeader = this.customHeader || this.builtInHeader;
+ const index = this.headerContainer.children.indexOf(currentHeader);
if (factory) {
- // Create and add custom header at position 1 (after initial spacer)
+ // Create and add custom header
this.customHeader = factory(this.ui, theme);
- this.ui.children.splice(1, 0, this.customHeader);
+ if (index !== -1) {
+ this.headerContainer.children[index] = this.customHeader;
+ } else {
+ // If not found (e.g. builtInHeader was never added), add at the top
+ this.headerContainer.children.unshift(this.customHeader);
+ }
} else {
- // Restore built-in header at position 1
+ // Restore built-in header
this.customHeader = undefined;
- this.ui.children.splice(1, 0, this.builtInHeader);
+ if (index !== -1) {
+ this.headerContainer.children[index] = this.builtInHeader;
+ }
}
this.ui.requestRender();The PR references issue #500. Let me read that issue. Also, I need to read the current main branch file to compare the diff properly.
Let me read the linked issue #500 and the current source file:
>gh issue view 500 --repo badlogic/pi-mono --json title,body,comments,labels,state{"body":"Pretty self-explanatory. The example in `packages/coding-agent/examples/extensions/custom-header.ts` displays an 8-bit mascot (admittedly inspired by 3blue1brown's pi)\r\n<img width=\"798\" height=\"217\" alt=\"Screenshot 2026-01-06 at 16 35 49\" src=\"https://github.com/user-attachments/assets/ddff67f4-cea5-4e43-aa25-98092a0b6777\" />\r\n","comments":[{"id":"IC_kwDOPbFNk87ddUgs","author":{"login":"badlogic"},"authorAssociation":"OWNER","body":"We should make that the default :) Will review tonight, cheers!","createdAt":"2026-01-06T16:46:55Z","includesCreatedEdit":false,"isMinimized":false,"minimizedReason":"","reactionGroups":[{"content":"HEART","users":{"totalCount":2}}],"url":"https://github.com/badlogic/pi-mono/pull/500#issuecomment-3715450924","viewerDidAuthor":true},{"id":"IC_kwDOPbFNk87dgF44","author":{"login":"badlogic"},"authorAssociation":"OWNER","body":"Merged via rebase. Thanks @tudoroancea!","createdAt":"2026-01-06T20:20:46Z","includesCreatedEdit":false,"isMinimized":false,"minimizedReason":"","reactionGroups":[],"url":"https://github.com/badlogic/pi-mono/pull/500#issuecomment-3716177464","viewerDidAuthor":true}],"labels":[],"state":"CLOSED","title":"added custom header support and example extension"}The file is large. Let me continue reading it to see the full picture, especially the renderInitialMessages method and the setExtensionHeader method.
Let me read more of the file to understand the renderInitialMessages method:
>rg -n "renderInitialMessages" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src/modes/interactive/interactive-mode.ts516: this.renderInitialMessages();
1006: this.renderInitialMessages();
1018: this.renderInitialMessages();
1036: this.renderInitialMessages();
2446: renderInitialMessages(): void {
3296: this.renderInitialMessages();
3413: this.renderInitialMessages();
3496: this.renderInitialMessages();Let me find and read the renderInitialMessages method:
Now I have enough context. Let me check the changelog file:
Now let me check the changelog:
>head -100 /Users/badlogic/workspaces/pi-mono/packages/coding-agent/CHANGELOG.md# Changelog
## [Unreleased]
### Added
- Added shell-style keybindings: `alt+b`/`alt+f` for word navigation, `ctrl+d` for delete character forward (when editor has text) ([#1043](https://github.com/badlogic/pi-mono/issues/1043) by [@jasonish](https://github.com/jasonish))
### Fixed
- Empty array in package filter now disables all resources instead of falling back to manifest defaults ([#1044](https://github.com/badlogic/pi-mono/issues/1044))
- Auto-retry counter now resets after each successful LLM response instead of accumulating across tool-use turns ([#1019](https://github.com/badlogic/pi-mono/issues/1019))
## [0.50.1] - 2026-01-26
### Fixed
- Git extension updates now handle force-pushed remotes gracefully instead of failing ([#961](https://github.com/badlogic/pi-mono/pull/961) by [@aliou](https://github.com/aliou))
- Extension `ctx.newSession({ setup })` now properly syncs agent state and renders messages after setup callback runs ([#968](https://github.com/badlogic/pi-mono/issues/968))
- Fixed extension UI bindings not initializing when starting with no extensions, which broke UI methods after `/reload`
- Fixed `/hotkeys` output to title-case extension hotkeys ([#969](https://github.com/badlogic/pi-mono/pull/969) by [@Perlence](https://github.com/Perlence))
- Fixed model catalog generation to exclude deprecated OpenCode Zen models ([#970](https://github.com/badlogic/pi-mono/pull/970) by [@DanielTatarkin](https://github.com/DanielTatarkin))
- Fixed git extension removal to prune empty directories
## [0.50.0] - 2026-01-26
### New Features
- Pi packages for bundling and installing extensions, skills, prompts, and themes. See [docs/packages.md](docs/packages.md).
- Hot reload (`/reload`) of resources including AGENTS.md, SYSTEM.md, APPEND_SYSTEM.md, prompt templates, skills, themes, and extensions. See [README.md#commands](README.md#commands) and [README.md#context-files](README.md#context-files).
- Custom providers via `pi.registerProvider()` for proxies, custom endpoints, OAuth or SSO flows, and non-standard streaming APIs. See [docs/custom-provider.md](docs/custom-provider.md).
- Azure OpenAI Responses provider support with deployment-aware model mapping. See [docs/providers.md#azure-openai](docs/providers.md#azure-openai).
- OpenRouter routing support for custom models via `openRouterRouting`. See [docs/providers.md#api-keys](docs/providers.md#api-keys) and [docs/models.md](docs/models.md).
- Skill invocation messages are now collapsible and skills can opt out of model invocation via `disable-model-invocation`. See [docs/skills.md#frontmatter](docs/skills.md#frontmatter).
- Session selector renaming and configurable keybindings. See [README.md#commands](README.md#commands) and [docs/keybindings.md](docs/keybindings.md).
- `models.json` headers can resolve environment variables and shell commands. See [docs/models.md#value-resolution](docs/models.md#value-resolution).
- `--verbose` CLI flag to override quiet startup. See [README.md#cli-reference](README.md#cli-reference).
Read the fully revamped docs in `README.md`, or have your clanker read them for you.
### SDK Migration Guide
There are multiple SDK breaking changes since v0.49.3. For the quickest migration, point your agent at `packages/coding-agent/docs/sdk.md`, the SDK examples in `packages/coding-agent/examples/sdk`, and the SDK source in `packages/coding-agent/src/core/sdk.ts` and related modules.
### Breaking Changes
- Header values in `models.json` now resolve environment variables (if a header value matches an env var name, the env var value is used). This may change behavior if a literal header value accidentally matches an env var name. ([#909](https://github.com/badlogic/pi-mono/issues/909))
- External packages (npm/git) are now configured via `packages` array in settings.json instead of `extensions`. Existing npm:/git: entries in `extensions` are auto-migrated. ([#645](https://github.com/badlogic/pi-mono/issues/645))
- Resource loading now uses `ResourceLoader` only and settings.json uses arrays for extensions, skills, prompts, and themes ([#645](https://github.com/badlogic/pi-mono/issues/645))
- Removed `discoverAuthStorage` and `discoverModels` from the SDK. `AuthStorage` and `ModelRegistry` now default to `~/.pi/agent` paths unless you pass an `agentDir` ([#645](https://github.com/badlogic/pi-mono/issues/645))
### Added
- Session renaming in `/resume` picker via `Ctrl+R` without opening the session ([#863](https://github.com/badlogic/pi-mono/pull/863) by [@svkozak](https://github.com/svkozak))
- Session selector keybindings are now configurable ([#948](https://github.com/badlogic/pi-mono/pull/948) by [@aos](https://github.com/aos))
- `disable-model-invocation` frontmatter field for skills to prevent agentic invocation while still allowing explicit `/skill:name` commands ([#927](https://github.com/badlogic/pi-mono/issues/927))
- Exposed `copyToClipboard` utility for extensions ([#926](https://github.com/badlogic/pi-mono/issues/926) by [@mitsuhiko](https://github.com/mitsuhiko))
- Skill invocation messages are now collapsible in chat output, showing collapsed by default with skill name and expand hint ([#894](https://github.com/badlogic/pi-mono/issues/894))
- Header values in `models.json` now support environment variables and shell commands, matching `apiKey` resolution ([#909](https://github.com/badlogic/pi-mono/issues/909))
- Added HTTP proxy environment variable support for API requests ([#942](https://github.com/badlogic/pi-mono/pull/942) by [@haoqixu](https://github.com/haoqixu))
- Added OpenRouter provider routing support for custom models via `openRouterRouting` compat field ([#859](https://github.com/badlogic/pi-mono/pull/859) by [@v01dpr1mr0s3](https://github.com/v01dpr1mr0s3))
- Added `azure-openai-responses` provider support for Azure OpenAI Responses API. ([#890](https://github.com/badlogic/pi-mono/pull/890) by [@markusylisiurunen](https://github.com/markusylisiurunen))
- Added changelog link to update notifications ([#925](https://github.com/badlogic/pi-mono/pull/925) by [@dannote](https://github.com/dannote))
- Added `--verbose` CLI flag to override quietStartup setting ([#906](https://github.com/badlogic/pi-mono/pull/906) by [@Perlence](https://github.com/Perlence))
- `markdown.codeBlockIndent` setting to customize code block indentation in rendered output
- Extension package management with `pi install`, `pi remove`, `pi update`, and `pi list` commands ([#645](https://github.com/badlogic/pi-mono/issues/645))
- Package filtering: selectively load resources from packages using object form in `packages` array ([#645](https://github.com/badlogic/pi-mono/issues/645))
- Glob pattern support with minimatch in package filters, top-level settings arrays, and pi manifest (e.g., `"!funky.json"`, `"*.ts"`) ([#645](https://github.com/badlogic/pi-mono/issues/645))
- `/reload` command to reload extensions, skills, prompts, and themes ([#645](https://github.com/badlogic/pi-mono/issues/645))
- `pi config` command with TUI to enable/disable package and top-level resources via patterns ([#938](https://github.com/badlogic/pi-mono/issues/938))
- CLI flags for `--skill`, `--prompt-template`, `--theme`, `--no-prompt-templates`, and `--no-themes` ([#645](https://github.com/badlogic/pi-mono/issues/645))
- Package deduplication: if same package appears in global and project settings, project wins ([#645](https://github.com/badlogic/pi-mono/issues/645))
- Unified collision reporting with `ResourceDiagnostic` type for all resource types ([#645](https://github.com/badlogic/pi-mono/issues/645))
- Show provider alongside the model in the footer if multiple providers are available
- Custom provider support via `pi.registerProvider()` with `streamSimple` for custom API implementations
- Added `custom-provider.ts` example extension demonstrating custom Anthropic provider with OAuth
### Changed
- `/resume` picker sort toggle moved to `Ctrl+S` to free `Ctrl+R` for rename ([#863](https://github.com/badlogic/pi-mono/pull/863) by [@svkozak](https://github.com/svkozak))
- HTML export: clicking a sidebar message now navigates to its newest leaf and scrolls to it, instead of truncating the branch ([#853](https://github.com/badlogic/pi-mono/pull/853) by [@mitsuhiko](https://github.com/mitsuhiko))
- HTML export: active path is now visually highlighted with dimmed off-path nodes ([#929](https://github.com/badlogic/pi-mono/pull/929) by [@hewliyang](https://github.com/hewliyang))
- Azure OpenAI Responses provider now uses base URL configuration with deployment-aware model mapping and no longer includes service tier handling
- `/reload` now re-renders the entire scrollback so updated extension components are visible immediately ([#928](https://github.com/badlogic/pi-mono/pull/928) by [@ferologics](https://github.com/ferologics))
- Skill, prompt template, and theme discovery now use settings and CLI path arrays instead of legacy filters ([#645](https://github.com/badlogic/pi-mono/issues/645))
### Fixed
- Extension `setWorkingMessage()` calls in `agent_start` handlers now work correctly; previously the message was silently ignored because the loading animation didn't exist yet ([#935](https://github.com/badlogic/pi-mono/issues/935))
- Fixed package auto-discovery to respect loader rules, config overrides, and force-exclude patterns
- Fixed /reload restoring the correct editor after reload ([#949](https://github.com/badlogic/pi-mono/pull/949) by [@Perlence](https://github.com/Perlence))
- Fixed distributed themes breaking `/export` ([#946](https://github.com/badlogic/pi-mono/pull/946) by [@mitsuhiko](https://github.com/mitsuhiko))
- Fixed startup hints to clarify thinking level selection and expanded thinking guidance
- Fixed SDK initial model resolution to use `findInitialModel` and default to Claude Opus 4.5 for Anthropic models
- Fixed no-models warning to include the `/model` instruction
- Fixed authentication error messages to point to the authentication documentation
- Fixed bash output hint lines to truncate to terminal width
- Fixed custom editors to honor the `paddingX` setting ([#936](https://github.com/badlogic/pi-mono/pull/936) by [@Perlence](https://github.com/Perlence))
- Fixed system prompt tool list to show only built-in tools
- Fixed package manager to check npm package versions before using cached copiesThe PR does not have a changelog entry in the [Unreleased] section. This is a bug fix for issue #500 (custom headers not playing nice with quietStartup setting).
Now let me analyze the PR diff carefully:
What the PR does:
headerContainer: Container field to hold header componentsheaderContainer instead of directly to this.uiheaderContainer as first child of uirenderInitialMessages() call from run() to init() (before ui.start())setExtensionHeader to work with headerContainer instead of direct ui.children manipulationThe problem being fixed:
According to the PR description, the custom header introduced in #500 doesn't work well with quietStartup - the custom header remains stuck to the text input even after multiple messages.
What I need to verify:
renderInitialMessages() from after this.subscribeToAgent() in run() to before this.ui.start() in init(). The comment says "Render initial messages before starting the UI to avoid layout jump"setExtensionHeader now works with the headerContainer rather than splicing into ui.children directlyLet me check the current setExtensionHeader implementation to understand the change better.
Let me check the current setExtensionHeader implementation in the main branch:
Now I understand the issue better. The current code does:
ui.childrenThe problem is that when quietStartup is enabled:
The PR solution:
headerContainer that holds all header-related componentsLet me also check if there are any documentation changes needed.
Let me check the docs to see if there's anything related to custom headers:
>rg -l "setHeader\|customHeader\|custom header" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/docs/ /Users/badlogic/workspaces/pi-mono/packages/coding-agent/README.md 2>/dev/null(no output)
Command exited with code 1>rg -l "setHeader\|customHeader\|custom header" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/examples/ 2>/dev/null(no output)
Command exited with code 1No docs mention custom headers. Let me check the example file mentioned in PR #500.
>find /Users/badlogic/workspaces/pi-mono/packages/coding-agent/examples -name "*.ts" -o -name "*.md" | xargs rg -l "header" 2>/dev/null | head -5/Users/badlogic/workspaces/pi-mono/packages/coding-agent/examples/extensions/antigravity-image-gen.ts
/Users/badlogic/workspaces/pi-mono/packages/coding-agent/examples/extensions/plan-mode/index.ts
/Users/badlogic/workspaces/pi-mono/packages/coding-agent/examples/extensions/plan-mode/utils.ts
/Users/badlogic/workspaces/pi-mono/packages/coding-agent/examples/extensions/custom-provider-anthropic/node_modules/@anthropic-ai/sdk/core/api-promise.d.ts
/Users/badlogic/workspaces/pi-mono/packages/coding-agent/examples/extensions/todo.tsLet me check the docs/extensions.md:
>rg -n "setHeader" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/docs/extensions.md1701:| `custom-header.ts` | Replace startup header | `on("session_start")`, `setHeader` |So the custom-header example does exist. Let me check if there's a custom-header.ts example and verify the context better.
>ls -la /Users/badlogic/workspaces/pi-mono/packages/coding-agent/examples/extensions/ | grep -i header-rw-r--r--@ 1 badlogic staff 2395 Jan 17 11:39 custom-header.tsNow I understand the context. Let me look at the custom-header.ts example:
Now I have a full picture of the changes. Let me analyze:
Summary:
The PR fixes an issue where custom headers (set via ctx.ui.setHeader() in extensions) don't play well with quietStartup mode. The bug manifests as the custom header remaining "stuck" to the text input even after exchanging multiple messages.
The Solution:
headerContainer field that holds all header-related componentsui to being children of headerContainerheaderContainer is added as the first child of uisetExtensionHeader now manipulates the container's children instead of splice-indexing into ui.childrenAdditional Change:
The PR also moves renderInitialMessages() from run() (after this.subscribeToAgent()) to init() (before this.ui.start()). The comment says "Render initial messages before starting the UI to avoid layout jump."
This is the part the user is concerned about ("im not convinced about moving the renderInitialMessages shit").
Let me analyze whether moving renderInitialMessages() is necessary for the fix:
Current flow (main branch):
init() is called, which:
ui.start()run() is called, which:
renderInitialMessages()PR flow:
init() is called, which:
headerContainer)renderInitialMessages() BEFORE ui.start()ui.start()run() is called, which:
So the PR removes the renderInitialMessages() call from run() entirely and moves it earlier in the initialization sequence. This timing change could affect how the UI renders when messages are present at startup, but I need to verify if this is actually required for fixing the header issue or if it's a separate optimization. Looking at the diff, renderInitialMessages() is being removed from run() after the subscribeToAgent call and moved earlier in init() before ui.start(). This isn't a duplication—it's a relocation of when initial messages render in the lifecycle.
The timing matters here: by moving it into init(), the messages render before extensions are initialized and before the session_start event fires, which is when custom headers typically get set. This ensures the header container is properly positioned before any initial output appears. the quiet startup case doesn't add builtInHeader to the UI at all, so when setExtensionHeader later tries to remove it, the operation fails. The PR solves this by introducing a headerContainer that always holds builtInHeader, regardless of startup mode, and then setExtensionHeader manipulates the container's children by index instead of trying to remove a header that may not exist in the UI.
As for moving renderInitialMessages(), the commit doesn't explain the rationale. It appears to be a separate optimization—rendering messages before ui.start() is called prevents a layout shift since all content loads together rather than the header appearing first. However, this change isn't strictly required for the header container fix to work, suggesting it was likely an ancillary improvement found during the debugging process. positions might behave differently. The PR author likely encountered a visual glitch during testing and added this change to address it, though the reasoning isn't well documented.
The headerContainer fix itself is sound and resolves the core issue. The renderInitialMessages() relocation is less clear—it needs better explanation or should be reconsidered if unnecessary. I also notice the diff includes adding builtInHeader to headerContainer in quiet startup mode, which is the actual fix since previously the header was created but never attached to the UI, preventing setExtensionHeader from working.
Now I have enough context to provide a complete review.
Changelog:
### Fixed in packages/coding-agent/CHANGELOG.md before merge, e.g.:
- Fixed custom header not displaying correctly with `quietStartup` enabled ([#1039](https://github.com/badlogic/pi-mono/pull/1039) by [@tudoroancea](https://github.com/tudoroancea))Good:
quietStartup enabled, builtInHeader was created but never added to the UI tree, so setExtensionHeader() couldn't find/replace it via ui.removeChild()headerContainer approach is clean and isolates header management from the overall UI child orderingheaderContainer.children[index] replacement is more robust than ui.children.splice(1, 0, ...) which depended on magic indicesthis.headerContainer.addChild(this.builtInHeader); ensuring the header is always in the containerBad:
pkg:coding-agent label on the PRrenderInitialMessages() relocation from run() to init() (before ui.start()) is bundled in without clear justification. The comment says "to avoid layout jump" but this is unrelated to the header container fixUgly:
renderInitialMessages() move could have subtle side effects:
run() also calls renderInitialMessages() in multiple other places (extension commands like /fork, /tree, /new etc.)subscribeToAgent() or initExtensions() that expect messages not to be rendered yet, this could breaksetHeader during session_start may now see different state depending on whether messages were already renderedQuestions or Assumptions:
renderInitialMessages() move actually necessary for the header fix, or was it discovered during debugging? The PR description doesn't mention it.-m flag?session_start?Change summary:
headerContainer: Container field to hold all header-related componentsheaderContainer as first child of ui in init()headerContainer instead of ui directlybuiltInHeader to headerContainer (the actual bug fix)setExtensionHeader to manipulate headerContainer.children by index instead of splicing into ui.childrenrenderInitialMessages() from run() to init() (before ui.start())Tests:
quietStartup: true + custom header extensionRecommendation: The headerContainer fix is correct. The renderInitialMessages() move should be either:
If you want to merge only the header fix without the renderInitialMessages change, the relevant lines to revert are the addition at lines 469-470 and the removal at lines 526-527 in the diff.
merge via gh cli, pull from origin, revert just the renderInitialMessages() shit
well, fuck it, just merge should be ok
The user wants me to merge the PR via gh cli. Let me do that.
>gh pr merge 1039 --repo badlogic/pi-mono --squash --delete-branch(no output)Merged. Now let me pull and add the changelog entry.
>cd /Users/badlogic/workspaces/pi-mono && git pull --rebaseerror: cannot pull with rebase: You have unstaged changes.
error: Please commit or stash them.
Command exited with code 128There are unstaged changes. Let me check what they are - but I shouldn't stash them since that could affect other agents' work. Let me see the status.
>cd /Users/badlogic/workspaces/pi-mono && git statusOn branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: packages/coding-agent/docs/models.md
no changes added to commit (use "git add" and/or "git commit -a")There's an unstaged change in packages/coding-agent/docs/models.md - this might be from another agent working in parallel. I shouldn't touch it. Let me try a regular pull (without rebase) or just fetch and reset to origin/main carefully... Actually, let me just fetch first and see what's happening.
>cd /Users/badlogic/workspaces/pi-mono && git fetch origin && git statusFrom https://github.com/badlogic/pi-mono
c808de60..d8808733 main -> origin/main
On branch main
Your branch is behind 'origin/main' by 4 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: packages/coding-agent/docs/models.md
no changes added to commit (use "git add" and/or "git commit -a")There's an unstaged change in models.md - this could be from another agent. I'll use git stash temporarily to pull, then pop it back. But wait, the AGENTS.md says I shouldn't use git stash because it stashes ALL changes including other agents' work.
Let me check if this file was modified by the incoming commits. If the merge would conflict with the local change, I need to handle it carefully.
Actually, let me just do a regular git pull (no rebase) and see what happens. If there's a conflict, we'll deal with it.
>cd /Users/badlogic/workspaces/pi-mono && git pullerror: cannot pull with rebase: You have unstaged changes.
error: Please commit or stash them.
Command exited with code 128It seems there's a git config that forces rebase on pull. Let me try with --no-rebase explicitly.
>cd /Users/badlogic/workspaces/pi-mono && git pull --no-rebaseUpdating c808de60..d8808733
Fast-forward
packages/coding-agent/CHANGELOG.md | 4 +
packages/coding-agent/src/core/settings-manager.ts | 72 ++++++-
.../src/modes/interactive/interactive-mode.ts | 63 ++++---
.../coding-agent/test/settings-manager-bug.test.ts | 99 ++++++++++
packages/tui/src/autocomplete.ts | 6 +-
packages/tui/src/components/editor.ts | 66 +++----
packages/tui/src/components/input.ts | 17 --
packages/tui/test/editor.test.ts | 208 +++++++++++++++++----
packages/tui/test/input.test.ts | 15 +-
9 files changed, 422 insertions(+), 128 deletions(-)
create mode 100644 packages/coding-agent/test/settings-manager-bug.test.tsGood, the pull succeeded. The PR was merged. Now I need to add the changelog entry. Let me check if it was already added (looking at the pull output, there was a change to CHANGELOG.md).
>head -30 /Users/badlogic/workspaces/pi-mono/packages/coding-agent/CHANGELOG.md# Changelog
## [Unreleased]
### Fixed
- External edits to `settings.json` are now preserved when pi reloads or saves unrelated settings. Previously, editing settings.json directly (e.g., removing a package from `packages` array) would be silently reverted on next pi startup when automatic setters like `setLastChangelogVersion()` triggered a save.
### Added
- Added shell-style keybindings: `alt+b`/`alt+f` for word navigation, `ctrl+d` for delete character forward (when editor has text) ([#1043](https://github.com/badlogic/pi-mono/issues/1043) by [@jasonish](https://github.com/jasonish))
### Fixed
- Empty array in package filter now disables all resources instead of falling back to manifest defaults ([#1044](https://github.com/badlogic/pi-mono/issues/1044))
- Auto-retry counter now resets after each successful LLM response instead of accumulating across tool-use turns ([#1019](https://github.com/badlogic/pi-mono/issues/1019))
## [0.50.1] - 2026-01-26
### Fixed
- Git extension updates now handle force-pushed remotes gracefully instead of failing ([#961](https://github.com/badlogic/pi-mono/pull/961) by [@aliou](https://github.com/aliou))
- Extension `ctx.newSession({ setup })` now properly syncs agent state and renders messages after setup callback runs ([#968](https://github.com/badlogic/pi-mono/issues/968))
- Fixed extension UI bindings not initializing when starting with no extensions, which broke UI methods after `/reload`
- Fixed `/hotkeys` output to title-case extension hotkeys ([#969](https://github.com/badlogic/pi-mono/pull/969) by [@Perlence](https://github.com/Perlence))
- Fixed model catalog generation to exclude deprecated OpenCode Zen models ([#970](https://github.com/badlogic/pi-mono/pull/970) by [@DanielTatarkin](https://github.com/DanielTatarkin))
- Fixed git extension removal to prune empty directories
## [0.50.0] - 2026-01-26The changelog doesn't have an entry for the PR we just merged (#1039). I need to add one. Looking at the structure, there are two ### Fixed sections right now (a formatting issue from another commit), and I should add my entry to the first one under ## [Unreleased].
Let me add the changelog entry for the custom header fix.
The PR is merged but no changelog entry was added. Let me add one:
Now let me also leave a comment on the PR thanking the contributor.
>cd /Users/badlogic/workspaces/pi-mono && git add packages/coding-agent/CHANGELOG.md && git commit -m "docs(coding-agent): add changelog entry for #1039"Running formatting, linting, and type checking...
> pi-monorepo@0.0.3 check
> biome check --write . && tsgo --noEmit && cd packages/web-ui && npm run check
Checked 445 files in 281ms. No fixes applied.
> @mariozechner/pi-web-ui@0.50.1 check
> biome check --write . && tsc --noEmit && cd example && biome check --write . && tsc --noEmit
Checked 73 files in 37ms. No fixes applied.
Checked 3 files in 8ms. No fixes applied.
✅ All pre-commit checks passed!
[main 06a7fedd] docs(coding-agent): add changelog entry for #1039
1 file changed, 1 insertion(+)Now push and leave a comment on the PR.
>cd /Users/badlogic/workspaces/pi-mono && git pushTo https://github.com/badlogic/pi-mono.git
d8808733..06a7fedd main -> main>gh pr comment 1039 --repo badlogic/pi-mono --body "Thanks @tudoroancea!"https://github.com/badlogic/pi-mono/pull/1039#issuecomment-3814904409The PR was merged, I added a changelog entry, pushed, and left a thank-you comment. Done!
Done. Merged PR #1039 via squash, added changelog entry, pushed, and left a comment thanking the contributor.
- Fixed custom header not displaying correctly with `quietStartup` enabled ([#1039](https://github.com/badlogic/pi-mono/pull/1039) by [@tudoroancea](https://github.com/tudoroancea))