PiG Documentation

Guides and references for configuring, using, and extending PiG.

Bug fix ledger

This page lists every bug fixed since PiG 0.4.1: what users saw, why it happened, what it cost them, where it happened, which commits fixed it, and which tests keep it fixed. make bug-ledger regenerates it and make bug-ledger-check fails when it is out of date.

How to read it

  • Source. Each entry has one source class. Own defect: PiG differed from Pi, or failed where Pi does not. Fixed ahead of Pi: Pi has the same defect, PiG fixed it first, and the entry names its divergence number (listed by pig docs show divergences), the Pi issue, and the condition that removes the difference when Pi fixes it. Pi fix ported: Pi shipped the fix and PiG ported it.
  • What counts as a bug. An entry exists for each item under Fixed in the release notes, for each fixed-ahead divergence added since PiG 0.4.1, and for each commit titled as a fix that changes shipped code. Commits titled as fixes that change only tests, tooling, generated files or the repair of a merge are not bugs in a release; they are counted at the end of this page.
  • Root cause. The text comes from the fix commit's message or the release note, in that order. Not recorded means neither states a cause.
  • User impact. A class chosen from the wording of the release note: credential exposure, crash or hang, lost data, a failed operation, wrong display, wrong output, or a difference from Pi. It describes the report, not a measured severity.
  • Fix commits. Short hashes from the project's development history, with the pull request number when the commit has one. Not yet committed marks a fix whose release note is written but whose commit is not in history.
  • Tests. Tests the fix commit adds or names, each checked to exist in the tree. None recorded marks a fix whose commit neither adds nor names one.

Summary

Entries are listed by ID. The page carries no totals, so a pull request that adds an entry changes only that entry's lines; go run ./automation/ci/bugledger -summary prints the counts by source and the number of entries with no recorded test.

ID
Bug
Source
Platform
Fixed in
BF-003f6f0Ctrl+U, Ctrl+K and the word-delete keys leave an open autocomplete list open, as Pi's editor doesPiG's own defectAll platforms0.5.0
BF-007c91dGo, Python and Rust extensions: in a before_agent_start handler, the event's systemPrompt and the…PiG's own defectAll platforms0.5.0
BF-02114fffix(tui): filterable list drops the label when no cell remains (Pi select-list)PiG's own defectAll platformsUnreleased
BF-0225a43A device-attributes reply delivered in slow reads no longer reaches the editor as input: an ESC[? prefix…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-0225bbfPressing Escape twice with only whitespace in the editor now opens the tree or fork selector, as in Pi, which…PiG's own defectAll platforms0.5.0
BF-022d99ffix(tui): the login dialog's input renders at every prompt positionPiG's own defectAll platformsUnreleased
BF-037b2e3A session message whose members are in another order than Pi writes them, or that holds members PiG does not…PiG's own defectAll platforms0.5.0
BF-03ad544fix(tui): card of an unregistered tool is Pi's formatToolExecutionPiG's own defectAll platformsUnreleased
BF-040088eai.CloudflareStreams drops the deferred fetch and cancel operations of the API it wraps, as Pi's…PiG's own defectAll platforms0.5.0
BF-04ef0acClosing a Chord host no longer hangs when a hold arrives over a link that closes at the same timePiG's own defectWindowsUnreleased
BF-051a79dA Provider object registered by an extension (pi.registerProvider(provider)) now receives the session's…PiG's own defectAll platforms0.5.0
BF-051a79d-2A Provider object's stream options no longer carry a stray hasOnPayload valuePiG's own defectAll platforms0.5.0
BF-053d96dRPC mode: when an extension command calls pi.sendUserMessage() or `pi.sendMessage(..., { triggerTurn: true…PiG's own defectAll platforms0.5.0
BF-056bbfbKey matching follows Pi for three terminal inputsPiG's own defectAll platforms0.5.0
BF-058e7a0The Go, Rust and Python SDKs export Pi's compaction helpers calculateContextTokens, shouldCompact…PiG's own defectAll platforms0.5.0
BF-0630078MCP tool calls no longer fail with "Invalid MCP tools/call structured content" when the server returns…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-0631c73Go, Python and Rust extensions now draw an overlay component (ctx.custom with overlay: true) at the…PiG's own defectAll platforms0.5.0
BF-064181ffix(extension): hold the theme colors map the wire palette memo is keyed onPiG's own defectAll platformsUnreleased
BF-07151d0In print and JSON mode, Node extensions in separate processes now keep Pi's order when a later extension's…PiG's own defectAll platforms0.5.0
BF-0790d5eString.prototype.trim vs strings.TrimSpace: four Pi .trim() sites used Go's strings.TrimSpace, which…PiG's own defectAll platforms0.5.0
BF-081e8c3Bug report files against live Pi: new TestBugReportFilesMatchPi…PiG's own defectAll platforms0.5.0
BF-082355fThe session picker checks its keys in Pi's order when bindings overlap (delete before rename, list actions…PiG's own defectAll platforms0.5.0
BF-082f350markdown.codeBlockIndent: Pi's getter is markdown?.codeBlockIndent ?? " ", so an authored "" means no…PiG's own defectAll platforms0.5.0
BF-086395acoding.DefaultAgentDir() (Pi's getAgentDir) now returns the directory named by PIG_CODING_AGENT_DIR…PiG's own defectAll platforms0.5.0
BF-08bf076An extension login callback (onPrompt, onSelect, onManualCodeInput) whose prompt fails for a reason…PiG's own defectAll platforms0.5.0
BF-093cf5cProvider error text is written the way Pi writes itPiG's own defectAll platforms0.5.0
BF-098e33dExtension ctx.ui.theme.fg, bg and getBgAnsi throw Unknown theme color: <token> in every SDK for a…PiG's own defectAll platforms0.5.0
BF-0a3e8ceIn print and JSON mode, SIGTERM or SIGHUP no longer hangs when a Go, Python or Rust extension's…PiG's own defectAll platforms0.5.0
BF-0aa31c4Extensions that import @earendil-works/pi-ai/models (for example for clampThinkingLevel) load in the Node…A fix Pi shipped that PiG portedAll platforms0.5.2
BF-0aba908Extensions' ctx.ui.setHiddenThinkingLabel() now changes the collapsed thinking label of every assistant…PiG's own defectAll platforms0.5.0
BF-0adc7a6A provider that an extension registers while it loads no longer fails the load when the model registry…PiG's own defectAll platformsUnreleased
BF-0ae3fcfThe login provider list selects with Enter when tui.select.confirm is rebound, as Pi's search input submits…PiG's own defectAll platforms0.5.0
BF-0ae4683PiG, the extension SDKs and the sprite selection use one configuration root: PIG_HOME, else…PiG's own defectAll platforms0.5.0
BF-0b13d8bpig auth --help names pig auth ... instead of pi auth ...PiG's own defectAll platforms0.5.0
BF-0b13d8b-2pig extension --help lists pig extension init --loginPiG's own defectAll platforms0.5.0
BF-0b13d8b-3pig --help lists pig login, pig logout, pig package, pig extension upgrade, pig reload, and `pig…PiG's own defectAll platforms0.5.0
BF-0b4e1e3The OpenAI Codex base URL keeps the whitespace around it, as Pi does (only an all-blank value falls back to…PiG's own defectAll platforms0.5.0
BF-0b674b3Gemini model-id checks follow Pi's lowercasing: an id spelled with a dotted capital İ or a long ſ is no…PiG's own defectAll platforms0.5.0
BF-0b85f55The Rust SDK keeps every session entry when one entry of the session file holds an unpaired surrogate (it…PiG's own defectAll platforms0.5.0
BF-0bbe365fix(codingagent): footer context usage refreshes on a context_edit entry (Pi getContextUsage)PiG's own defectAll platformsUnreleased
BF-0bc05bfFixed ! and RPC bash output keeping fragments of color codes, such as a stray m, when a code was split…PiG's own defectAll platforms0.5.0
BF-0ce459e/login offers a provider object's own login methods (Pi's registerNativeProvider, the…PiG's own defectAll platforms0.5.0
BF-0d0a3efA turn through OpenRouter (Chat Completions or Anthropic Messages) now reports the cost OpenRouter billed…PiG's own defectAll platforms0.5.0
BF-0d13b01SettingsManager.getTransport and fifteen sibling getters (settings-manager.ts: getSteeringMode…PiG's own defectAll platforms0.5.0
BF-0d1c973tui.Box no longer invalidates its children when a child is added or removed or the box is clearedPiG's own defectAll platforms0.5.0
BF-0d55b30A panicking notify while MCP reports startup problems is shown as "MCP failed to load" instead of ending the…PiG's own defectAll platforms0.5.0
BF-0d55b30-2The Python SDK reports a component whose set_invalidate(None) raises during cleanup in the extension log…PiG's own defectAll platforms0.5.0
BF-0dff068An OpenAI Responses function call streamed with an empty call_id no longer makes every later request fail…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-0e05901A provider an extension registers with refreshModels and no streamSimple streams through its API, as in PiPiG's own defectAll platforms0.5.0
BF-0e225b4fix(runtimecell): keep the Python runner's _run callable without the built eventPiG's own defectAll platformsUnreleased
BF-0e3411cFixed CI on main after the 0.5.1 ledger trim: the reviewed-pin ledgers (upstream-v0.84.0.json…PiG's own defectAll platforms0.5.2
BF-0e360a0The tui package's default keybindings no longer depend on the platform: undo is ctrl+- and the alt-screen…PiG's own defectWindows, WSL0.5.0
BF-0e8761bA system message with an empty sections object is stored as "sections": {}, as Pi stores it after a resetA fix Pi shipped that PiG portedAll platforms0.5.0
BF-0ffb06afix(tui): diff rows with a CR are wrapped like Pi's TextPiG's own defectAll platformsUnreleased
BF-116b36cfix(extensions): answer the start-window report of a tool call that holds no window, where the report is readPiG's own defectAll platformsUnreleased
BF-11a6fcbfix(extension): a restart adopts the new renderers; the late-registration row runs what it registeredPiG's own defectAll platformsUnreleased
BF-11b199dSession lines (and every other JSON.stringify-mode encode) now write ill-formed UTF-8 the way Pi does: one…PiG's own defectAll platforms0.5.0
BF-1268115fix(model runtime): a cancelled request to a caller-owned provider the runtime does not know is aborted, not…PiG's own defectAll platformsUnreleased
BF-1356aaefix(train): load pig_sdk.tui with the package, run the TS queue check with no runtime, pin three fixture…PiG's own defectAll platformsUnreleased
BF-1364791Stopping a codemode script or reaching its deadline no longer waits up to half a second for a built-in that…PiG's own defectAll platforms0.5.0
BF-1390c9dfix(ai): the Anthropic event reader matches the type member exactlyPiG's own defectAll platformsUnreleased
BF-155d0f3In RPC mode, SIGTERM or SIGHUP during a tool call likewise stops the run while the session_shutdown…PiG's own defectAll platforms0.5.0
BF-15a5c0bThe Kimi Code, Meta and xAI device logins judge the verification URI the way Pi does, and Meta and xAI open…PiG's own defectAll platforms0.5.0
BF-160d95aIn RPC mode, a run that an extension command starts with sendMessage or sendUserMessage now emits…PiG's own defectAll platforms0.5.0
BF-160d95a-2In RPC mode, the Go, Python and Rust SDKs no longer let the host move on while a notify, setStatus…PiG's own defectAll platforms0.5.0
BF-1631753Queueing a steering or follow-up message while the agent streams prints no status line, as Pi doesPiG's own defectAll platforms0.5.0
BF-163f7b7Empty quoted prompt-template arguments ("" or '') are kept as empty arguments, so later positional…PiG's own defectAll platforms0.5.0
BF-165cf58pig package publish --to npm and pig piglet publish --to npm no longer run an npm that the published…PiG's own defectWindows0.5.0
BF-165cf58-2On Windows, publishing to npm now works when npm is an npm.cmd at a path with spaces, such as `C:\Program…PiG's own defectWindows0.5.0
BF-167dae3fix(codingagent): the idle collection runs two GC cycles again so pool victims are freed (D172)PiG's own defectAll platformsUnreleased
BF-16f8d39fix(codingagent): keep the clock allowance on the marker header's time.Now linePiG's own defectAll platformsUnreleased
BF-1827831The MCP extension's tool_call handler passes a tool's reachesTools the call's input in the member order…PiG's own defectAll platforms0.5.0
BF-1848633Extension widgets share one key namespace across extensions, as in Pi: an extension that sets a widget key…PiG's own defectAll platforms0.5.0
BF-184ed40fix(hljs): a language's reach does not depend on the first text it highlights; red cases for resumeLimitPiG's own defectAll platformsUnreleased
BF-1896331Changing the thinking level in interactive mode (cycle key, /thinking, the settings selector) goes through…PiG's own defectAll platforms0.5.0
BF-18daf7fThe Go SDK's typed branch entry keeps the text, thinking and tool calls of an assistant message whose tool…PiG's own defectAll platforms0.5.0
BF-18f7d39Codemode @options errors report V8's JSON error text, tool and schema declarations keep unpaired…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-1971aaeA bash shell that starts at the moment pig handles an emergency exit, a crash or a signal is now killed…PiG's own defectAll platforms0.5.0
BF-19a07f0fix(sdk-rs): give the request-start wire test's RequestMsg the batch, child_session, tool_call_index and…PiG's own defectAll platformsUnreleased
BF-1a855cdThe /session block now follows a later theme change and shows the cache-warming countdown as of each…PiG's own defectAll platforms0.5.0
BF-1b5540aclient.Client.Disconnect takes the reason string as Pi's Client.disconnect(reason) does (it was an…PiG's own defectAll platforms0.5.0
BF-1c6b864A Session that an extension creates with createAgentSession, or that coding.CreateAgentSession returns…PiG's own defectAll platformsUnreleased
BF-1ca0e2ffix(codingagent): the terminal reader yields to the main loop it readied before it polls againPiG's own defectAll platformsUnreleased
BF-1cc7519fix(sdk): a command, shortcut, flag or renderer registered after load reaches the host in every SDKPiG's own defectAll platformsUnreleased
BF-1ce00b0Fix in shipped codePiG's own defectAll platformsUnreleased
BF-1d02c73Filters that Pi writes with toLowerCase() (select lists, the config selector, file autocomplete, /model…PiG's own defectAll platforms0.5.0
BF-1d1c8aaRPC mode now ends by the signal when a termination signal arrives while a quit session_shutdown handler is…PiG's own defectAll platforms0.5.0
BF-1d5b272A text file passed as an @file argument that holds invalid UTF-8 now reaches the prompt as Pi sends it…PiG's own defectAll platforms0.5.0
BF-1dcaef7Durable: a JSONL line that starts with a byte order mark now opens, as in Pi, instead of failing with…PiG's own defectAll platforms0.5.0
BF-1ed43b3fix(train): load pig_sdk.tui with the package, run the TS queue check with no runtime, pin three fixture…PiG's own defectAll platformsUnreleased
BF-1ee7deaThe Mistral request URL for a base URL with a non-special scheme or a host or port the WHATWG parser rejects…PiG's own defectAll platforms0.5.0
BF-1f7fa2fIn print and JSON mode, an extension in its own Node process no longer runs its callbacks with a stale ctx…PiG's own defectAll platforms0.5.0
BF-1fbdabbA CRLF that a proxy splits across two network chunks no longer aborts an Anthropic turn with `Could not parse…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-1fcf017toUpperCase ports use a shared JavaScript jsstring.ToUpper (matches Node over every code point).…PiG's own defectAll platforms0.5.0
BF-202b8f1On arm64 and other CPUs with a fused multiply-add, the OKHSL saturation of a color (used when generating the…PiG's own defectAll platforms0.5.0
BF-205933efix(ai): a Gemini model outside the catalog carries the provider's API, so replay's isSameModel matches Pi…PiG's own defectAll platformsUnreleased
BF-20718f7fix(coding-agent): resolve model references through one provider selection policy (Pi)PiG's own defectAll platformsUnreleased
BF-20aee29The crash notice prints the year of a crash time the way Node's toLocaleString does: unpadded, and counted…PiG's own defectAll platforms0.5.0
BF-211af49/hotkeys lists extension shortcuts, and the extension shortcut listener tries them, in the order of Pi's…PiG's own defectAll platforms0.5.0
BF-2165463On Windows, saving credentials to auth.json, models.json or the MCP OAuth store no longer fails at once…PiG's own defectWindows0.5.0
BF-224dbd9On Linux and macOS, the bash tool, a ! command and an extension's exec no longer report no output for a…PiG's own defectmacOS, LinuxUnreleased
BF-2280bf8The per-API stream entry points (ai.AnthropicMessagesAPI() through ai.PiMessagesAPI() and the deprecated…PiG's own defectAll platforms0.5.0
BF-2309604Tool schemas in OpenAI Completions, OpenAI Responses, Codex and Mistral request bodies keep the members in…PiG's own defectAll platforms0.5.0
BF-2316fe3The Piglet JSON Schema that pig piglet schema prints rejected powershell in tools, which `pig piglet…PiG's own defectWindows0.5.3
BF-234316dOn Windows, a Go extension's source hash no longer fails, or makes a build beside it fail, with `The process…PiG's own defectWindowsUnreleased
BF-24872abExtensions receive each model (ctx.model, model_select, ctx.modelRegistry reads, scoped models…PiG's own defectAll platforms0.5.0
BF-24a821dThe openai stream decoder follows openai 7.19.0, the version packages/ai declares: the stream ends at [DONE]…PiG's own defectAll platforms0.5.0
BF-24a9b69fix(hljs): a resumed run counts the scan iterations before its checkpointPiG's own defectAll platformsUnreleased
BF-25aa7fdA subprocess extension's markdown transform that was busy past the five-second renderer inactivity boundary…PiG's own defectAll platforms0.5.0
BF-25c854dSession directories (the agent directory, sessions and each project's directory) are created owner-only…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-2699291The serializeConversation parity row records that Rust session entries still read an unpaired surrogate as…PiG's own defectAll platforms0.5.0
BF-26b6705The RPC test harness no longer races on its recorded output error (found by a go test -race ./coding/...…PiG's own defectAll platforms0.5.0
BF-26d302aA login flow's select prompt opens a selector in the login dialog, as Pi's does, instead of ending the…PiG's own defectAll platforms0.5.0
BF-26d302a-2A provider object's own auth.apiKey.login runs in the login dialog, as Pi's showApiKeyLoginDialog runs…PiG's own defectAll platforms0.5.0
BF-26d302a-3A provider object's login that sends a prompt or event type Pi does not define fails with an error that names…PiG's own defectAll platforms0.5.0
BF-27a1650The keybindings manager's key text and key matching now cover every keybinding the merged table defines…PiG's own defectAll platforms0.5.0
BF-27ddc30A tool card shows the arguments of a tool call while they stream, as Pi does: a streamed write card shows its…PiG's own defectAll platforms0.5.0
BF-27f3324The npm release packer reads npm pack --json output from npm 12, which keys the result by package name, as…PiG's own defectAll platforms0.5.0
BF-28b8425Blank-key detection for compat API keys, the Vertex API key and the Vertex custom base URL trim whitespace…PiG's own defectAll platforms0.5.0
BF-28f8b3bfix(tui): editor wrap of a one-grapheme segment wider than the width no longer overflows the stack (D158)PiG's own defectAll platformsUnreleased
BF-2a055a7An extension dialog (select, confirm, input, editor) opened while a focused ctx.ui.custom component…PiG's own defectAll platforms0.5.0
BF-2a0fb96/cost lists its tools in a stable order: formatCostSummary ranged over snap.ToolTotals, so the Tools:…PiG's own defectAll platforms0.5.0
BF-2a919a4An indented continuation line of a list item loses up to four spaces, a lazy line after a quoted list item…PiG's own defectAll platforms0.5.0
BF-2ab79ceAn extension provider whose streamSimple hands its request to pi-ai's compat streamSimple (or stream)…PiG's own defectAll platforms0.5.0
BF-2ab79ce-2A provider registered with its own streamSimple runs that callback only for its models whose api is the…PiG's own defectAll platforms0.5.0
BF-2ad7249fix(ledger): close the getJsonSchemaToolParameters and google convertTools rowsPiG's own defectAll platformsUnreleased
BF-2be0883fix(extensions): parallel Node runtime linking reports the walk-order first failure under every schedulePiG's own defectAll platformsUnreleased
BF-2c1959cA Mistral stream is no longer aborted mid-generation by the request timeout: the timeout now covers only the…PiG's own defectAll platforms0.5.0
BF-2c5db8bA Piglet Binary with a fused Go extension starts within a few milliseconds of Stock PiGPiG's own defectAll platforms0.5.0
BF-2c88c5fIll-formed UTF-8 in a string that PiG re-encodes as JSON (an extension's raw JSON, tool schemas, code-mode…PiG's own defectAll platforms0.5.0
BF-2d05dc8A models.json cost that sets tiers: [] (a custom model's cost, or a modelOverrides cost) keeps…PiG's own defectAll platforms0.5.0
BF-2d71556fix(codingagent): queued-message lines use the dim foreground; hint separator closes only the foregroundPiG's own defectAll platformsUnreleased
BF-2d99958A terminal's reply to the startup color query now applies the colors and ends the startup wait before the…PiG's own defectAll platforms0.5.0
BF-2ea8ad2The openai-completions, openai-responses and openrouter-images requests build their URL as the openai SDK…PiG's own defectAll platforms0.5.0
BF-2f881ba"Your first extension" now says that pig install --validate-only exits 1 when the extension does not buildPiG's own defectAll platforms0.5.0
BF-2fc2307A scalar that names a collection tag (!!map 1, !!seq true) inside a complex mapping key stays plain in…PiG's own defectAll platforms0.5.0
BF-305bd8bA durable task that panics with a value whose String method panics now faults with the message `[object…PiG's own defectAll platforms0.5.0
BF-30d4a6dFix in shipped codePiG's own defectAll platformsUnreleased
BF-30e47f4fix(tui): SetThemeInstance notifies the active theme observers, as Pi's setThemeInstance runs its callbackPiG's own defectAll platformsUnreleased
BF-3154dfbStarting a Go extension no longer fails with `hash <dir> after its build: open …/release/deps/rmeta…: no…PiG's own defectAll platformsUnreleased
BF-31740ecA waitForIdle() waiter registered during a run stays pending through agent_settled handlers and the…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-31a7d77SessionBeforeTreeResultSummary.Usage is typed as ai.UsagePiG's own defectAll platforms0.5.0
BF-31cde9fA Node extension that calls pi.appendEntry now sees the new entry in ctx.sessionManager.getEntries() in…PiG's own defectAll platforms0.5.0
BF-31d09faFixed OpenRouter router models such as openrouter/auto showing a cost of -$1,000,000 per 1M tokens…PiG's own defectAll platforms0.5.0
BF-31d85abfix duplicate jsstring import in tui/text.goPiG's own defectAll platformsUnreleased
BF-31f4517Radius gateway and OAuth authorization URLs are parsed and serialized with the WHATWG URL parser, as Node's…PiG's own defectAll platforms0.5.0
BF-3270e5fllama-cpp-classify answers match Pi bit for bit: the softmax uses jsmath.Exp, V8's fdlibm Math.exp…PiG's own defectAll platforms0.5.0
BF-32ae04cDurable (Pi 89e5c2c0): a generation waiting on a blocking threshold compaction fails with model_error…PiG's own defectAll platforms0.5.0
BF-32b5367A running !command that prints a lot, the /resume search over many long sessions, and streamed answers…PiG's own defectAll platforms0.5.0
BF-32ce7adThe Termux page shows the [Context] line as PiG prints it, ~/.pig/agent/AGENTS.mdPiG's own defectAndroid and Termux0.5.0
BF-32e86a8Go, Python and Rust SDK: theme.bold, italic, underline, inverse and strikethrough now draw as Pi's…PiG's own defectAll platforms0.5.0
BF-334bb4bA Gemini or Mistral stream chunk with a member of an unexpected type no longer sorts the members of a tool…PiG's own defectAll platforms0.5.0
BF-3351167The OAuth callback server reads an absolute-form request target with a scheme other than http or https, a…PiG's own defectAll platforms0.5.0
BF-33dc8a0fix(ai): keep a custom client identity's slash in Bedrock's user agent, as the JavaScript SDK doesPiG's own defectAll platformsUnreleased
BF-33ea9e4fix(sdk): name the Go ModelRegistry.Find change in the upgrade rules; Node adoption survives an unbound leaf…PiG's own defectAll platformsUnreleased
BF-33fe10dModelsError appends the cause detail the way Pi does: an Error cause with an empty message contributes its…PiG's own defectAll platforms0.5.0
BF-33fe10d-2Live-Pi oracles TestExtensionSelectorMatchesPi (4,320 probes) and TestExtensionInputMatchesPi (1,144…PiG's own defectAll platforms0.5.0
BF-3452432Escape cancels a running ! or !! command again, as in Pi: the command stops and its block ends with…PiG's own defectAll platforms0.5.0
BF-34aa421fix(agent): hand the stream function Pi's messages; repair the tool flow in the provider pathPiG's own defectAll platformsUnreleased
BF-34b8e92Fix in shipped codePiG's own defectAll platformsUnreleased
BF-34bcd58OpenAI Responses prices a response that reports no service_tier at the requested tier, as Pi's…PiG's own defectAll platforms0.5.0
BF-34c4d8eMCP tools declare the CallToolResult scripts receive as Pi 1.2.0 does: content blocks with their type…PiG's own defectAll platforms0.5.0
BF-34c4d8e-2The MCP extension decides which tools reach MCP tools by their ReachesTools, as Pi 1.2.0 does: another…PiG's own defectAll platforms0.5.0
BF-34f7594An extension that calls ctx.shutdown() from a session_start handler, or from a session_before_switch or…PiG's own defectAll platforms0.5.0
BF-34f7594-2Fixed the interactive bash block stripping escape sequences with the wrong rule…PiG's own defectAll platforms0.5.0
BF-34f7594-3Fixed the re: mode of the session selector search to read its pattern as JavaScript does (`new…PiG's own defectAll platforms0.5.0
BF-34f7594-4Repeated codemode timeouts or aborts can no longer pile up running VMs: at most two codemode VMs are alive…PiG's own defectAll platforms0.5.0
BF-3548f29fix(tui): bash block strips ANSI with Pi's utils/ansi.ts rulePiG's own defectAll platformsUnreleased
BF-35e46b1Codemode output and returned values keep a nested Error's name and message, with its own enumerable fields…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-364cc57RPC model objects of built-in models match Pi's: each keeps its catalog entry's own member order and spelling…PiG's own defectAll platforms0.5.0
BF-3651a90A provider stream that keeps sending keepalive comments or pings but no event now ends with an error after…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-369b1e1grep with context no longer loads each matched file into memoryPiG's own defectAll platforms0.5.0
BF-36e192bfix(lint): port Kitty chunking in UTF-16 units; clear the codemode, terminal image and exit-restore lint hitsPiG's own defectAll platformsUnreleased
BF-36e82adfix(durable): a non-finite number is a strict JSON error whichever JSON package a value's own encoder usedPiG's own defectAll platformsUnreleased
BF-37c6048fix(tui): a streamed Markdown never resumes past a paragraph clipped before a LaTeX startPiG's own defectAll platformsUnreleased
BF-37d2daaAn extension's ctx.model and model lists keep a model's cost.tiers when the model's record has it as an…PiG's own defectAll platforms0.5.0
BF-3804a5aThe pi-messages and classifier (System One, decisions) requests are sent to the URL as written and a URL with…PiG's own defectAll platforms0.5.0
BF-3840820MCP tool results whose content blocks carry members of another type than the protocol's (a numeric text, a…PiG's own defectAll platforms0.5.0
BF-38b0f0cSyntax highlighting keeps its colors after the first line of multiline strings and comments in fenced code…PiG's own defectAll platforms0.5.0
BF-397866fA Go MCP client that declares an empty capability, such as sampling or elicitation, now sends it as {}PiG's own defectAll platforms0.5.0
BF-3a0d0e1A Provider object that an extension registers while it loads is now applied after every extension has…PiG's own defectAll platformsUnreleased
BF-3ad9ec8fix(tui): tree label prompt gives its input width-2 cells (Pi LabelInput)PiG's own defectAll platformsUnreleased
BF-3b009a3chord/host: a child host forked by ForkDriver no longer inherits its parent's CHORD2_LOCKPiG's own defectAll platforms0.5.0
BF-3b3dba6A streamed tool argument of -0 is held as 0, so the serialized arguments write 0 as Pi's…PiG's own defectAll platforms0.5.0
BF-3b8aa66A Piglet Binary that strips bedrock-converse-stream links the AWS SDK again: the Bedrock retry backoff…PiG's own defectAll platforms0.5.0
BF-3ba74f2fix(codingagent): wait for a read's rest on the route snapshot that found no modalPiG's own defectAll platformsUnreleased
BF-3cb7131fix(sdk-py): stage pig_sdk.tui with the package, which pig_sdk imports at load; keep the pi-ai patterns…PiG's own defectAll platformsUnreleased
BF-3ce6bcbfix(changelog,source): /i scheme tests fold as JavaScript does, not as RE2's (?i)PiG's own defectAll platformsUnreleased
BF-3cf03c0A faux provider error message that carries a response id writes responseId after errorMessage in session…PiG's own defectAll platforms0.5.0
BF-3d12e48fix(tui): tree selector cursor follows Pi's selectedIndex over an empty listPiG's own defectAll platformsUnreleased
BF-3d475c4Image resize strategy against live Pi: new TestResizeImageDimensionStrategyMatchesPi…PiG's own defectAll platforms0.5.0
BF-3def2a7MCP servers whose names are numbers ("9", "42") load and report their errors in the order Pi does: a JSON…PiG's own defectAll platforms0.5.0
BF-3e755efFix in shipped codePiG's own defectAll platformsUnreleased
BF-3ee123cCI: the Linux / portmap job (make portmap-check) no longer runs out its 60-minute limitPiG's own defectLinux0.5.2
BF-3fa6e63MCP servers with resources registered no tools until every resource was listed, so a slow listing (a large…PiG's own defectAll platforms0.5.0
BF-3fbdfd2A configured number in settings text prints as JavaScript's String(number): -0 is 0, and a magnitude of…PiG's own defectAll platforms0.5.0
BF-4027b34A finished code-mode execution no longer keeps its closed VM reachable through its stopped halt-grace timer…PiG's own defectLinux0.5.0
BF-4058f34The /fork message picker checks its keys in Pi's order (up, down, confirm, cancel) when bindings overlapPiG's own defectAll platforms0.5.0
BF-40b5a89Assistant messages in session files and RPC output list responseId before responseModel, Codex's…PiG's own defectAll platforms0.5.0
BF-40bc651The Anthropic event reader decides message_start and message_stop from the parsed event type as Pi does (not…PiG's own defectAll platforms0.5.0
BF-410d733settings.json, trust.json and auth.json are written as Pi's JSON.stringify writes them: <, > and…PiG's own defectAll platforms0.5.0
BF-411a8cbBackground runs of pi-subagents (and other helpers an extension starts as a detached Node process) complete…PiG's own defectAll platforms0.5.0
BF-4163646AuthStorage file semantics: new live-Pi oracle TestAuthStorageFileMatchesPi…PiG's own defectAll platforms0.5.0
BF-41b305dCodemode rejects a // @options: line that is not the first line of the script with an error before the…PiG's own defectAll platforms0.5.0
BF-42116f8Durable: the system baseline written after a compaction now leads the model context when the kept history…PiG's own defectAll platforms0.5.0
BF-4259631An extension's direct call into a pi-ai API module (openAICompletionsApi() and the other bridged APIs)…PiG's own defectAll platforms0.5.0
BF-42c059afix(tui): bash block strips ANSI with Pi's utils/ansi.ts rulePiG's own defectAll platformsUnreleased
BF-42e3d0efix(hljs): a class that negates only \r still reaches across lines; gate the streamed code block LinearPiG's own defectAll platformsUnreleased
BF-4312595fix(ai,durable): keep a value that is not JSON an error when a message or a nested result is encodedPiG's own defectAll platformsUnreleased
BF-432ab7fAn agent assistant message that holds an empty diagnostics array writes it as [], as Pi does, instead of…PiG's own defectAll platforms0.5.0
BF-433d232A request sent after /tree navigation to a point between parallel tool calls and their results no longer…PiG's own defectAll platforms0.5.0
BF-433d232-2An extension custom message whose content is a block array (text or images), or an empty string, now reaches…PiG's own defectAll platforms0.5.0
BF-433d232-3DefaultResourceLoader.reload: new live-Pi oracle TestDefaultResourceLoaderReloadMatchesPi…PiG's own defectAll platforms0.5.0
BF-433d232-4ReadOnlyAuthStorage: new live-Pi oracle TestReadOnlyAuthStorageMatchesPi…PiG's own defectAll platforms0.5.0
BF-43a2293Key order of non-index keys stays byte order (a Go map has no insertion order): question Q-state-order filedPiG's own defectAll platformsUnreleased
BF-43c46f5Chord host timers (reload vote, gate and retire deadlines, link pings, service linger, host manager grace…PiG's own defectAll platforms0.5.0
BF-43d2597@ file autocomplete finds paths for queries that hold regex metacharacters such as [ or (, for a query…A fix Pi shipped that PiG portedAll platforms0.5.2
BF-43d2597-2/compact <instructions> applies the instructions to the summary of the start of a turn that compaction…A fix Pi shipped that PiG portedAll platforms0.5.2
BF-43d2597-3A resumed session and /tree navigation activate the extension tools the branch never added or removed, such…A fix Pi shipped that PiG portedAll platforms0.5.2
BF-43d2597-4Switching to a session whose system message holds a toolsAdded or toolsRemoved member the loadout restore…A fix Pi shipped that PiG portedAll platforms0.5.2
BF-448d2a9Fix in shipped codePiG's own defectAll platformsUnreleased
BF-448ff71A panic in an extension error listener is now named on stderr instead of vanishing, and extension SDK…PiG's own defectAll platforms0.5.0
BF-448ff71-2MCP notifications no longer swallow every panic from the notify callback; only the "MCP failed to load"…PiG's own defectAll platforms0.5.0
BF-44f8d86Fix in shipped codePiG's own defectAll platformsUnreleased
BF-4599fabfix(ai): an iteration's shared cancellation does not compare contextsPiG's own defectAll platformsUnreleased
BF-45ec149Read-only credential storage reports an unreadable or malformed auth.json as Pi does: `Failed to read…PiG's own defectAll platforms0.5.0
BF-4610c9fA ! or !! command's block draws the bytes Pi's BashExecutionComponent draws: its rows are padded to the…PiG's own defectAll platforms0.5.0
BF-4649166fix(sdk): place the arrival-order start in the request scope so a tool_call round trip keeps its 68…PiG's own defectAll platformsUnreleased
BF-46abed3Fix in shipped codePiG's own defectAll platformsUnreleased
BF-471c46eAn MCP tool or resource link with an explicit empty title keeps it, as Pi does, instead of falling back to…PiG's own defectAll platforms0.5.0
BF-4789fa5thinking_level_select and turn_start handlers are covered through the production Session: a handler sees…PiG's own defectAll platforms0.5.0
BF-47c3007/mcp now opens at once while servers are still connecting, and connection actions (reconnect, enable…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-47c3007-2MCP OAuth sign-ins can now be cancelled with Esc at every step, session shutdown stops a running sign-in, and…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-47c3007-3Shutdown no longer waits up to 15 seconds to refresh an MCP OAuth token that was about to expire, only to…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-48318ebfix(hljs): a resumed run counts the scan iterations before its checkpointPiG's own defectAll platformsUnreleased
BF-48435cdA parsed http, https, ws, wss or ftp URL reports its fragment percent-encoded, as Node's url.hash doesPiG's own defectAll platforms0.5.0
BF-486aee5fix(sdk): the Go SDK ends the connection on a frame it cannot decodePiG's own defectAll platformsUnreleased
BF-488a47achord/host: ProcessLock no longer fails with invalid argument on macOS and the BSDs when the temporary…PiG's own defectmacOS0.5.0
BF-48a28feModels an extension's provider object returns keep the member order the extension wrote them in, as Pi keeps…PiG's own defectAll platforms0.5.0
BF-495f017A bashExecution entry records excludeFromContext: false when the caller passed false, as Pi does; an…PiG's own defectAll platforms0.5.0
BF-495f017-2A branch-summary entry writes fromHook: false in Pi's member order, and the summary has its SpacerPiG's own defectAll platforms0.5.0
BF-4a0bc3dThe footer context percentage is refreshed after a ! command is recorded, as Pi's footer asks the session…PiG's own defectAll platforms0.5.0
BF-4ac21c8fix(jsloop): run a tick whose tool call already ended at once, though no wait is openPiG's own defectAll platformsUnreleased
BF-4afdbacProjectTrustStore reads entries in JavaScript order: readTrustFile named whichever invalid value Go's map…PiG's own defectAll platforms0.5.0
BF-4bcc7daStreamProxy merges the final tool call and reports abort and timeout as Pi does, and it no longer changes a…PiG's own defectAll platforms0.5.0
BF-4c45afafix(sdk-py): stage pig_sdk.tui with the package, which pig_sdk imports at load; keep the pi-ai patterns…PiG's own defectAll platformsUnreleased
BF-4c7ccf2A classifier response that reports more than 2,147,483,647 input or output tokens keeps the reported count…PiG's own defectAll platforms0.5.0
BF-4d04134The cancel hint of extension loaders (/share, /bug) shows the key bound to tui.select.cancel instead of…PiG's own defectAll platforms0.5.0
BF-4d33f78fix(tui): a tool error result collapses like any other (Pi updateResult)PiG's own defectAll platformsUnreleased
BF-4d965e3The /tree label of a custom tool call shows its arguments in the order the model sent them, with <, >…PiG's own defectAll platforms0.5.0
BF-4e204f4fix(sdk-py): clear bandit's findings in the 0.5.2 Python SDK codePiG's own defectAll platformsUnreleased
BF-4e47260A lazy line joins a list item as written (indentation kept), four spaces after the marker make the first line…PiG's own defectAll platforms0.5.0
BF-4e5b684A list item whose first line is another list item is rendered as a nested list with no bullet of its own, and…PiG's own defectAll platforms0.5.0
BF-4e8c517fix(runtimecell): keep the Python runner's _run callable without the built eventPiG's own defectAll platformsUnreleased
BF-4f079a7An MCP request over Streamable HTTP that is cancelled or times out while the server's authorization is being…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-4f6992dDurable: a tool's PrepareArguments result of a typed object (a struct, or a map of another element type)…PiG's own defectAll platforms0.5.0
BF-4f6992d-2Durable: a PrepareArguments result that is not an object fails validation as an invalid_arguments tool…PiG's own defectAll platforms0.5.0
BF-4fab2efDurable error values (FileError, ExecutionError, JsonlCorruptionError, ReadAfterWrite, StorageRejected…PiG's own defectAll platforms0.5.0
BF-4fc8c2eWith an active Piglet, an extension that denies every tool with SetActiveTools([]) is no longer widened…PiG's own defectAll platforms0.5.0
BF-4fe779eA /reload that arrived while the supervisor was restarting a crashed isolated extension could end the…PiG's own defectAll platforms0.5.2
BF-50233fdAn env SSH command that runs past its timeout now receives SIGTERM, as in Pi, instead of SIGKILL, and the…PiG's own defectAll platforms0.5.0
BF-504869bThe MCP client uses JavaScript's trim() and \s where Pi does: in stdio and Streamable HTTP blank-line and…PiG's own defectAll platforms0.5.0
BF-504869b-2MCP response headers are read as Node's fetch returns them, one Latin-1 character per bytePiG's own defectAll platforms0.5.0
BF-504869b-3A JSON-RPC number id is a JavaScript numberPiG's own defectAll platforms0.5.0
BF-50f526dThe Go, Rust and Python SDKs export Pi's CURRENT_SESSION_VERSION, VIRTUAL_MODEL_STATE_ENTRY…PiG's own defectAll platforms0.5.0
BF-5144418Fix in shipped codePiG's own defectAll platformsUnreleased
BF-5260e5cThe Rust SDK's get_model_info() reads Pi's maxTokens and cost (it reported 0 for the output limit and…PiG's own defectAll platforms0.5.0
BF-52858aasetWorkingMessage with no message restores the default working label and an empty string shows an empty…PiG's own defectAll platforms0.5.0
BF-53385a3Registering a large MCP catalog, building a CodeMode sandbox from one, and CodeMode tool discovery were…PiG's own defectAll platforms0.5.2
BF-5380e0afix(extension): load failure shows the extension's own cause; registration edge-case rowsPiG's own defectAll platformsUnreleased
BF-54283cefix(extension): Shutdown closes live child sessions and releases the objects they holdPiG's own defectAll platformsUnreleased
BF-542b536fix(cli): PIG_VERSION_FORMAT=pi prints the pinned Pi package versionPiG's own defectAll platformsUnreleased
BF-5443b6cdurable/images is the image processor of pi-durable: images.CreatePhotonImages() (also images/node and…PiG's own defectAll platforms0.5.0
BF-54a5bf6The host answers a parsed frontmatter with <, >, & and U+2028 as they are (as JSON.stringify writes…PiG's own defectAll platforms0.5.0
BF-5654e71MCP OAuth discovery and resource selection now read URLs the way Node's URL does (default ports, dot…PiG's own defectAll platforms0.5.0
BF-5654e71-2A JSON-RPC request or notification with a malformed error member is no longer rejected, and a method that…PiG's own defectAll platforms0.5.0
BF-56731e9/login runs a provider object's OAuth login with Pi's login interaction, so an info event shows in the…PiG's own defectAll platforms0.5.0
BF-56731e9-2An extension's API-key login no longer shows "Waiting for authentication..." after an authorization URLPiG's own defectAll platforms0.5.0
BF-56a0400Single-line inputs (login prompts, search boxes) ignore xterm modifyOtherKeys text sequences, as Pi's Input…PiG's own defectAll platforms0.5.0
BF-56f1d3cA family document member accepts a typed Go value as its seed, as CreateTask input and snapshots already doPiG's own defectAll platforms0.5.0
BF-56f1d3c-2Submission.Wait, WaitForTask and the idle waits return after every commit subscriber has run for the…PiG's own defectAll platforms0.5.0
BF-570f857Fixed interactive sessions leaving extension prompt sections, such as the MCP mcp_servers list, out of the…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-5732479auth.json keeps each credential's properties in the order they were read or returned by an extension's…PiG's own defectAll platforms0.5.0
BF-5774749A Piglet found by name in <project>/.pig/piglets now follows project trust: it is used only when the…PiG's own defectAll platforms0.5.0
BF-578c1a3The "Applied use cases" page in the manual that ships inside PiG now links the Security page on the websitePiG's own defectAll platforms0.5.0
BF-579b4d2fix(durable): a non-finite number is a strict JSON error whichever JSON package a value's own encoder usedPiG's own defectAll platformsUnreleased
BF-57a1687fix(ai,agent): return a projection's blocks apart from its scalars so the caller's buffer stays on its stackPiG's own defectAll platformsUnreleased
BF-57ee33bA model an extension registers with registerProvider reaches ctx.modelRegistry, ctx.model and RPC as…PiG's own defectAll platforms0.5.0
BF-59456aePressing a key in a subprocess extension's overlay no longer freezes interactive mode when the key reaches…PiG's own defectAll platforms0.5.0
BF-598e27fEscape over an open autocomplete list during compaction now closes the list and lets compaction continue, as…PiG's own defectAll platforms0.5.0
BF-59fb623fix(requestvalidity): one Responses assistant turn holds all its items, in any orderPiG's own defectAll platformsUnreleased
BF-5a093cdIn print and JSON mode, Node extensions that share one process now keep Pi's order at disposalPiG's own defectAll platforms0.5.0
BF-5aab94cThe color level check reads TERM with JavaScript's case-insensitive matching, as Pi's chalk does: a name…PiG's own defectAll platforms0.5.0
BF-5ab2304Starting an extension no longer fails with fork/exec …: text file busy when the host builds or starts…PiG's own defectLinuxUnreleased
BF-5b0e909/scoped-models: an empty search no longer makes enable-all/clear-all act on every model, clearing every model…PiG's own defectAll platforms0.5.0
BF-5b15407In-process extensions that call pi.getCommands() now receive the extension commands, prompt templates and…PiG's own defectAll platforms0.5.0
BF-5b1d5b1A resource pattern that starts with ~ (!~/claude-backup/skills/pdf, +~/…, -~/…, wildcards) now…PiG's own defectAll platforms0.5.0
BF-5b4a3d3A lazy line no longer continues a quote whose last quoted line is empty (>\nnext is an empty quote then a…PiG's own defectAll platforms0.5.0
BF-5bc4773RPC set_model applies after its auth check, as Pi's awaited setModel does, so a cycle_model in the same…PiG's own defectAll platforms0.5.0
BF-5bdbf20fix(cli): keep scratch install output off the validate-only reportPiG's own defectAll platformsUnreleased
BF-5c4167afix(extension): hold the theme colors map the wire palette memo is keyed onPiG's own defectAll platformsUnreleased
BF-5c9d23fA provider object's OAuth login receives Pi's login options, as oauth.login(interaction, options) does in PiPiG's own defectAll platforms0.5.0
BF-5d004aefix: give Windows git children core.longpaths through the environment, keeping Pi's argvPiG's own defectWindowsUnreleased
BF-5d36a58fix(agent): end the tool flow at custom user messages and hold system messages mid-flowPiG's own defectAll platformsUnreleased
BF-5d6bb25The request-string walk that splices cached strings into a request body finds a cycle by the pointer, map or…PiG's own defectAll platforms0.5.0
BF-5d8eda1pig auth check and pig auth print-api-key no longer reject --api-key that another option takes as its…PiG's own defectAll platforms0.5.0
BF-5dbfed1A scripted faux response can carry DurationMsPiG's own defectAll platforms0.5.0
BF-5e36d87The assistant-message frame encoder no longer re-parses and re-serializes a whole tool call's arguments for…PiG's own defectAll platforms0.5.0
BF-5e3e346An explicit anthropic-beta header, from a model's headers or the request, no longer makes a…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-5eab79dprocessImage against live Pi: new TestProcessImageMatchesPi…PiG's own defectAll platforms0.5.0
BF-5ec5f5fThe built-in bash, read and list tool cards now show an image block that has no MIME type as `[Image…PiG's own defectAll platforms0.5.0
BF-5f03fa4An extension that a child agent session's resource loader loads now receives the session's events, as in Pi…PiG's own defectAll platforms0.5.2
BF-5f03fa4-2The contexts of those extensions read the child session: ctx.model, ctx.scopedModels (the session's…PiG's own defectAll platforms0.5.2
BF-5f22b85fix(coding): shut down the extension host of a DefaultResourceLoader an extension constructed when its last…PiG's own defectAll platformsUnreleased
BF-60c4c6fA tool call whose finished arguments are not a valid JSON object no longer runs with the parsed prefix: its…PiG's own defectAll platforms0.5.0
BF-612955aThe Rust and Go SDKs keep an unpaired surrogate through prepareBranchEntries…PiG's own defectAll platforms0.5.0
BF-61300bbMCP OAuth reads a non-HTTP redirect or server URL as Node's new URL does: dot segments are removed, a…PiG's own defectAll platforms0.5.0
BF-61300bb-2Sorting names with localeCompare (MCP servers, sessions, login providers, codemode namespaces, model lists…PiG's own defectAll platforms0.5.0
BF-6132277An MCP OAuth authorization response with an iss parameter is now rejected when no authorization server…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-61fc01dlist_mcp_resources, list_mcp_resource_templates and read_mcp_resource return every member a server sent…PiG's own defectAll platforms0.5.0
BF-61fc01d-2MCP tool descriptions, resource MIME types, a pasted sign-in redirect URL and the resource tools' server…PiG's own defectAll platforms0.5.0
BF-6235532Filterable selector lists (theme, images, tree-filter-like pickers) check up/down before confirm/cancel like…PiG's own defectAll platforms0.5.0
BF-6258d27fix(rpc): a replacement aborts the compactions of its batch at its teardown onlyPiG's own defectAll platformsUnreleased
BF-625a32eDisplay text that PiG cuts at a length (the /tree listing, the Piglet system-prompt preview, an extension…PiG's own defectAll platforms0.5.0
BF-626dcccOpenAI-compatible Chat Completions requests resolve chat_template_args (the baseten thinking format) and…PiG's own defectAll platforms0.5.0
BF-62a1c87The ExtensionSelector and ExtensionInput oracles also run with the Kitty keyboard protocol active (CSI-u…PiG's own defectAll platforms0.5.0
BF-62ae30dDurable: reading the history of a conversation whose parent conversation is not stored now fails with Pi's…PiG's own defectAll platforms0.5.0
BF-62c3691grep with a large context no longer holds every context line in memory: its memory is bounded by the 50KB…PiG's own defectAll platforms0.5.0
BF-6323c8efix(ai): a Bedrock model outside the catalog is a bedrock-converse-stream model of amazon-bedrock, so its own…PiG's own defectAll platformsUnreleased
BF-63b4fc8NodeExecutionEnv.Watch gives a directory replaced at the same path a new watcher, reports changes to the…A fix Pi shipped that PiG portedmacOS0.5.0
BF-63bb0b7pig extension upgrade now rewrites a Context getter call that is the only argument of a variadic call…PiG's own defectAll platforms0.5.0
BF-644a9f2Changelog link rewriting and git source parsing match a URL scheme case-insensitively as Pi's regex with…PiG's own defectAll platforms0.5.0
BF-644a9f2-2A Node extension's ctx.ui.custom factory can call tui.showOverlay, tui.hideOverlay and tui.setFocus…PiG's own defectAll platforms0.5.0
BF-644b3e0A codemode script that calls text() tens of thousands of times no longer blocks the process: adjacent text…PiG's own defectAll platforms0.5.0
BF-648ff3eA child session's message_update event carries the message so far and the assistant event's partial, as…PiG's own defectAll platforms0.5.2
BF-648ff3e-10pig --list-models now prints only the models the model runtime reports as available, as Pi doesPiG's own defectAll platforms0.5.2
BF-648ff3e-100An SDK's ctx.hasPendingMessages() in interactive mode counts queued user messages only, as Pi does, and no…PiG's own defectAll platforms0.5.2
BF-648ff3e-101A Node extension that changes the active tools or registers a tool now reads the system prompt back as Pi doesPiG's own defectAll platforms0.5.2
BF-648ff3e-102ctx.isIdle() after pi.sendMessage(..., { triggerTurn: true }) in an agent_settled handler of a Node…PiG's own defectAll platforms0.5.2
BF-648ff3e-103A Node extension's ctx.ui.select, confirm and input honor options.signal, as Pi's RPC and interactive…PiG's own defectAll platforms0.5.2
BF-648ff3e-104A start could fail with a bare missing-file error when another pig's cache collection removed the cached…PiG's own defectAll platforms0.5.2
BF-648ff3e-105A tool's prepareLoadout hook now runs again before every later turn of a run, as in Pi, so the descriptions…PiG's own defectAll platforms0.5.2
BF-648ff3e-106Fixed the chord host keeping a disposed object reachable until its ready timeout (a minute by default) had…PiG's own defectAll platforms0.5.2
BF-648ff3e-107The /tree "Summarize branch?" selector and "Custom summarization instructions" editor, the /import…PiG's own defectAll platforms0.5.2
BF-648ff3e-108OpenAI Responses requests keep the prompt cache across tool changes (Pi #11142)PiG's own defectAll platforms0.5.2
BF-648ff3e-109On a file system that refuses file locks (an NFS mount without a lock manager, a FUSE or SMB share), starting…PiG's own defectAll platforms0.5.2
BF-648ff3e-11/reload no longer reports `Extension "builtin:codemode" error: This extension ctx is stale after session…PiG's own defectAll platforms0.5.2
BF-648ff3e-110In RPC mode, a toast an extension deferred with setTimeout(0) from a model_select or session_start…PiG's own defectAll platforms0.5.2
BF-648ff3e-111In RPC mode, a message an extension's session_start listener sent while a command replaced the Session was…PiG's own defectAll platforms0.5.2
BF-648ff3e-112In RPC mode, an extension command that sends a user message and then awaits ctx.waitForIdle() (the init…PiG's own defectAll platforms0.5.2
BF-648ff3e-113Two Bun runtime installs, or an install and a status check, in different processes no longer leave a moment…PiG's own defectAll platforms0.5.2
BF-648ff3e-114A slow toolchain version check no longer blocks other starts without limitPiG's own defectAll platforms0.5.2
BF-648ff3e-115Fixed the chord host manager letting other code read it as stopped, or start the child again, before the…PiG's own defectAll platforms0.5.2
BF-648ff3e-116Fixed a chord link closed with a reason reporting "peer closed" to its close handlers instead when the peer…PiG's own defectAll platforms0.5.2
BF-648ff3e-119Concurrent extension select, confirm, input, editor dialogs and inline custom() components in…PiG's own defectAll platforms0.5.2
BF-648ff3e-12Prompts submitted after /scoped-models refreshed the model catalogs and Ctrl+P switched the model no longer…PiG's own defectAll platforms0.5.2
BF-648ff3e-120Pasting in the shell no longer hangs after copying text in pig and suspending it with Ctrl+Z on Linux: the…PiG's own defectLinux0.5.2
BF-648ff3e-123mcp.json files that start with a UTF-8 BOM, which Windows PowerShell 5.1 adds when it rewrites a file, load…PiG's own defectWindows0.5.2
BF-648ff3e-124A Go or Rust extension edited while it was being built could be cached under the hash of the source from…PiG's own defectAll platforms0.5.2
BF-648ff3e-125A Node extension that registers or unregisters a provider through ctx.modelRegistry (or…PiG's own defectAll platforms0.5.2
BF-648ff3e-126ctx.abort() aborts the run's ctx.signal, and the signal a tool receives, before it returns in a Node…PiG's own defectAll platforms0.5.2
BF-648ff3e-127pi.getCommands() in a Node extension lists a command registered after load by the same turn's…PiG's own defectAll platforms0.5.2
BF-648ff3e-128A Node extension tool that awaits real work and then takes the file mutation queue (the upstream subagent…PiG's own defectAll platforms0.5.2
BF-648ff3e-129RPC mode: an extension_ui_response whose value is not a string, whose confirmed is not a boolean, or…PiG's own defectAll platforms0.5.2
BF-648ff3e-13Two parallel edit calls through two names of one existing file (a hard link, or a case alias on a…A Pi defect that PiG fixed firstmacOS0.5.2
BF-648ff3e-130A YAML date used as a mapping key is named with the time zone names of Node 24.22 and later, the Node Pi runs…PiG's own defectAll platforms0.5.2
BF-648ff3e-14The Cloudflare host's pi-durable facade sends Pi v1.4.0's client identity in every model request's options…PiG's own defectAll platforms0.5.2
BF-648ff3e-15The Go, Rust and Python extension SDKs' isContextOverflow treats an HTTP 413 error as a context overflow…PiG's own defectAll platforms0.5.2
BF-648ff3e-16The Go, Rust and Python extension SDKs' isRetryableAssistantError retries on a status code (429, 500, 502…PiG's own defectAll platforms0.5.2
BF-648ff3e-17CI: TestNodeCellAddedMembersStayWithinTheirOwnState no longer fails under loadPiG's own defectAll platforms0.5.2
BF-648ff3e-18CI: a Node extension no longer holds a request the host sends it behind the highlight.js language loadPiG's own defectAll platforms0.5.2
BF-648ff3e-19CI: waitForRenderProxy, the wait of the render proxy and packed padding tests for the extension's answer…PiG's own defectAll platforms0.5.2
BF-648ff3e-2A streamed child-session text costs wire bytes linear in its length: message_update events travel as a…PiG's own defectAll platforms0.5.2
BF-648ff3e-20CI: TestStartupReusesPreTrustExtensionsAcrossEntrypoints no longer reads an extension process that is still…PiG's own defectAll platforms0.5.2
BF-648ff3e-21CI: the scoped-model selector parity scenarios no longer capture the selector before the catalog refresh…PiG's own defectAll platforms0.5.2
BF-648ff3e-22CI: a Node extension no longer holds the first request the host sends it behind its bytecode-cache flushPiG's own defectAll platforms0.5.2
BF-648ff3e-23CI: TestTerminalProgramGetsDefaultSignalsWhenTheDaemonStartedIgnoringThem no longer fails every run under…PiG's own defectAll platforms0.5.2
BF-648ff3e-24A package manifest glob walks as Node 24.22 and later walk itPiG's own defectAll platforms0.5.2
BF-648ff3e-25An HTTP 413 from any provider is treated as request-size overflow, so the agent compacts and retries instead…PiG's own defectAll platforms0.5.2
BF-648ff3e-26On Windows, PiG finds PowerShell in its installation directories when it is not on PATH: PowerShell 7 under…PiG's own defectWindows0.5.2
BF-648ff3e-27On Windows, the legacy WSL bash.exe launcher is recognized under the actual Windows directory in…PiG's own defectWindows, WSL0.5.2
BF-648ff3e-28On Windows, an empty SystemRoot takes C:\Windows for taskkill.exe, as an unset one already did, in the…PiG's own defectWindows0.5.2
BF-648ff3e-29On Windows, shells that are runnable Microsoft Store aliases are accepted: a configured shell path…PiG's own defectWindows0.5.2
BF-648ff3e-3coding.CreateAgentSession binds the Session it returns to the Extension Host its resource loader started…PiG's own defectAll platforms0.5.2
BF-648ff3e-30URL parsing follows Node 24.22 and laterPiG's own defectWindows0.5.2
BF-648ff3e-31A packed Go, Python or Rust cell no longer hangs when a later member's factory calls the host before an…PiG's own defectAll platforms0.5.2
BF-648ff3e-32An extension's ctx.hasPendingMessages() now asks the Host instead of reading a cached state snapshot, so it…PiG's own defectAll platforms0.5.2
BF-648ff3e-33Durable with telemetry off (no telemetry or the no-op span) no longer builds span attributes, events or…PiG's own defectAll platforms0.5.2
BF-648ff3e-34The grep tool passes --no-config to ripgrep, as Pi does at 8c4e8738, so a user's RIPGREP_CONFIG_PATH…PiG's own defectAll platforms0.5.2
BF-648ff3e-35Recovery of a truncated assistant attempt (retry and overflow/length compaction) finds the tool results that…PiG's own defectAll platforms0.5.2
BF-648ff3e-36Classifier models take images (Pi 7f225f25): models.classify() accepts images for TypeSafe's Jev and…PiG's own defectAll platforms0.5.2
BF-648ff3e-37The catalog tolerates the free field Pi v1.4.0's radius data carries (recursive/flint), keeping it in the…PiG's own defectAll platforms0.5.2
BF-648ff3e-38The upstream pin moves to Pi v1.4.0PiG's own defectAll platforms0.5.2
BF-648ff3e-39The durable turn classifier retries on a status code (429, 500, 502, 503, 504, 520, 524) only when it stands…PiG's own defectAll platforms0.5.2
BF-648ff3e-4Extension tools of one parallel batch reach the host in Pi's orderPiG's own defectAll platforms0.5.2
BF-648ff3e-44Automatic retry no longer treats digits inside a request id or another number (for example req_011CU503xyz…PiG's own defectAll platforms0.5.2
BF-648ff3e-45Automatic retry now triggers for stream disconnected before completion errors that a proxy wrapping the…PiG's own defectAll platforms0.5.2
BF-648ff3e-46Sign in with ChatGPT: a ChatGPT Business workspace usage-limit error…PiG's own defectAll platforms0.5.2
BF-648ff3e-47Context and the types that hold one (EventBus, ModelRegistry, ReplacedSessionContext…PiG's own defectAll platforms0.5.2
BF-648ff3e-48pi.events.emit in a Go, Rust or Python factory behaves as Pi's does: the first factory-time bus call…PiG's own defectAll platforms0.5.2
BF-648ff3e-49The Python SDK test test_provider_transport_shutdown_does_not_settle_owned_stream[True-*] no longer fails…PiG's own defectAll platforms0.5.2
BF-648ff3e-5The tool_result handlers of a parallel batch start in the order the tools settled, each running to its…PiG's own defectAll platforms0.5.2
BF-648ff3e-50A Go extension SDK loop that is served without Run (a test on its own connection) no longer waits for a…PiG's own defectAll platforms0.5.2
BF-648ff3e-51The Node extension API probe of a failed factory (TestUpstreamFailedFactoryDisablesCapturedAPI) names…PiG's own defectAll platforms0.5.2
BF-648ff3e-52A listener a Go, Rust or Python factory subscribes hears another extension's emit made while the factory…PiG's own defectAll platforms0.5.2
BF-648ff3e-53A command that an extension started with pi.exec and an AbortSignal kept running after the signal…PiG's own defectAll platforms0.5.2
BF-648ff3e-54Extension API parity ledger: removed the stale ctx.ui.custom(options.onHandle) exception (Go, Rust and…PiG's own defectAll platforms0.5.2
BF-648ff3e-55Conformance fixtures now reach the realized SDK export cells the export ledger listed as unreached: the…PiG's own defectAll platforms0.5.2
BF-648ff3e-56Releasing a handle of an object a child session was created over (a ModelRuntime first of all) no longer…PiG's own defectAll platforms0.5.2
BF-648ff3e-57Removing a local package reports start and complete remove progress events, as Pi's remove does for…PiG's own defectAll platforms0.5.2
BF-648ff3e-58registerProvider of a model without an api fails with Pi's Set it at model level or provider level.…PiG's own defectAll platforms0.5.2
BF-648ff3e-59An extension's SessionManager.appendContextEdit(id, null) clears the content instead of failing, and…PiG's own defectAll platforms0.5.2
BF-648ff3e-6The Go, Python and Rust SDKs start the handlers of concurrent event and command requests in the order the…PiG's own defectAll platforms0.5.2
BF-648ff3e-60Extension SDKs: formatKeyText/keyDisplayText upper-cased the first rune of a part, where Pi's…PiG's own defectAll platforms0.5.2
BF-648ff3e-61Extensions: ctx.ui.setTheme(theme) with a theme object makes that object the active theme as it is, as Pi's…PiG's own defectAll platforms0.5.2
BF-648ff3e-62A PiG TLS client no longer accepts a certificate chain through a compromised eMudhra intermediate or a root…PiG's own defectAll platforms0.5.2
BF-648ff3e-63The CI images no longer ship the three removed emSign roots: ca-certificates 20261020-r0 in the ci-go lock…PiG's own defectAll platforms0.5.2
BF-648ff3e-64Shutting a host down while a child session is live no longer leaves its extension host runningPiG's own defectAll platforms0.5.2
BF-648ff3e-65Extensions: in a packed Go, Rust or Python cell, a member that stops or is replaced no longer releases a file…PiG's own defectAll platforms0.5.2
BF-648ff3e-66Extensions: withFileMutationQueue is now one queue for the built-in edit and write tools and every…PiG's own defectAll platforms0.5.2
BF-648ff3e-67A Python extension's ctx.session_name and a Rust extension's ctx.session_name() returned an empty string…PiG's own defectAll platforms0.5.2
BF-648ff3e-68ctx.model is the model's id in every SDK, as in Pi (ctx.model.id)PiG's own defectAll platforms0.5.2
BF-648ff3e-69A read of the session name, the session log or the model right after setLabel, setSessionName…PiG's own defectAll platforms0.5.2
BF-648ff3e-7An edit or write call of a parallel batch registers in the file mutation queue after the extension tools…PiG's own defectAll platforms0.5.2
BF-648ff3e-70An extension that reads ctx.model, pi.getSettings(), pi.getSessionName(), pi.getThinkingLevel() or…PiG's own defectAll platforms0.5.2
BF-648ff3e-71In a Rust extension's before_agent_start handler, ctx.get_system_prompt() returns the prompt the…PiG's own defectAll platforms0.5.2
BF-648ff3e-72RPC mode no longer writes entry_appended for a pi.appendEntry or session_info_changed for a…PiG's own defectAll platforms0.5.2
BF-648ff3e-73Docs: another PiG build that shares the config root no longer removes the documentation bundle a running…PiG's own defectAll platforms0.5.2
BF-648ff3e-74RPC mode now writes the members of an extension_ui_request that Pi writes empty, as Pi's JSON.stringify…PiG's own defectAll platforms0.5.2
BF-648ff3e-75Starting a second Piglet Binary instance no longer fails on Windows while the first is runningPiG's own defectWindows0.5.2
BF-648ff3e-76RPC mode writes one setWidget request per ctx.ui.setWidget call from the Go, Python and Rust SDKs, in…PiG's own defectAll platforms0.5.2
BF-648ff3e-77In print and JSON mode, a /skill:name prompt whose skill file cannot be read now reports the skill's read…PiG's own defectAll platforms0.5.2
BF-648ff3e-78Images are now resized with the image profile of the model that answered last under a virtual model selectionPiG's own defectAll platforms0.5.2
BF-648ff3e-79The system prompt a run forced no longer shows while the bash and custom messages held during that run are…PiG's own defectAll platforms0.5.2
BF-648ff3e-8The Node runtime reports a tool whose execute awaits something that is not a host call, and the host closes…PiG's own defectAll platforms0.5.2
BF-648ff3e-80An extension that reads pi.getMcpServers() from a timer, thread or goroutine and has no…PiG's own defectAll platforms0.5.2
BF-648ff3e-81An extension that reads ctx.ui.theme from a timer, thread or goroutine now sees the theme the user selects…PiG's own defectAll platforms0.5.2
BF-648ff3e-82A Node extension that registers or unregisters a virtual model…PiG's own defectAll platforms0.5.2
BF-648ff3e-83CI: the nightly -race run now covers the packages that own most of PiG's goroutinesPiG's own defectAll platforms0.5.2
BF-648ff3e-84A pig session on another machine or container that shares your ~/.pig (an NFS home, a bind mount) is no…PiG's own defectAll platforms0.5.2
BF-648ff3e-85The upstream test inventory records Pi's shell-path-exists.test.ts as ported instead of partial: Pi mocks…PiG's own defectWindows0.5.2
BF-648ff3e-86Fixed a chord host root failing with superseded: code version N is not retained although it was started in…PiG's own defectAll platforms0.5.2
BF-648ff3e-87Print mode and --mode json no longer run an extension command a second time on text an input handler or a…PiG's own defectAll platforms0.5.2
BF-648ff3e-88Shutting down a host while a crashed extension waits out its restart backoff no longer leaves a goroutine…PiG's own defectAll platforms0.5.2
BF-648ff3e-89Forking a session from a file no longer overwrites a session file that already has the new session's…PiG's own defectAll platforms0.5.2
BF-648ff3e-9A Node extension tool that awaits the event loop before it takes the file mutation queue registers after the…PiG's own defectAll platforms0.5.2
BF-648ff3e-90Extension SDKs: ModelRegistry.find reports a host or transport failure instead of "no such model"PiG's own defectAll platforms0.5.2
BF-648ff3e-91Python SDK: has_configured_auth and is_using_oauth answer False for a model with no provider, as Pi and…PiG's own defectAll platforms0.5.2
BF-648ff3e-92Go SDK: Context.CancellationReason returns the reason the host gave when it cancelled the request, as the…PiG's own defectAll platforms0.5.2
BF-648ff3e-93A compiled-in extension factory, inline or built in, no longer panics "Extension runtime not initialized"…PiG's own defectAll platforms0.5.2
BF-648ff3e-94The interactive mode no longer rewrites every provider request's system prompt into one headPiG's own defectAll platforms0.5.2
BF-648ff3e-95Markdown bold and italic render next to CJK punctuation in the terminal, for example…PiG's own defectAll platforms0.5.2
BF-648ff3e-96A message sent with deliverAs: "nextTurn" while a run was starting could join that run instead of the next…PiG's own defectAll platforms0.5.2
BF-648ff3e-97Markdown bold and italic next to CJK punctuation render in HTML exports and in Markdown that Node extensions…PiG's own defectAll platforms0.5.2
BF-648ff3e-98An extension's UI closures now run only in the TUI, as in PiPiG's own defectAll platforms0.5.2
BF-648ff3e-99Interactive mode now routes a message an extension sends with sendMessage while a run streams as Pi doesPiG's own defectAll platforms0.5.2
BF-65426f3fix(rpc): pipelined set_models apply in input order, after the batch's synchronous responsesPiG's own defectAll platformsUnreleased
BF-65932e6fix(extensions): run a tick call after the tool_execution_end of the tool call that queued its callback…PiG's own defectAll platformsUnreleased
BF-65932e6-2A notification, status or message that an extension queued with setImmediate or setTimeout from a tool…PiG's own defectAll platforms0.5.2
BF-663e84bThe built-in models page lists the real thinking levels (off, minimal, low, medium, high, xhigh, max) instead…PiG's own defectAll platforms0.5.0
BF-663e84b-2The built-in models page documents the models.json provider and model fields (models[], contextWindow…PiG's own defectAll platforms0.5.0
BF-663e84b-3The built-in docs gain the compaction summary format, branch summaries and extension hooks, the RPC, JSON…PiG's own defectAll platforms0.5.0
BF-663e84b-4The packages page says how to build and run pig-acp, which npm:@pi-in-go/pigpen-acp needs but does not…PiG's own defectAll platforms0.5.0
BF-6646adcA durable transaction that settles a submission with a detail that is not strict JSON now counts as having…PiG's own defectAll platforms0.5.0
BF-666acffPackage filters, manifest !/+/- overrides, settings resource patterns and built-in extension overrides…PiG's own defectAll platforms0.5.0
BF-666acff-2A pattern that Pi cannot compile now fails where Pi fails: a local Package keeps only the resource kinds…PiG's own defectAll platforms0.5.0
BF-668b3faFix the shared attribute-order store case: advance the clock before flush and compare metric row names onlyPiG's own defectAll platformsUnreleased
BF-66b0fc0The Anthropic workload-identity token endpoint check parses the base URL with the WHATWG parser, as the…PiG's own defectAll platforms0.5.0
BF-66db311The login dialog now holds TUI focus while it is shown, so the prompt cursor appears in the right place for…PiG's own defectAll platforms0.5.0
BF-6708fbbWhen a Go, Rust or Python extension that shares a process registers under a different name than the one it…PiG's own defectAll platforms0.5.0
BF-677ded1An indented line after a quoted heading, rule or fence in a blockquote renders as the code block Pi showsPiG's own defectAll platforms0.5.0
BF-68a4ed2PageUp and PageDown move the selection by a page in selection lists and selectors (settings, model…PiG's own defectAll platforms0.5.0
BF-68a63f2A tool namespace registered with aliases: [] keeps the empty array in pi.getAllTools() and the replicated…PiG's own defectAll platforms0.5.0
BF-68b129d/export to a directory that cannot be created on a read-only file system (such as macOS's root) reports…PiG's own defectmacOS0.5.0
BF-68ceee2Tool-argument validation compares JSON values as JSON Schema does: a number is never equal to a stringPiG's own defectAll platforms0.5.0
BF-68d0628The Go, Rust and Python SDKs export Pi's getLanguageFromPathPiG's own defectAll platforms0.5.0
BF-68f8761insecure: true on a provider an extension registers with refreshModels now reaches the request transportPiG's own defectAll platforms0.5.0
BF-68f98d7Fixed the error message of a failed lazy API setup, such as a module load or auth failure, using its failure…PiG's own defectAll platforms0.5.0
BF-6992ffbA Go, Python or Rust extension that calls appendEntry in a handler that already read the session log now…PiG's own defectAll platforms0.5.0
BF-69b9811Two PiG builds that share a config root (two installed versions, or stock PiG beside a Piglet Binary) no…PiG's own defectAll platforms0.5.0
BF-69c175bfix(models): models.json entries keep exposed limits and cost; bound thinking-only messages in summaries; pin…PiG's own defectAll platformsUnreleased
BF-69f6341The Radius relay WebSocket URL is built with the WHATWG URL parser, as Pi's new URL does: gateway hosts are…PiG's own defectAll platforms0.5.0
BF-6a6fffaA session message whose integer member, such as timestamp, a usage count or durationMs, is spelled with a…PiG's own defectAll platforms0.5.0
BF-6a6fffa-2formatToolCallWithArgs against live Pi: new TestFormatToolCallWithArgsMatchesPi…PiG's own defectAll platforms0.5.0
BF-6a6fffa-3getTextOutput against live Pi: new TestGetTextOutputMatchesPi…PiG's own defectAll platforms0.5.0
BF-6a6fffa-4Live-Pi oracles for utils/headers.ts: TestHeadersToRecordMatchesPi (800 header sequences over case…PiG's own defectAll platforms0.5.0
BF-6a6fffa-5The Skills page said your own skills live in ~/.pig/skills, that a skill's text is added to the system…PiG's own defectAll platforms0.5.0
BF-6a6fffa-6Live-Pi oracle TestOpenAICompletionsCompatAndCacheOptionsPayloadMatchesPi…PiG's own defectAll platforms0.5.0
BF-6a6fffa-7The settings documentation now says that images.blockImages replaces each image sent to the model with…PiG's own defectAll platforms0.5.0
BF-6a6fffa-8In --mode json, SIGTERM or SIGHUP during a tool call now stops the output and the session file where Pi…PiG's own defectAll platforms0.5.0
BF-6a6fffa-9TestSettingsSelectorMatchesPi opens each row under the Kitty keyboard protocol with CSI-u keys; no…PiG's own defectAll platforms0.5.0
BF-6ac4c8cCtrl+V pastes clipboard text in TermuxA fix Pi shipped that PiG portedLinux, Android and Termux0.5.0
BF-6cb29f0Started from a directory reached through a symlink, pig install/remove/list/update resolve local…PiG's own defectAll platforms0.5.0
BF-6cb29f0-2Started from a directory reached through a symlink, /export <file>.jsonl and /bug now write and name…PiG's own defectAll platforms0.5.0
BF-6cb29f0-3Switching to or importing an empty session file from a directory reached through a symlink starts the session…PiG's own defectAll platforms0.5.0
BF-6ce1db1In print and JSON mode, SIGTERM and SIGHUP always leave their line in exit.logPiG's own defectAll platforms0.5.0
BF-6d0ec02Durable (Pi eb2fce43): a wait that closes a cycle through waits and held owners now fails the task's commit…PiG's own defectAll platforms0.5.0
BF-6d3cfc8A user_bash operations object from an extension is no longer released while a command is running through itPiG's own defectAll platforms0.5.2
BF-6d9b403The /config selector keeps the first-seen order of groups that tie on origin, scope and source (auto…PiG's own defectAll platforms0.5.0
BF-6debb12fix(train): gate repairs after merging train 3 and the ordering stackPiG's own defectAll platformsUnreleased
BF-6dfa6bfgo install github.com/MichaelKinsy/PiG/cmd/pig@<version> downloads a root module of 181 MB instead of 304 MBPiG's own defectAll platforms0.5.2
BF-6e6a0ddA session message with a member of another JSON type than Pi writes is now kept, as Pi keeps itPiG's own defectAll platforms0.5.0
BF-6ee6eaeA Session delivers each event to its listeners the way Pi's AgentSession doesPiG's own defectAll platforms0.5.0
BF-6f26a84Xiaomi MiMo models send only the thinking on/off toggle (no reasoning_effort) and expose only the off and…PiG's own defectAll platforms0.5.0
BF-6f4a82fValidation errors for propertyNames name the failing member in their path, contains reports only "must…PiG's own defectAll platforms0.5.0
BF-6f7daf1fix: clone Windows Git Packages with core.longpaths so a deep file:// source path fitsPiG's own defectWindowsUnreleased
BF-6fb1307openai-codex-responses reads instructions from the initial system message of the resolved transcript before…PiG's own defectAll platforms0.5.0
BF-70d51e5Fixed bash and PowerShell results losing Took after reloading a session, and the live Took including…A fix Pi shipped that PiG portedWindows0.5.0
BF-70d51e5-2Fixed the message-format page: it said PiG does not carry the nestedCalls record on tool result messages…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-70d8a41Keybinding conflict detection ignores user bindings for actions the table does not define, as Pi's…PiG's own defectAll platforms0.5.0
BF-70e1841/settings warnings submenu: a warning the settings never set shows as on and a toggle always reports the…PiG's own defectAll platforms0.5.0
BF-7177d5ffix(tui): card of an unregistered tool is Pi's formatToolExecutionPiG's own defectAll platformsUnreleased
BF-72e7ab2A tool card whose app.tools.expand key is unbound shows an empty key in "..PiG's own defectAll platforms0.5.0
BF-72e7ab2-2TestMcpManagerViewMatchesPi renders the /mcp manager footers under rebound, multi-key and unbound…PiG's own defectAll platforms0.5.0
BF-72e7ab2-3TestLlamaViewMatchesPi runs a quarter of its 700 scenarios under rebound, multi-key and unbound…PiG's own defectAll platforms0.5.0
BF-72e7ab2-4TestCodemodeCardsMatchPi and TestMcpToolCardsMatchPi repeat their collapsed probes under rebound…PiG's own defectAll platforms0.5.0
BF-72e7ab2-5TestBashExecutionComponentMatchesPi probes the collapsed expand hint under rebound, multi-key and unbound…PiG's own defectAll platforms0.5.0
BF-72e7ab2-6TestSummaryAndSkillMessageComponentsMatchPi probes the compaction, branch summary and skill messages under…PiG's own defectAll platforms0.5.0
BF-731474bcoding.DefineTool returns the tool definition it is given, as Pi's defineTool doesPiG's own defectAll platforms0.5.0
BF-731474b-2codemode.CodemodeJsonSchema names the raw JSON of a tool's JSON Schema document, as Pi's…PiG's own defectAll platforms0.5.0
BF-7375fe4After pig piglet update installs a new release of a pulled Piglet Binary, pig piglet list shows the…PiG's own defectAll platforms0.5.0
BF-73a09ebfix: the ANSI renderer that replaces a frontend surface draws on the options terminal, not the surface's…PiG's own defectAll platformsUnreleased
BF-73a6c8cOpenAI-compatible chat completions requests keep the tool-call ids of the same model's earlier calls as they…PiG's own defectAll platforms0.5.0
BF-73ef1eaReduced context-limit request failures by estimating input at 3.5 characters per token instead of 4 when…PiG's own defectAll platforms0.5.0
BF-7472171An extension dialog (select, confirm, input, editor) that opens while another is shown now replaces…PiG's own defectAll platforms0.5.0
BF-7489735pig -e <directory> now loads a Node extension whose entry file is index.mjs and that has no…PiG's own defectAll platforms0.5.0
BF-74bc69bfix(rpc): every Session replacement aborts the compactions of its batch, and an abort before the request…PiG's own defectAll platformsUnreleased
BF-74e801fFix in shipped codePiG's own defectAll platformsUnreleased
BF-74f806cfix(agent): end the tool flow at custom user messages after /tree navigation (#203)PiG's own defectAll platformsUnreleased
BF-74fb98cMarkdown list items lex their text line by line like Pi: an HTML block starts at any item line, and an item…PiG's own defectAll platforms0.5.0
BF-75426aaThe /login and /logout provider picker and the /tree selector show the terminal cursor in their search…PiG's own defectAll platforms0.5.0
BF-75426aa-2A Node extension's tool_call handler can edit event.input in place and the tool runs with the edit, as in…PiG's own defectAll platforms0.5.0
BF-756cac8A streaming run no longer hangs, rarely and mostly under load, when a response turn resumes just as the drain…PiG's own defectAll platforms0.5.0
BF-75c6ccbfix(codingagent): context edits leave system messages unchangedPiG's own defectAll platformsUnreleased
BF-7633906On arm64, tool_search and searchTools() scores use the same natural logarithm as Pi running on Node for…PiG's own defectAll platforms0.5.0
BF-7662334An extension that writes a large amount to stderr no longer grows its diagnostic log without limitPiG's own defectAll platforms0.5.0
BF-773a868RPC mode no longer hangs when a Node extension command calls pi.sendMessage(..., { triggerTurn: true })…PiG's own defectAll platforms0.5.0
BF-773a868-2RPC mode no longer hangs when a Node extension command calls pi.appendEntry and then pi.setSessionName…PiG's own defectAll platforms0.5.0
BF-7768beafix(ai): tool-call arguments are written as JSON.stringify writes them, and keep a lone surrogatePiG's own defectAll platformsUnreleased
BF-779318aOpenAI-compatible streams where one chunk carries reasoning and content (LiteLLM and similar proxies) now…PiG's own defectAll platforms0.5.0
BF-78c4c6cpig piglet add now names the offending bundled path and where it came from (for example `Package herdr…PiG's own defectAll platforms0.5.0
BF-78f2d55pig mcp login --help described --timeout as only the wait for the browser; it limits the whole sign-in…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-79eadcfLong sessions no longer slow down each streamed frame in the main screen: a frame redraws from the first line…PiG's own defectAll platforms0.5.0
BF-79f9a9efix(agent): convert a live partial with one clone and keep events at an unchanged publication (TU-T7 rest)PiG's own defectAll platformsUnreleased
BF-7a4a8e8fix duplicate jsstring import in tui/assistant_message_block.goPiG's own defectAll platformsUnreleased
BF-7b0f5d7A native extension provider's filterAllModels receives the models its getAllModels (or getModels)…PiG's own defectAll platforms0.5.0
BF-7b59fe2pig piglet build --format binary from a Pig source directory that is not a Git checkout (for example…PiG's own defectAll platforms0.5.0
BF-7b92429find and grep trim like JavaScript: FindTool trimmed each fd output line with strings.TrimSpace where…PiG's own defectAll platforms0.5.0
BF-7ba3211A resumed session no longer holds its whole file in memoryPiG's own defectmacOS0.5.0
BF-7c59949fix(codingagent): session selector keeps Pi's out-of-range selected indexPiG's own defectAll platformsUnreleased
BF-7cca5eeA Node extension can default-import TypeBox (import Type from "typebox", and likewise typebox/value…PiG's own defectAll platforms0.5.0
BF-7d0621ffix(extension): keep Pi's three-parameter EmitBeforeAgentStart (prompt, images, options); a custom run's…PiG's own defectAll platformsUnreleased
BF-7dc9b09-e <local Package> now contributes its prompts, skills and themes, loaded before the project, user and…PiG's own defectAll platforms0.5.0
BF-7dc9b09-2--prompt-template and --theme paths are now merged after the project, user and Package resources, as in…PiG's own defectAll platforms0.5.0
BF-7e09ec0fix(codingagent): the idle collection runs one forced GC cycle (D172)PiG's own defectAll platformsUnreleased
BF-7e1a05cResource resolution follows Pi's DefaultPackageManager.resolve in two cases: built-in extensions come after…PiG's own defectAll platforms0.5.0
BF-7ea21f3RPC output writes summed usage (a tool result's usage with nested model calls, a compaction summary's usage)…PiG's own defectAll platforms0.5.0
BF-7f3a71cfix(durable): the reference Harness wraps a plain Models once tooPiG's own defectAll platformsUnreleased
BF-7f441d7MCP OAuth URLs are WHATWG URLs, as in PiPiG's own defectAll platforms0.5.0
BF-7fc4ef4fix(coding): shut down the extension host of a DefaultResourceLoader an extension constructed when its last…PiG's own defectAll platformsUnreleased
BF-8001cc5SettingsManager collection getters (getPackages, getExtensionPaths, getSkillPaths, getPromptTemplatePaths…PiG's own defectAll platforms0.5.0
BF-8001cc5-2SettingsManager.getCompactionSettings (+ getCompactionReserveTokens, getCompactionKeepRecentTokens)…PiG's own defectAll platforms0.5.0
BF-801caddfix(codingagent): session files decode invalid UTF-8 as Node doesPiG's own defectAll platformsUnreleased
BF-8048c85Tool-call header argument preview no longer splits a character: truncateArg (tui/tool_execution.go) cut…PiG's own defectAll platforms0.5.0
BF-8070fccOpenAI Codex and Azure OpenAI requests parse their URL as fetch does again: an upper-case scheme, a space or…PiG's own defectAll platforms0.5.0
BF-8085dc0A legacy oauth.json or settings.json that starts with a byte order mark migrates to auth.json, as in PiPiG's own defectAll platforms0.5.0
BF-80970daThe Go SDK page now says that leaving AgentDir empty in CreateAgentSessionServicesOptions makes a program…PiG's own defectAll platforms0.5.0
BF-80b912aA failed install-receipt write during pig update or install, such as one rejected because the update CA…PiG's own defectWindowsUnreleased
BF-80bb522Compacting a history longer than the model's context window no longer fails with the provider's "prompt is…PiG's own defectAll platforms0.5.0
BF-80bb522-2Gemini 2.x models (Google Generative AI and Vertex) no longer reject a conversation in which a parallel tool…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-80bb522-3Amazon Bedrock Converse requests merge adjacent messages that have the same roleA Pi defect that PiG fixed firstAll platforms0.5.0
BF-80c797dText that holds ill-formed UTF-8 is written with one U+FFFD for each ill-formed sequence, as Node decodes it…PiG's own defectAll platforms0.5.0
BF-81e584banthropic-messages payload now matches Pi for four cases found by the new live-Pi oracle…PiG's own defectAll platforms0.5.0
BF-81e584b-2Live-Pi oracle TestMistralMessagesPayloadMatchesPi (ai/mistral_messages_payload_oracle_test.go…PiG's own defectAll platforms0.5.0
BF-8241433A Mistral stream chunk with a member of an unexpected type is read as Pi's JavaScript reads it instead of…PiG's own defectAll platforms0.5.0
BF-82d1808Subprocess extensions no longer slow down each streamed token in long sessions: the state sent before each…PiG's own defectAll platforms0.5.0
BF-83d5488pig install <dir> of a local directory with no package manifest and no extensions/, skills/, prompts/…PiG's own defectAll platforms0.5.0
BF-83fea11On Windows, a refused connection reads as Node's connect ECONNREFUSED and is not retried, as in Pi; it was…PiG's own defectWindows0.5.0
BF-844d215Repairing a partial tool-call arguments text follows partial-json's obj["__proto__"] = value: a…PiG's own defectAll platforms0.5.0
BF-848836afix(frontmatter): header starts one UTF-16 unit after the fencePiG's own defectAll platformsUnreleased
BF-852652ePiG no longer names documentation that does not existPiG's own defectAll platforms0.5.0
BF-852f029Durable document, conversation-view and task-graph states report failing state listeners to the Session's…PiG's own defectAll platforms0.5.0
BF-85618d9fix(rpc): a failed switch_session answers before the replacement ahead of it; prompt compaction checks…PiG's own defectAll platformsUnreleased
BF-85908aeA /tree branch summary the provider refuses as too long is retried with the newest half of the request…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-85a7b01A prompt now fails with Pi's error when the Session's history holds a system message whose content is a…PiG's own defectAll platforms0.5.0
BF-85c98bedurable/interop/bench.mjs and sqlhost-bench seed\|sample report the process's own peak RSS on Linux…PiG's own defectLinux0.5.0
BF-85ca0f5Rebuilding the transcript (resume, /tree, display settings) now places cache-miss notices as Pi does in a…PiG's own defectAll platforms0.5.0
BF-860776aA Google provider built from a model id without catalog metadata now sends its own earlier thinking blocks as…PiG's own defectAll platforms0.5.0
BF-86364b4fix(minimatch): case-insensitive patterns compare as a JavaScript RegExp doesPiG's own defectAll platformsUnreleased
BF-866c7f9fix(rpc): two prompts of one batch answer in request orderPiG's own defectAll platformsUnreleased
BF-869cef8A pi-messages stream event that is a number, string, boolean or array is skipped, as Pi skips an event of…PiG's own defectAll platforms0.5.0
BF-86c411afix(builtin): codemode and tool_search declare their parameters in TypeBox key orderPiG's own defectAll platformsUnreleased
BF-871b48dpig install <source> --validate-only now validates git and npm sources that are not installed yetPiG's own defectAll platforms0.5.3
BF-87a6ac5Fix in shipped codePiG's own defectAll platformsUnreleased
BF-88177e8fix(cli): type the validate-only scratch-fetch marker as D28 and extend the recordPiG's own defectAll platformsUnreleased
BF-8894a13A double-quoted YAML string in a skill, prompt template or parseFrontmatter header now reads a…PiG's own defectAll platforms0.5.0
BF-8894a13-2The liveness conformance harness answers the host-parser helper calls a factory makes before the register…PiG's own defectAll platforms0.5.0
BF-893ff97A read call whose offset or limit arrives as a string shows the right line range (:25-37, not…PiG's own defectAll platforms0.5.0
BF-89d167fFix in shipped codePiG's own defectAll platformsUnreleased
BF-8a18a6bA Node extension no longer keeps reading an entry the host refusedPiG's own defectAll platforms0.5.2
BF-8a25539llama-cpp-classify answers match Pi bit for bit: the softmax uses jsmath.Exp, V8's fdlibm Math.exp…PiG's own defectAll platforms0.5.0
BF-8a88c34On Windows, a custom theme edited in a rapid burst of writes no longer stays on a stale versionPiG's own defectWindowsUnreleased
BF-8a9bb40fix(ai): Anthropic retry-delay message and Codex error body without an error memberPiG's own defectAll platformsUnreleased
BF-8ab5a89A long session no longer holds each tool result twicePiG's own defectAll platforms0.5.0
BF-8ab86c8RPC get_state sent right after new_session, fork, clone, switch_session or export_html now…PiG's own defectAll platforms0.5.0
BF-8b75dc0Config selector (pig config): every key that reaches the search input re-filters and resets the selection…PiG's own defectAll platforms0.5.0
BF-8b8f379A pi-ai call made while a module loads is answered in every extension host: SDK sessions, the extension…PiG's own defectAll platforms0.5.0
BF-8b9e467PIG_PROFILE=metrics reports the exact cumulative allocation countsPiG's own defectAll platforms0.5.0
BF-8bc7c8fFix in shipped codePiG's own defectAll platformsUnreleased
BF-8c3f5a1fix(tui): a finished block's hidden thinking run keeps no streaming memosPiG's own defectAll platformsUnreleased
BF-8c5a7a4The Go, Python and Rust extension SDKs' serializeConversation (SerializeConversation…PiG's own defectAll platforms0.5.0
BF-8c7bacfdelta.AssertValidOp checks path safety as Pi's assertValidOp does: it rejects a negative, fractional…PiG's own defectAll platforms0.5.0
BF-8c7bacf-2delta.ApplyImmutable sets a numeric path segment on an object as that key, as Pi doesPiG's own defectAll platforms0.5.0
BF-8c7bacf-3PathError and UnsafePathError messages print paths as JSON.stringify does: <, >, &, U+2028 and…PiG's own defectAll platforms0.5.0
BF-8d008ebfix(interactive): paint the requested first frame before the color wait returnsPiG's own defectAll platformsUnreleased
BF-8d23cfcSaving a setting keeps the key order of the existing settings.json, at the top level and inside a patched…PiG's own defectAll platforms0.5.0
BF-8eb1202An OpenAI Chat Completions chunk with a member of an unexpected type (a number for delta.content…PiG's own defectAll platforms0.5.0
BF-8f45099Compaction summaries, branch summaries and bug reports omit thinking blocks (Pi 15e7bdc1, Pi #9602): replayed…PiG's own defectAll platforms0.5.0
BF-8f88d43On Windows, an extension's exec no longer returns no output for a command that printed, when the machine is…PiG's own defectWindowsUnreleased
BF-905f3d0A trap of the codemode module on a Worker no longer loses the output the script printed before it: the host…PiG's own defectAll platforms0.5.0
BF-9088293The Azure OpenAI base URL is parsed and serialized with the WHATWG URL parser, as Pi does: hosts are…PiG's own defectAll platforms0.5.0
BF-909581eThe terminal no longer stays in mouse-tracking, focus-reporting, Kitty keyboard, bracketed-paste or…A fix Pi shipped that PiG portedAll platforms0.5.2
BF-909a2e2MCP OAuth metadata documents with a member whose name differs from a modeled one only in case (for example…PiG's own defectAll platforms0.5.0
BF-90c1f0fFix in shipped codePiG's own defectAll platformsUnreleased
BF-9101bc8Bedrock model matching follows Pi: separator runs in a model id or display name become a single - (so "Opus…PiG's own defectAll platforms0.5.0
BF-918f21cformatThousands (tui/compaction_summary.go, compaction-summary-message.ts:40…PiG's own defectAll platforms0.5.0
BF-919575aA JSON syntax error reports what V8's JSON.parse reports in two cases it got wrong: a number or string…PiG's own defectAll platforms0.5.0
BF-91e912aTwo PiG versions that share a config root (two installed versions, or PiG beside a Piglet Binary) no longer…PiG's own defectAll platforms0.5.0
BF-91f9946OpenRouter image generation reads the chat completions body as Pi does, checked against the pinned Pi in a…PiG's own defectAll platforms0.5.0
BF-928e15cA listener that fails while agent_start is dispatched now ends the run as in Pi (failure messages, no…PiG's own defectAll platforms0.5.0
BF-928e15c-10Constrained-sampling errors quote tool and property names as Pi does (raw text between double quotes, not…PiG's own defectAll platforms0.5.0
BF-928e15c-11GitHub Copilot model-catalog fetches now report a failed response as Pi does (`<status> <reason>: <untrimmed…PiG's own defectAll platforms0.5.0
BF-928e15c-12GitHub Copilot login now reports failed HTTP responses as Pi does (<status> <reason>: <body>, not `HTTP…PiG's own defectAll platforms0.5.0
BF-928e15c-13GitHub Copilot, ChatGPT and OpenAI Codex logins report what Pi reports when a server's response cannot be…PiG's own defectAll platforms0.5.0
BF-928e15c-14GitHub Copilot login sends the browser to the verification URL exactly as Pi does (new URL(...).href: the…PiG's own defectAll platforms0.5.0
BF-928e15c-15Device-code login polling now does its time arithmetic in floating-point milliseconds as Pi does, so a NaN…PiG's own defectAll platforms0.5.0
BF-928e15c-16A durable directory reader now refuses a maxEntries above Number.MAX_SAFE_INTEGER with an invalid…PiG's own defectAll platforms0.5.0
BF-928e15c-17Durable entries keep a present empty edits list ("edits":[]) when they are appended, stored and read…PiG's own defectAll platforms0.5.0
BF-928e15c-18Durable: a JSONL store whose task or submission record carries a status outside the closed set (a damaged…PiG's own defectAll platforms0.5.0
BF-928e15c-19Fixed Editor.SetText keeping the previous scroll offset: as in Pi, replacing the text puts the view back at…PiG's own defectAll platforms0.5.0
BF-928e15c-2A stored models catalog whose contextWindow or maxTokens is written as 128000.0 or 1.28E5 loads like…PiG's own defectAll platforms0.5.0
BF-928e15c-20Fixed frontmatter parsing when the opening --- is followed by a multi-byte character (for example U+0085 or…PiG's own defectAll platforms0.5.0
BF-928e15c-21Google streams replace an ill-formed UTF-8 sequence with one U+FFFD per maximal subpart (the WHATWG…PiG's own defectAll platforms0.5.0
BF-928e15c-22Case-insensitive matching of tool names (Claude Code tool casing), the anthropic-beta, reserved Bedrock and…PiG's own defectAll platforms0.5.0
BF-928e15c-23Kimi Code OAuth login and refresh now read the server's response the way Pi does: a body that is not a JSON…PiG's own defectAll platforms0.5.0
BF-928e15c-24Kimi Code and xAI OAuth decode a response body as fetch does: ill-formed UTF-8 becomes one U+FFFD per maximal…PiG's own defectAll platforms0.5.0
BF-928e15c-25A tool or command that throws an Error with an empty message reports an empty message in the Node runtime…PiG's own defectAll platforms0.5.0
BF-928e15c-26The Rust SDK parses every JSON number as the nearest double, as JSON.parse does; it could land one unit in…PiG's own defectAll platforms0.5.0
BF-928e15c-27The Go SDK writes NaN and infinities as null (and negative zero as 0) in the frames it sends, as…PiG's own defectAll platforms0.5.0
BF-928e15c-28The Node SDK's messageRole now returns "" for a message whose role is not a string, as the Go, Rust and…PiG's own defectAll platforms0.5.0
BF-928e15c-29The Go and Rust SDKs now answer a tool result past the 128 MiB frame limit with a frame too large error, as…PiG's own defectAll platforms0.5.0
BF-928e15c-3Provider HTTP error bodies are decoded like response.text(): ill-formed UTF-8 becomes one U+FFFD per…PiG's own defectAll platforms0.5.0
BF-928e15c-30The Python SDK sends NaN and infinities as null (as JSON.stringify does) instead of the bare words…PiG's own defectAll platforms0.5.0
BF-928e15c-31The Rust SDK decodes tool arguments, notifications and host-call results nested past 128 levels (up to 4096)…PiG's own defectAll platforms0.5.0
BF-928e15c-32The Rust SDK no longer drops its host connection when a frame (a tool argument, event payload or object key)…PiG's own defectAll platforms0.5.0
BF-928e15c-33The login dialog renders its one input at every prompt position until a submit replaces them with the…PiG's own defectAll platforms0.5.0
BF-928e15c-34Markdown: a rule written with the list's own marker (* * *) after a blank line ends the list with its blank…PiG's own defectAll platforms0.5.0
BF-928e15c-35Markdown: a list item line that is a rule (- a, a blank line, - - -; or - * * *) renders as a rule…PiG's own defectAll platforms0.5.0
BF-928e15c-36Markdown: an indented line that a table delimiter row (---) follows joins the paragraph before it with its…PiG's own defectAll platforms0.5.0
BF-928e15c-37Markdown: a text that ends in one line of spaces after an indented code block, an HTML block or a table no…PiG's own defectAll platforms0.5.0
BF-928e15c-38Markdown no longer adds a blank row after a quote or block when only a repeated link definition follows it…PiG's own defectAll platforms0.5.0
BF-928e15c-39Markdown: a line that continues a nested list item's paragraph (- a, - b, x) renders at the nested…PiG's own defectAll platforms0.5.0
BF-928e15c-4A Gemini (or Bedrock/native) response streamed in many small chunks no longer hangs the stream…PiG's own defectAll platforms0.5.0
BF-928e15c-40Markdown no longer adds an empty row after a table that is too narrow to draw, when the table is inside a…PiG's own defectAll platforms0.5.0
BF-928e15c-41Markdown: list items whose markers are indented by different amounts (0 to 3 spaces) form one list, so a…PiG's own defectAll platforms0.5.0
BF-928e15c-42Markdown rendering shows a horizontal rule (- - -, * * *) inside a nested list item as a rule, as Pi…PiG's own defectAll platforms0.5.0
BF-928e15c-43Markdown nested lists treat a less-indented continuation line the way Pi does: a line indented to a nested…PiG's own defectAll platforms0.5.0
BF-928e15c-44Markdown: a blockquote whose last quoted line holds only spaces or tabs (> q, >, x) now renders the…PiG's own defectAll platforms0.5.0
BF-928e15c-45Markdown task lists whose later item is a heading (- [ ] b over ===) keep and strip checkbox markers on…PiG's own defectAll platforms0.5.0
BF-928e15c-46MCP HTTP servers and the OAuth requests of a sign-in now report a connection that fails before any response…PiG's own defectAll platforms0.5.0
BF-928e15c-47Meta OAuth login now reads the server's responses as Pi does: an invalid device-authorization response is…PiG's own defectAll platforms0.5.0
BF-928e15c-48Case-insensitive glob matching compares as a JavaScript regular expression does: İ no longer matches i…PiG's own defectAll platforms0.5.0
BF-928e15c-49OpenAI ChatGPT and Codex OAuth token failures with an empty body report the reason phrase the server sent…PiG's own defectAll platforms0.5.0
BF-928e15c-5Sign in with ChatGPT now reads the token endpoint's responses as Pi does: a failed response quotes the body…PiG's own defectAll platforms0.5.0
BF-928e15c-50OpenRouter OAuth key exchange now ignores a leading byte order mark in the response body, as Pi's…PiG's own defectAll platforms0.5.0
BF-928e15c-51Fullscreen double-click selects the word Pi selects: words come from the same ICU segmentation as…PiG's own defectAll platforms0.5.0
BF-928e15c-52Anthropic: a server-requested retry delay above the cap fails with `Server requested Ns retry delay (max…PiG's own defectAll platforms0.5.0
BF-928e15c-53OpenAI Codex: an error body whose error member is absent or falsy (an object without it, an array, null)…PiG's own defectAll platforms0.5.0
BF-928e15c-54Provider streams read integers written as 12.0, 1.2e1 or 12E0 (a token count, an index) as JavaScript's…PiG's own defectAll platforms0.5.0
BF-928e15c-55Radius OAuth login now reads the gateway's responses as Pi does: an error body that is a JSON string, number…PiG's own defectAll platforms0.5.0
BF-928e15c-56Radius OAuth login again signs in when the gateway's token response has no refresh token, as Pi doesPiG's own defectAll platforms0.5.0
BF-928e15c-57A provider request whose URL path Go cannot parse no longer sends the URL fragment to the server; fetch never…PiG's own defectAll platforms0.5.0
BF-928e15c-58The Anthropic stream reader takes an event's type only from a member named exactly type, as Pi does; a…PiG's own defectAll platforms0.5.0
BF-928e15c-59A provider HTTP error body that starts with two byte order marks reads as response.text() reads it: both…PiG's own defectAll platforms0.5.0
BF-928e15c-6The codemode-store session entry lists the keys of its set object as Pi does (array-index keys ascending…PiG's own defectAll platforms0.5.0
BF-928e15c-60A models.json whose contextWindow, maxTokens or a model override writes an integer as 128000.0 or…PiG's own defectAll platforms0.5.0
BF-928e15c-61Classifier (typesafe-system-one, llama.cpp) and Radius gateway config responses are decoded as…PiG's own defectAll platforms0.5.0
BF-928e15c-62Amazon Bedrock streams read integers written as 12.0 or 1e0 (a content block index, a token count) as the…PiG's own defectAll platforms0.5.0
BF-928e15c-63An OpenAI Codex HTTP error with an empty body reports the reason phrase the server sent, or "Request failed"…PiG's own defectAll platforms0.5.0
BF-928e15c-64Print mode (-p) now prints the partial text, writes a warning to stderr and exits 1 when the final response…PiG's own defectAll platforms0.5.0
BF-928e15c-65Delayed terminal replies (OSC color, XTVersion, Kitty graphics) that arrive in pieces are no longer typed…PiG's own defectAll platforms0.5.0
BF-928e15c-66In the alternate screen, wheel input that a focused overlay does not handle scrolls the transcript (Pi 1.2.0…PiG's own defectAll platforms0.5.0
BF-928e15c-67A session file with invalid UTF-8 inside a record loads with one U+FFFD per maximal invalid subpart, as…PiG's own defectAll platforms0.5.0
BF-928e15c-68A system message's sections object now enumerates integer-like section names first, in ascending order…PiG's own defectAll platforms0.5.0
BF-928e15c-69A configured thinking budget of zero (or below) now wins over the default in the OpenAI-compatible, Google…PiG's own defectAll platforms0.5.0
BF-928e15c-7OpenAI Codex device-code login now reads the server's responses the way Pi does: a field of an unexpected…PiG's own defectAll platforms0.5.0
BF-928e15c-70A tool call whose arguments hold a key with an escaped lone surrogate ("\ud800a") keeps that key's place in…PiG's own defectAll platforms0.5.0
BF-928e15c-71The agent loop now gives a custom stream function Pi's messages (errored and aborted turns kept, no synthetic…PiG's own defectAll platforms0.5.0
BF-928e15c-72A real provider's tool-flow repair (D48) no longer moves a tool result back to an earlier unanswered call…PiG's own defectAll platforms0.5.0
BF-928e15c-73Fixed the session tree selector keeping its cursor inside the rowsPiG's own defectAll platforms0.5.0
BF-928e15c-74SettingsList draws Pi's bytes: its scroll indicator, description and hint rows are no longer padded to the…PiG's own defectAll platforms0.5.0
BF-928e15c-75A ! command block draws Pi's bytes: two consecutive blocks are separated by one blank row instead of two…PiG's own defectAll platforms0.5.0
BF-928e15c-76A provider defined only by virtual models reports configured with source environment and label virtual…PiG's own defectAll platforms0.5.0
BF-928e15c-77Provider order puts a provider of only virtual models where Pi's providerIds does (the place of its…PiG's own defectAll platforms0.5.0
BF-928e15c-78xAI OAuth login and refresh now read the server's response the way Pi does: a body that is not a JSON object…PiG's own defectAll platforms0.5.0
BF-928e15c-8OpenAI Codex and OpenRouter OAuth responses read ill-formed UTF-8 and a body that fails to read as fetch…PiG's own defectAll platforms0.5.0
BF-928e15c-9An OpenAI Codex login or refresh whose success response is not JSON reports JavaScript's own message, as Pi…PiG's own defectAll platforms0.5.0
BF-92da987Codemode no longer reserves its whole 256 MiB script-memory limit when a session starts: the QuickJS linear…PiG's own defectAll platforms0.5.0
BF-93319f4The terminal UI worked example now describes the typed /model prefix in wordsPiG's own defectAll platforms0.5.0
BF-93c09d6A local package source that is a file Node cannot import, such as a packed .tgz, fails to load as in Pi…PiG's own defectAll platforms0.5.0
BF-93c09d6-2pig install --validate-only on a Package root validates every extension the Package names in…PiG's own defectAll platforms0.5.0
BF-93c5defA built-in extension named with -e builtin:<name> loads before the extensions from settings and the…PiG's own defectAll platforms0.5.0
BF-93f8b7cfix(agent): a listener failure at agent_start ends the run as in Pi; failure messages carry the model's apiPiG's own defectAll platformsUnreleased
BF-94e3498On Windows, PiG no longer searches C:\etc\pig\piglets or C:\opt\pig\piglet.yaml for a PigletPiG's own defectWindows0.5.0
BF-94e6796A Durable Harness task that went to sleep for a short time, such as the 1 ms backoff before a generation…PiG's own defectAll platforms0.5.2
BF-94f1e6ffix(coding): a provider of only virtual models is configured with source virtualPiG's own defectAll platformsUnreleased
BF-9515bcbCode mode: a deterministic run's clocks round like Math.round (0.49999999999999994 ms read 1 ms), and a…PiG's own defectAll platforms0.5.0
BF-958e80dA tool call whose arguments hold a number out of float64 range (1e999, -1e999) no longer ends the session…PiG's own defectAll platformsUnreleased
BF-95a5f79RPC mode starts the --tools list active in its own order when it names extension tools, as Pi doesPiG's own defectAll platforms0.5.0
BF-95eefe2openai-completions thinking formats now match Pi (ai/openai.go, openai-completions.ts:885-985 and…PiG's own defectAll platforms0.5.0
BF-9721c48An auto, manual or overflow compaction status shown in the editor's top border now takes the editor border…PiG's own defectAll platforms0.5.0
BF-9740507pig extension upgrade now rewrites a Context.ConfigHome() call in an if or switch init statement and…PiG's own defectAll platforms0.5.0
BF-9740507-2pig extension upgrade no longer discards the error of Context.ConfigHome with `home, _ :=…PiG's own defectAll platforms0.5.0
BF-9740507-3pig extension upgrade is all or nothingPiG's own defectAll platforms0.5.0
BF-976af87The documentation said PiG starts without a first-time setup screen and that Stock PiG has no built-in spritesPiG's own defectAll platforms0.5.0
BF-97cd675fix(agent): nested records keep a message whatever the spelling of its numbersPiG's own defectAll platformsUnreleased
BF-97e178fTool-call arguments that arrive as incomplete or malformed JSON are now recovered exactly as Pi's…PiG's own defectAll platforms0.5.0
BF-983d651The bash truncation notice for a last line over 50 KB that ends with a newline reports the line's size…PiG's own defectAll platforms0.5.0
BF-985b5f9fix(coding): a resumed session takes its active tools from the transcript (pi-mono #10785) [SIGNOFF-PENDING]PiG's own defectAll platformsUnreleased
BF-992f3cbA before_provider_request replacement that is not an object leaves Anthropic Messages as Pi's `{ ...value…PiG's own defectAll platforms0.5.0
BF-99a06cfInteractive startup paints the editor and footer first and adds the startup header once the terminal reports…PiG's own defectAll platforms0.5.0
BF-9af407cfix(ai): race builds read provider sockets through syscall.ReadPiG's own defectAll platformsUnreleased
BF-9b4312bA Rust extension build that ends without a compiler diagnostic (cargo or rustc killed, cargo unable to start)…PiG's own defectAll platforms0.5.0
BF-9b4312b-2The Rust build failure test asserts that a killed rustc is named by its program name, not its full pathPiG's own defectAll platforms0.5.0
BF-9b63b2bOn Windows, the bash tool no longer risks waiting forever after a command exits while a process it started in…PiG's own defectWindows0.5.0
BF-9b63b2b-2A running ! command or bash tool command no longer outlives pig when the session ends because its…PiG's own defectAll platforms0.5.0
BF-9ba2589fix(agent): write a decoded message as the object it was decoded fromPiG's own defectAll platformsUnreleased
BF-9be5765Pressing Escape while an extension tool runs now lets the tool finish: a tool that stops on its abort signal…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-9c1a0e9An OpenAI Responses stream event or output item with a member of an unexpected type is read as Pi's…PiG's own defectAll platforms0.5.0
BF-9c98038An unclosed $$ or \[ display-math block no longer swallows or corrupts every Markdown table after itA fix Pi shipped that PiG portedAll platforms0.5.2
BF-9d4fe9cSelecting a model or cycling models with Ctrl+P no longer freezes the interactive mode when an extension's…PiG's own defectAll platforms0.5.0
BF-9d64980fix(coding): provider order and availability of virtual models follow PiPiG's own defectAll platformsUnreleased
BF-9f4702cA failed extension shortcut handler is now reported as Error: Shortcut handler error: … in the error…PiG's own defectAll platforms0.5.0
BF-D110Git sources: #subdirectory= selects a subdirectory; git:// and upper-case schemes yield usable…A Pi defect that PiG fixed firstAll platformsUnreleased
BF-D150--no-stdin skips the startup read of piped stdin (Pi #10415)A Pi defect that PiG fixed firstAll platformsUnreleased
BF-D151grep with context reads a window of each matched file (Pi #9276)A Pi defect that PiG fixed firstAll platformsUnreleased
BF-D153The bash truncation notice names the size of the last line, not the bytes after the final newline (Pi #10015)A Pi defect that PiG fixed firstAll platformsUnreleased
BF-D155Selection lists page with PageUp and PageDown (Pi #7629)A Pi defect that PiG fixed firstAll platformsUnreleased
BF-D156MCP server url expands environment references (Pi #10654)A Pi defect that PiG fixed firstAll platformsUnreleased
BF-D195An empty AWS_BEARER_TOKEN_BEDROCK is no token, where Pi's AWS SDK treats the variable as set and the request…A Pi defect that PiG fixed firstAll platformsUnreleased
BF-D196Under skip-auth a bearer token from the process environment is not sent over an http:// endpoint, where Pi's…A Pi defect that PiG fixed firstAll platformsUnreleased
BF-D198The Go, Rust and Python Type has no Codec family (Codec, Decode, Encode, DecodeBuilder…A Pi defect that PiG fixed firstAll platformsUnreleased
BF-D200With no home directory PiG still runs the commands that load no resources, where Pi crashes at import, and…A Pi defect that PiG fixed firstAll platformsUnreleased
BF-D203registerProvider with an empty apiKey leaves the provider unconfigured where Pi treats the empty string as…A Pi defect that PiG fixed firstAll platformsUnreleased
BF-a076a50A model request on a reused connection gets the full HTTP idle timeout to receive its first response bytes…PiG's own defectAll platforms0.5.0
BF-a09da26pi.getSettings() in a subprocess extension lists the settings in Pi's order: the global settings file's…PiG's own defectAll platforms0.5.0
BF-a09da26-2A bash or PowerShell tool card reads the result's truncation details by their exact keys, as Pi does: a…PiG's own defectWindows0.5.0
BF-a110f68fix(tui,codingagent): owner-loop frame deadline and reader-side input routing (TU-T11, TU-T12)PiG's own defectAll platformsUnreleased
BF-a13bae4A read result shown in the transcript no longer keeps the file text a second time as its structured contentPiG's own defectAll platforms0.5.0
BF-a156ef3On Linux, clipboard reads accept every DISPLAY spelling Pi's xcb_connect accepts: white space or a sign…PiG's own defectLinux0.5.0
BF-a178939The Go and Python serializeConversation keep an unpaired UTF-16 surrogate in a message, a tool-call name or…PiG's own defectAll platforms0.5.0
BF-a1fa6d1NO_PROXY entries and ports are trimmed with JavaScript's whitespace rules, as in Pi: an entry or port that…PiG's own defectAll platforms0.5.0
BF-a238095coding: fix session executor queue-prefix race and durable execMu waitsPiG's own defectAll platformsUnreleased
BF-a3859aaAn OpenAI-compatible provider that streams a tool call without an id or a function name (OpenRouter and GLM…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-a3bfab2A Radius gateway config cached on an OAuth credential is sanitized as Pi doesPiG's own defectAll platforms0.5.0
BF-a40a16dfix(codingagent): typed input restarts the idle collection's wait on every routePiG's own defectAll platformsUnreleased
BF-a46f930A Package configured as a settings object reads a resource kind its pi manifest does not declare (or…PiG's own defectAll platforms0.5.0
BF-a4753ecA panic in a background goroutine of an interactive session no longer ends PiG with only a stderr dumpPiG's own defectAll platforms0.5.0
BF-a4753ec-2The prompt-cache refresh that fires minutes after a prompt now treats a panic in the extension decision or in…PiG's own defectAll platforms0.5.0
BF-a4753ec-3When the terminal's input ends while its output is still there, PiG keeps running as Pi does instead of…PiG's own defectAll platforms0.5.0
BF-a47eb1aA tool result from a Rust or Python extension can set the collapsed-view preview the Go SDK already had…PiG's own defectAll platforms0.5.0
BF-a53f72fA context an extension kept across a session replacement or reload now raises Pi's stale message from cwd…PiG's own defectAll platforms0.5.0
BF-a5524c8In print and JSON mode, an extension whose session_shutdown handler throws after SIGTERM or SIGHUP now has…PiG's own defectAll platforms0.5.0
BF-a5524c8-2In print and JSON mode, SIGTERM or SIGHUP during a tool call stops the run where Pi does while the…PiG's own defectAll platforms0.5.0
BF-a690122PIG_PROFILE=heap and the heap_live_bytes of PIG_PROFILE=metrics count only memory PiG retainsPiG's own defectAll platforms0.5.0
BF-a6932b4fix(coding): pass a Node extension request's client identity to the providerPiG's own defectAll platformsUnreleased
BF-a6e0ce8fix(codingagent): session selector page keys keep the index valid (Pi #7680)PiG's own defectAll platformsUnreleased
BF-a739b6dThe Bedrock endpoint region is read from the WHATWG hostname of the base URL, as Pi's new URL does…PiG's own defectAll platforms0.5.0
BF-a7be906Pressing Enter in the model picker's search box selects the highlighted model even when tui.select.confirm…PiG's own defectAll platforms0.5.0
BF-a7d2ef5Path autocomplete after resuming a session created in another directory now lists files from that session's…PiG's own defectAll platforms0.5.0
BF-a81ec0dA key pressed while an extension's custom overlay is open no longer freezes pig when the key reaches the…PiG's own defectAll platforms0.5.0
BF-a8a04f4PIG_DEBUG writes its log with mode 0600 and refuses a symlink or a file owned by another user in the shared…PiG's own defectAll platforms0.5.0
BF-a8ff0c7The anthropic-messages and google-generative-ai requests build their URL as @anthropic-ai/sdk and…PiG's own defectAll platforms0.5.0
BF-a901509OpenRouter turns (the built-in provider and custom providers on OpenRouter's endpoint) now take OpenRouter's…PiG's own defectAll platforms0.5.3
BF-a94455cThe durable code mode joins the text items of a script's output in linear time (Pi f11e50f7…PiG's own defectAll platforms0.5.0
BF-a9757c8A Cloudflare model built with coding.BuildModel and streamed through its own Provider resolves…PiG's own defectAll platforms0.5.0
BF-aa2003cThe edit tool's prepared arguments keep the members in the order the model sent themPiG's own defectAll platforms0.5.0
BF-aa69959Key matching follows Pi for more terminal inputs and key identifiersPiG's own defectAll platforms0.5.0
BF-aabd300An OpenAI Responses, Azure OpenAI Responses or Google stream event whose JSON is a number, string, boolean or…PiG's own defectAll platforms0.5.0
BF-ab0ee71Hiding an overlay whose preFocus is another overlay, such as a panel focused when the overlay was shown over…PiG's own defectAll platforms0.5.0
BF-ab5be97Long streamed answers render each frame faster: the Markdown view re-wraps and re-renders only the lines and…PiG's own defectAll platforms0.5.0
BF-ac0dc3bAn empty list item ends its list: a following unindented line is no longer taken for its lazy continuation…PiG's own defectAll platforms0.5.0
BF-ac6fceaA session_start handler that calls ctx.shutdown() during /reload ends the session and exits 0, as Pi…PiG's own defectAll platforms0.5.0
BF-aca35e7In print and JSON mode, a Node extension no longer runs a timer or other callback between a prompt's last…PiG's own defectAll platforms0.5.0
BF-ad49f9fpig status no longer reports a shared extension name as an unhealthy duplicatePiG's own defectAll platformsUnreleased
BF-ad9ed64Appending a tool result whose details are a tool's own value (a truncated read, bash output details, an…PiG's own defectAll platforms0.5.0
BF-ad9ed64-2A large message's session record no longer keeps a regrown buffer when its text has escapes such as newlines…PiG's own defectAll platforms0.5.0
BF-aeaaf97Model requests reuse their HTTP connectionPiG's own defectAll platforms0.5.0
BF-af1fe1aCycling models in the interactive UI goes through the Session as in Pi: the new model persists as the…PiG's own defectAll platforms0.5.0
BF-af36fe8A streamed tool call whose arguments are valid JSON but not an object ([], null, 5, "s") keeps them…PiG's own defectAll platforms0.5.0
BF-afa1dd5Amazon Bedrock requestMetadata keeps its keys' insertion order on the wire, as Pi's AWS SDK writes the…PiG's own defectAll platforms0.5.0
BF-afa1dd5-2make port-lint has three more checks: regexpinfunc (a constant pattern compiled inside a function)…PiG's own defectAll platforms0.5.0
BF-affc944Bedrock requests that fail with The pending stream has been canceled after a stalled HTTP/2 connection are…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-b02876bchord/host: a link that is already closed when a host accepts or routes to it is no longer kept as a…PiG's own defectAll platforms0.5.0
BF-b0608a9OpenRouter generated-image data URLs are matched exactly as Pi does (^data:([^;]+);base64,(.+)$): a URL…PiG's own defectAll platforms0.5.0
BF-b06ad92An MCP server url can use environment variables ("url": "${TOOLS_URL}"): PiG checks and connects to the…PiG's own defectAll platforms0.5.0
BF-b18a7ffctx.compact answers onComplete with the compaction result's members in Pi's order (summary…PiG's own defectAll platforms0.5.0
BF-b2b0c63Fix in shipped codePiG's own defectAll platformsUnreleased
BF-b2c219ffix(runtimecell): satisfy the divergence, port and perf gates for the usage-lease and fingerprint changesPiG's own defectAll platformsUnreleased
BF-b341a82go install github.com/MichaelKinsy/PiG/cmd/pig@latest works againPiG's own defectAll platforms0.5.2
BF-b3543f8Fixed MCP stdio servers staying alive after PiG exited because the terminal closed, it crashed, or it…PiG's own defectAll platforms0.5.0
BF-b430c7aExtension widgets now appear in the order they were set when Node or other extension-process widgets and Go…PiG's own defectAll platforms0.5.0
BF-b4504ddAmazon Bedrock request bodies match Pi's: PiG writes the ConverseStream body itself in the member order Pi's…PiG's own defectAll platforms0.5.0
BF-b47b6bcfix(tui): number the editor wrap divergence D159 (lead block), cite Pi's measured RangeError; add…PiG's own defectAll platformsUnreleased
BF-b4822efpig install <dir> now installs a local Go, Rust or Python extension folder and loads it as an extension, as…PiG's own defectAll platforms0.5.0
BF-b4c6569~/dir/ expands to the home directory with the trailing separator kept ($HOME/dir/), as Node's path.join…PiG's own defectAll platforms0.5.0
BF-b4e83dbAnthropic tools list eager_input_streaming and strict before input_schema, and the input schema and…PiG's own defectAll platforms0.5.0
BF-b4f1d4aPasting a large text that arrives in many terminal reads no longer stalls input: the paste is joined once…PiG's own defectAll platforms0.5.0
BF-b54f9aaThe tree selector label time of a session entry whose timestamp is not a valid date prints "aN/NaN/NaN…PiG's own defectAll platforms0.5.0
BF-b5b7b43Frontmatter YAML parsing matches the yaml library on text outside ASCII and on unterminated quotes: an…PiG's own defectAll platforms0.5.0
BF-b5ebeb5A resumed session draws its ! and !! command blocks, as Pi doesPiG's own defectAll platforms0.5.0
BF-b5fb4aafix(tui): a streamed Markdown is not resumed past a paragraph that a later LaTeX closer clips; keep wraps…PiG's own defectAll platformsUnreleased
BF-b607c35Started from a directory reached through a symlink (a $PWD naming a link), pig now uses the physical…PiG's own defectAll platforms0.5.0
BF-b684b81RPC and session JSON write an assistant message's members in the order Pi's providers assign them…PiG's own defectAll platforms0.5.0
BF-b6c65b3! command and RPC bash output no longer keeps fragments of color codes, such as a stray m, when an…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-b6d8ab0Fix changelog fragment and bug ledger naming the pre-squash commit of #307 (#317)PiG's own defectAll platformsUnreleased
BF-b730205OpenRouter image models that only the Images API serves (for example openai/gpt-image-2.5-flare) now…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-b751326When an extension in a shared Go, Rust or Python process crashes, the error for each stopped extension now…PiG's own defectAll platforms0.5.0
BF-b78cdaeA YAML parse error on a block scalar used as an implicit key (name: x followed by \|description: ...)…PiG's own defectAll platforms0.5.0
BF-b84facbfix(codingagent,tui): extension editor follows the terminal rows; title and image fallback close only the…PiG's own defectAll platformsUnreleased
BF-b88044fA login flow's text prompt accepts an empty answer, as Pi's does, instead of failing with "<prompt> is…PiG's own defectAll platforms0.5.0
BF-b8c5de6Interactive startup no longer reads terminal input while it checks for fd and rg unless one of them is being…PiG's own defectAll platforms0.5.0
BF-b8df7ebOn Windows, a session's private snapshot file is created in the temporary directory instead of the session…PiG's own defectWindows0.5.0
BF-b8df7eb-2On Windows, an image fallback links a path that starts with a slash but no drive (/tmp/x.png) as Pi does…PiG's own defectWindows0.5.0
BF-b8df7eb-3Alt-screen frames outside the Kitty protocol no longer read the environment for WezTerm on every render…PiG's own defectWindows0.5.0
BF-b99457ftool_search and codemode tool search match Chinese, Japanese and Korean queries: each such character is a…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-b9db06cfix(subprocess): keep a user_bash operations object alive while its exec runsPiG's own defectAll platformsUnreleased
BF-ba8b6b7/logout reads stored credentials with Pi's 15-second signal, so an unreadable auth.json reports "Could…PiG's own defectAll platforms0.5.0
BF-ba9c37bStarting pig no longer waits for the daily cleanup of the extension cachePiG's own defectAll platforms0.5.0
BF-bada2afSession and crash timestamps are read like Date.parse of a JavaScript string: U+00A0 and U+FEFF between…PiG's own defectAll platforms0.5.0
BF-bb324f7A JavaScript or TypeScript extension loads when its registerCommand options carry a member Pi does not…PiG's own defectAll platforms0.5.0
BF-bb3d661An extension served in-process now refuses to serve a second connection while it serves one, with the error…PiG's own defectAll platformsUnreleased
BF-bb888aeThe /model picker now keeps its model list when the catalog refresh times out, as in PiPiG's own defectAll platforms0.5.0
BF-bb9e9d8fix(extensions): parallel Node runtime linking reports the walk-order first failure under every schedulePiG's own defectAll platformsUnreleased
BF-bc24321fix(mem M3): arm the idle collection at run end and run it after 500 msPiG's own defectAll platformsUnreleased
BF-bc4cd62toPrecision rounds a value just below a power of ten (1e-11 to 16 digits, 9.999999999999998e9 to 16 digits)…PiG's own defectAll platforms0.5.0
BF-bc8d37fFileModelsStore roots and index keys: new live-Pi oracle…PiG's own defectAll platforms0.5.0
BF-bcc2046A nested list whose last item is empty but for spaces after its marker is followed by an empty row before the…PiG's own defectAll platforms0.5.0
BF-bd3e8abOn arm64 and other CPUs with a fused multiply-add, Kimi, Radius and xAI OAuth credentials store the expires…PiG's own defectAll platforms0.5.0
BF-bd3e8ab-2On arm64 and other CPUs with a fused multiply-add, the cache-warming expected savings (shown by /session…PiG's own defectAll platforms0.5.0
BF-bdb7ad8A single server-sent-event line larger than 32 MiB (for example a base64 image chunk) no longer fails the…PiG's own defectAll platforms0.5.0
BF-beeb36aRunning with no home directory now fails the way Pi's package manager does, instead of reading the project's…PiG's own defectAll platformsUnreleased
BF-bf45320Go embedding hosts can bind extension appends through coding.Session.AppendExtensionEntry, retaining…PiG's own defectAll platformsUnreleased
BF-c1055c4Package manifest globs match as Node's fs.globSync does in Pi: brace sets and sequences, extglobs such as…PiG's own defectWindows, macOS0.5.0
BF-c10ae73In RPC mode, runs that an extension command's handler sends with sendMessage or sendUserMessage start in…PiG's own defectAll platforms0.5.0
BF-c122088A durable nested call keeps its arguments in the member order the caller wrote them, as Pi does: the stored…PiG's own defectAll platforms0.5.0
BF-c1ed918fix(sdk-rs): give the request-start wire test's RequestMsg the batch, child_session, tool_call_index and…PiG's own defectAll platformsUnreleased
BF-c1fedc2The durable read tool reads a file that grows while it is read, such as an active log, instead of failing…PiG's own defectAll platforms0.5.0
BF-c1fedc2-2Bounded durable tool output drops a byte-order mark only at the start of byte output, and keeps a U+FEFF at…PiG's own defectAll platforms0.5.0
BF-c225c97fix(rpc): compaction_start precedes a refused replacement's error; D148 names the extra compaction_end; D141…PiG's own defectAll platformsUnreleased
BF-c229e1cA Pi Durable SQLite file written by PiG can be read by Pi, and the reverse, when a task kind, request ID, or…PiG's own defectAll platforms0.5.0
BF-c2b2b8bfix(tui): the inline memo is used only with tui's own style functionsPiG's own defectAll platformsUnreleased
BF-c350117Fixed incorrect MCP reconnect behavior after cancellation during transport cleanupPiG's own defectAll platformsUnreleased
BF-c3a6ecaMarkdown rendering follows Pi for lines made only of Unicode spaces (U+00A0, U+3000, U+2003 and the others…PiG's own defectAll platforms0.5.0
BF-c416945fix(ai): tool-argument member order keeps a key with an escaped lone surrogatePiG's own defectAll platformsUnreleased
BF-c47bafdA fused Go extension's built-in tool execute without an extension context (`createBashTool(cwd).execute(id…PiG's own defectAll platforms0.5.3
BF-c4ad739Extension shortcut conflict warnings name both extensions ("registered by both A and BPiG's own defectAll platforms0.5.0
BF-c4d51d5The OpenAI Codex login reads the account id from the access token the way Pi does: the token payload is…PiG's own defectAll platforms0.5.0
BF-c4fae28Provider request bodies write a lone UTF-16 surrogate as its escape, as JSON.stringify does, instead of…PiG's own defectAll platforms0.5.0
BF-c4fef6cStreaming long code blocks no longer grows memory with every frame: the syntax-highlight cache keeps one copy…PiG's own defectAll platforms0.5.0
BF-c51c77eAn extension that calls registerTool({}) or registerTool(null) now fails to load with Pi's error text…PiG's own defectAll platforms0.5.0
BF-c58ceedchord/host.Forbidden("") keeps the empty message, as Pi's forbidden("") does; Forbidden() still says…PiG's own defectAll platforms0.5.0
BF-c5ce588Gemini 4 and later models send thinkingLevel instead of thinkingBudget, fixing Gemini 4 Argon on Vertex…PiG's own defectAll platforms0.5.0
BF-c625bb4The durable powershell tool fails with "No command to run" when its program list is empty, instead of…PiG's own defectWindows0.5.0
BF-c6c2cf3An env request whose context is already cancelled is now always sent to the daemon and then cancelled, as…PiG's own defectAll platforms0.5.0
BF-c7ebb23A cancelled prompt in a config.oauth login now rejects with Pi's Login cancelled in every SDK (it was…PiG's own defectAll platforms0.5.0
BF-c7ebb23-2A config.oauth login's onManualCodeInput asks "Paste the authorization code", as Pi does, and a provider…PiG's own defectAll platforms0.5.0
BF-c8a90a6Pressing the login dialog's cancel key again cancels again, and Enter after a cancelled prompt leaves the…PiG's own defectAll platforms0.5.0
BF-c8e3d86Settings submenus: the type-to-filter box no longer shows a hardware cursor marker (Pi never focuses it), and…PiG's own defectAll platforms0.5.0
BF-c98e9f9Without HOME set, the project trust check takes the home directory from the user database, as Pi's…PiG's own defectAll platforms0.5.0
BF-c9acbeaA Session listener that queues more messages than the Session's event buffer holds (64 by default), while it…PiG's own defectAll platforms0.5.0
BF-c9acbea-2A goroutine that waits to run Session events, such as the Session's event goroutine while a producer runs a…PiG's own defectAll platforms0.5.0
BF-c9f1cffThe experimental client takes Pi's ByteTransportFactory shape: ClientOptions.TransportFactory returns a…PiG's own defectAll platforms0.5.0
BF-c9fbfccInline images render in Herdr again: TERM_PROGRAM=herdr detects the Kitty graphics protocol, as in Pi 1.2.0PiG's own defectAll platforms0.5.0
BF-ca06a0bTool arguments that TypeBox's Convert wraps in an array (an object where the schema asks for an array, such…PiG's own defectAll platforms0.5.0
BF-ca27ba6The Go extension SDK accepts a native Provider object with no auth methods, as Pi's ProviderAuth (both…PiG's own defectAll platforms0.5.0
BF-ca675e0A list item whose text is [ ] or [x] with nothing after the spaces on the line is plain text, not a…PiG's own defectAll platforms0.5.0
BF-cb00273OpenRouter's signature-only reasoning.text delta for Claude is kept and replayed with its reasoning block…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-cb21ee0Markdown resolves link reference definitions as Pi does: [text][label], [label][] and [label] link to a…PiG's own defectAll platforms0.5.0
BF-cc9370bA Node extension's setWidget, setHeader and setFooter factories receive the same tui as a…PiG's own defectAll platforms0.5.0
BF-ccaade2The /thinking selector edits its search like Pi's input, selects with the input's submit key, and no longer…PiG's own defectAll platforms0.5.0
BF-cd7fa1cA read-only config root no longer prints warnings at every startPiG's own defectAll platforms0.5.0
BF-cda6e3aPiG opens a session file that holds a valid JSON line that is not an entry (an array, true, a string) or an…PiG's own defectAll platforms0.5.0
BF-cdf0be7Markdown rendering matches Pi on blank lines at the start and end of the text, empty headings and closing #…PiG's own defectAll platforms0.5.0
BF-cdf2887fix(ai): keep a reused-id tool result in its own turn during the D48 repairPiG's own defectAll platformsUnreleased
BF-ce0f352createPowerShellTool now uses the operations optionA fix Pi shipped that PiG portedWindows0.5.0
BF-ce0f352-2The editor's autocomplete list takes its colors from one getSelectListTheme equivalent, the same function…PiG's own defectAll platforms0.5.0
BF-ce366ddfix(coding): resume restores the transcript's tools only for the unchanged default selection…PiG's own defectAll platformsUnreleased
BF-ce42877durable/images.CreatePhotonImages, and durable/images/node.CreateNodePhotonImages with Worker: false…PiG's own defectAll platforms0.5.0
BF-ce8587eOn macOS, reading an image or file paths from the clipboard and writing text to it no longer risk passing…PiG's own defectmacOS0.5.0
BF-ced3a9cRPC model objects of built-in models carry "type":"chat" (get_available_models, get_state, set_model…PiG's own defectAll platforms0.5.0
BF-ced3a9c-2A URL of a non-special scheme keeps an explicit port 0 (ssh://git@host:0/repo, an MCP OAuth redirect such…PiG's own defectAll platforms0.5.0
BF-cf4fff8A project whose only configuration is a project mcp.json (.pig/mcp.json, or .pi/mcp.json with…PiG's own defectAll platforms0.5.0
BF-cf648c7A restarted extension, isolated or in a recovered packed cell, now offers the commands, flags, shortcuts and…PiG's own defectAll platforms0.5.0
BF-cf7f368A local file listed as a Package with autoload: false still loads as one enabled extension, and when a…PiG's own defectAll platforms0.5.0
BF-d0c2727fix(codingagent): decode a Node request's client identity from its fields; regenerate the normalization…PiG's own defectAll platformsUnreleased
BF-d11ca55pig piglet build --format binary from a Pig checkout with a go.work builds a Piglet whose fused Go…PiG's own defectAll platforms0.5.0
BF-d133e3dCodemode scripts that patch built-ins, for example Array.prototype.toJSON = ..., no longer break the tool…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-d133e3d-2MCP OAuth sign-in no longer fails with invalid_redirect_uri on OpenID Connect servers: client registration…A fix Pi shipped that PiG portedAll platforms0.5.0
BF-d1cbde3The pi-messages, Cloudflare Workers AI and TypeSafe System One, and OpenAI decisions request URLs are built…PiG's own defectAll platforms0.5.0
BF-d1f258fThe /settings search edits like Pi's search input: the cursor moves with the arrow keys, Ctrl+W, Ctrl+K…PiG's own defectAll platforms0.5.0
BF-d212cdfpig --piglet piglets/standard/pig-standard.yaml no longer warns on every start that its piglogin…PiG's own defectAll platforms0.5.0
BF-d249c70SDK: Session.Dispose now releases the Session as Pi's dispose doesPiG's own defectAll platforms0.5.0
BF-d26d494fix(tui): Editor.SetText resets the scroll offsetPiG's own defectAll platformsUnreleased
BF-d276c00pig piglet schema lists the apis strip listPiG's own defectAll platforms0.5.0
BF-d2b1fc1GitHub Copilot login reads an enterprise domain the way Pi does: an IPv6 host keeps its brackets, an…PiG's own defectAll platforms0.5.0
BF-d2b1fc1-2Pasting an OAuth authorization into the Anthropic, OpenAI Codex or OpenRouter login now reads it the way Pi…PiG's own defectAll platforms0.5.0
BF-d2d4384A streamed write card no longer slows down as its content growsPiG's own defectAll platforms0.5.0
BF-d305b76A Go extension whose go.mod replace (or a Rust extension whose pig-sdk path dependency) points at an SDK…PiG's own defectAll platforms0.5.0
BF-d36b329The edit tool no longer accepts a JSON-string edits value that starts or ends with non-JSON whitespace such…PiG's own defectAll platforms0.5.0
BF-d37c769A replacement request body from a before_provider_request hook on Anthropic Messages, and every OpenAI…PiG's own defectAll platforms0.5.0
BF-d39069dTool cards rebuilt when a session is resumed now follow the showImages and imageWidthCells settingsPiG's own defectAll platforms0.5.0
BF-d3b3618Fix in shipped codePiG's own defectAll platformsUnreleased
BF-d42aebbai.ValidateToolArguments and ai.ValidateToolCall list a failed validation's errors in the order the…PiG's own defectAll platforms0.5.0
BF-d449f74tool_search and the Durable searchTools() ranker compute the same BM25 scores as Pi on arm64: the length…PiG's own defectAll platforms0.5.0
BF-d490a8bTool argument validation errors follow Pi: at most eight errors are listed (TypeBox's maxErrors), and…PiG's own defectAll platforms0.5.0
BF-d4ddb14The /scoped-models search input stays editable after a submit key reaches it (LF under the Kitty keyboard…PiG's own defectAll platforms0.5.0
BF-d50ec25The TypeScript extension runtime no longer depends on Node-only behaviour: its entry modules do not await the…PiG's own defectAll platforms0.5.0
BF-d514b3dIn a Node extension's before_agent_start handler, event.systemPrompt and ctx.getSystemPrompt() now…PiG's own defectAll platforms0.5.0
BF-d53cd09A tool call whose arguments are not an object is written back to each provider as Pi writes it: Anthropic and…PiG's own defectAll platforms0.5.0
BF-d60d422pi.exec gives the command PiG's environment unchanged, as Pi does, so a command started in a symlinked…PiG's own defectAll platforms0.5.0
BF-d64f8eddurable.StorageRequestError and durable.SessionFailed are Pi's error classes of the same names…PiG's own defectAll platforms0.5.0
BF-d6aaf72A setTimeout(0) or setImmediate callback that an extension queues while an RPC stdin command runs (a…PiG's own defectAll platforms0.5.2
BF-d6e0dcaThe extension SDK surface matrix and the extension API pages say that setEditorComponent and…PiG's own defectAll platforms0.5.0
BF-d7855fdA subprocess extension's message, entry or tool renderer, or tool renderer resolver, that was busy past the…PiG's own defectAll platforms0.5.0
BF-d7bfea6A git source whose ref has no repository (git:#v1) no longer panics, and a ref split out of an opaque-path…PiG's own defectAll platforms0.5.0
BF-d7c7f86fix(runtimecell): start a packed cell's members in order so a later member's factory cannot take the factory…PiG's own defectAll platformsUnreleased
BF-d80425cfix(train): gate repairs after merging train 3 and the ordering stackPiG's own defectAll platformsUnreleased
BF-d80c1cdWhere Pi calls new URL(value), Pig now parses a ws:, wss:, ftp: or file: URL, and a bracketed IPv6…PiG's own defectAll platforms0.5.0
BF-d8357e2Cache warmer pure functions against live Pi: new TestCacheWarmerPureFunctionsMatchPi…PiG's own defectAll platforms0.5.0
BF-d87b70cA second ctx.ui.custom component opened while one is shown now opens over it at once, as in Pi, whether it…PiG's own defectAll platforms0.5.0
BF-d88dca7harness.ToolTaskInput.Kind and ToolTaskResult.Kind are the named string type harness.ToolTaskKind with…PiG's own defectAll platforms0.5.0
BF-d8bed22auth.json keeps every provider's entry as written, as Pi doesPiG's own defectAll platforms0.5.0
BF-d8d513aWith the Bun TypeScript runtime (PIG_TS_RUNTIME=bun), /reload now evaluates an edited ES module extension…PiG's own defectAll platforms0.5.0
BF-d90c2f4Markdown now nests a **strong** run inside *emphasis* as Pi does, renders a line underlined with === or…PiG's own defectAll platforms0.5.0
BF-d983b74Anthropic requests whose strict prefer tools exceed a strict tool limit ("The compiled grammar is too…PiG's own defectAll platforms0.5.0
BF-d9886fefix(hljs): no magic-literal hits for the resume and dispatch boundsPiG's own defectAll platformsUnreleased
BF-da5d32bfix(extension): theme palette reports the theme's own sourcePath; conformance rows for the rest of…PiG's own defectAll platformsUnreleased
BF-da8a870Pasting a ChatGPT sign-in callback URL with a ws:, wss: or ftp: scheme, a backslash path or an unusual…PiG's own defectAll platforms0.5.0
BF-da907caThe Go program in the SDK page now compilesPiG's own defectAll platforms0.5.0
BF-da907ca-2The bundled command page (pig docs show commands) and the website's CLI reference now agree with `pig…PiG's own defectAll platforms0.5.0
BF-da907ca-3pig docs show divergences lists D110 and D111, and the README names the pinned Pi release (1.1.0) and its…PiG's own defectAll platforms0.5.0
BF-da907ca-4The Termux page's link to the Quickstart reaches its "Authenticate a provider" sectionPiG's own defectAndroid and Termux0.5.0
BF-dac3cd5A session keeps reading its history as it loaded it when another program rewrites, truncates, replaces or…PiG's own defectAll platforms0.5.0
BF-dae37befix: the input loop never waits for the Session lock; the abort-fix leak and two stale testsPiG's own defectAll platformsUnreleased
BF-db12780fix(agent): end the tool flow at custom user messages and hold system messages mid-flowPiG's own defectAll platformsUnreleased
BF-db1ee65A lone UTF-16 surrogate in a tool result's details, in a nested tool call's result, or in a code mode call's…PiG's own defectAll platformsUnreleased
BF-db7007dA Radius gateway model's samplingParamsByThinkingLevel now reaches the pi-messages model and the composed…PiG's own defectAll platforms0.5.0
BF-db9a315Codemode peak memory is now measured and guarded through the normal tool path in a fresh process with 40…PiG's own defectAll platforms0.5.0
BF-dbe1ad3pig setup now flags a go on PATH older than Go 1.26, the extension SDK minimum, naming the version found…PiG's own defectAll platforms0.5.0
BF-dbe25efCI: make interface-gaps passes on main againPiG's own defectAll platforms0.5.2
BF-dbe25ef-10The bug fix ledger no longer credits a squash commit's tests to a release note that names no fix commitPiG's own defectAll platforms0.5.2
BF-dbe25ef-11Two calls of a Go, Python or Rust extension tool in one assistant message that each open a dialog now behave…PiG's own defectAll platforms0.5.2
BF-dbe25ef-12pig extension upgrade finds and rewrites every changed API of an extension in one runPiG's own defectAll platforms0.5.2
BF-dbe25ef-13pig extension upgrade applies the rewrites that have one safe form and lists the rest with file, line and…PiG's own defectAll platforms0.5.2
BF-dbe25ef-14The Node extension runtime's ctx.modelRegistry.runtime is now a model runtime that a nested Session can…PiG's own defectAll platforms0.5.2
BF-dbe25ef-15Starting a child host could wait out its 30-second ready timeout ("child not ready in time") although the…PiG's own defectAll platforms0.5.2
BF-dbe25ef-16Fixed the chord host's Close waiting forever when a hold that a link was still delivering created an object…PiG's own defectAll platforms0.5.2
BF-dbe25ef-17pig update --extensions no longer fails when two Git Packages come from one repositoryPiG's own defectAll platforms0.5.2
BF-dbe25ef-18A Go, Python or Rust extension tool that awaits a host call such as exec before its first dialog no longer…PiG's own defectAll platforms0.5.2
BF-dbe25ef-19Two calls of an extension tool in one assistant message now end in the order their tools answer, as Pi ends…PiG's own defectAll platforms0.5.2
BF-dbe25ef-2CI: the Windows test jobs no longer fail the pig extension upgrade notice tests when the temporary…PiG's own defectWindows0.5.2
BF-dbe25ef-20A compaction, branch summary or bug-report summary request is now sent through the session's own stream…PiG's own defectAll platforms0.5.2
BF-dbe25ef-21Fixed pig --mode rpc exiting 143 instead of 0 when stdin ended while a termination signal's shutdown was…PiG's own defectAll platforms0.5.2
BF-dbe25ef-3pig extension upgrade: the command a startup notice or the upgrade offer prints now quotes a POSIX path…PiG's own defectWindows0.5.2
BF-dbe25ef-4RPC mode no longer runs ctx.ui.setWidget component factories or emits setWidget requests carrying their…PiG's own defectAll platforms0.5.2
BF-dbe25ef-5pig piglet verify <binary> --source <name\|path> and pig piglet lineage <binary> --source <name\|path>…PiG's own defectAll platforms0.5.2
BF-dbe25ef-6pig piglet verify accepts --workspace <dir>, and pig piglet lineage --workspace now applies to the…PiG's own defectAll platforms0.5.2
BF-dbe25ef-7A run that an extension's sendMessage with triggerTurn starts now carries the session's system promptPiG's own defectAll platforms0.5.2
BF-dbe25ef-8go install github.com/MichaelKinsy/PiG/cmd/pig@main builds againPiG's own defectAll platforms0.5.2
BF-dbe25ef-9A tool that an extension activates with setActiveTools now appears in the system prompt's tool list of the…PiG's own defectAll platforms0.5.2
BF-dc53412A pig_strip_bedrock_converse_stream Piglet Binary no longer links the AWS SDK through the Bedrock retry…PiG's own defectAll platforms0.5.0
BF-dca5763A notification, status or message that an extension queues on setImmediate or setTimeout(0) from inside a…PiG's own defectAll platforms0.5.2
BF-ddd4772fix(ai): a lone surrogate in a content block stays one code unit, as in JSON.parsePiG's own defectAll platformsUnreleased
BF-dde6aa6Cache warming no longer refreshes at once when a model's promptCache lifetime is far larger than the one-hour…PiG's own defectAll platforms0.5.0
BF-dde6aa6-10On Windows, path normalization and joining follow Node 24's reserved device name handling: a path such as…PiG's own defectWindows0.5.0
BF-dde6aa6-11OpenDirReader on a FIFO now fails with not_directory at once instead of waiting for a writerPiG's own defectAll platforms0.5.0
BF-dde6aa6-12Fixed the queued steering and follow-up lines, the dequeue hint and the startup hint separator to use Pi's…PiG's own defectAll platforms0.5.0
BF-dde6aa6-13A session entry timestamp that is not RFC 3339 but that JavaScript Date reads (a space for the T, an RFC 2822…PiG's own defectAll platforms0.5.0
BF-dde6aa6-14Fixed the session selector's selected row after a down or page-down key over an empty list: as in Pi the…PiG's own defectAll platforms0.5.0
BF-dde6aa6-15Fixed a failing tool call's result always opening fully expanded: like Pi, an error result collapses to the…PiG's own defectAll platforms0.5.0
BF-dde6aa6-16Fixed the tree selector's label prompt drawing an input row one cell too wide in a terminal two columns wide…PiG's own defectAll platforms0.5.0
BF-dde6aa6-17Fixed the card of a tool call for a tool that does not exist (a name the model made up): it now shows the…PiG's own defectAll platforms0.5.0
BF-dde6aa6-2Fixed context edits replacing the content of a system message: as in Pi's projectContextEntry, only user…PiG's own defectAll platforms0.5.0
BF-dde6aa6-3Fixed the edit tool's diff card for files with CRLF line endings: a diff row that ends in a carriage return…PiG's own defectAll platforms0.5.0
BF-dde6aa6-4Fixed the extension editor dialog to size its text window from the terminal rows (`max(5, floor(rows *…PiG's own defectAll platforms0.5.0
BF-dde6aa6-5Fixed lists drawn by the submenu and filterable selectors (settings submenus, model and thinking lists)…PiG's own defectAll platforms0.5.0
BF-dde6aa6-6Fixed the footer's context percentage keeping the usage of a reply that a context edit had dropped (a…PiG's own defectAll platforms0.5.0
BF-dde6aa6-7A table delimiter row on a lazy blockquote line no longer splits the quoted paragraph above it unless it…PiG's own defectAll platforms0.5.0
BF-dde6aa6-8A setext underline on a lazy line after another lazy line in a blockquote now renders as Pi does, joined to…PiG's own defectAll platforms0.5.0
BF-dde6aa6-9A line of only spaces and tabs that starts with a tab or four spaces, alone or after a blank line or in a…PiG's own defectAll platforms0.5.0
BF-de0aaa7RPC mode now writes the PIG_PROFILE files when it exits; it ended the process before they were writtenPiG's own defectAll platforms0.5.0
BF-de38ce2fix(ai,durable): a lone UTF-16 surrogate survives the JSON of a tool result, a nested call result and a…PiG's own defectAll platformsUnreleased
BF-de85b0eAn HTTP MCP client opens its server-to-client stream before it sends the next request after…PiG's own defectAll platforms0.5.0
BF-de91c76When an MCP server drops tools or is disabled, its tools are hidden in the order the server listed themPiG's own defectAll platforms0.5.0
BF-de91c76-2The error of an MCP stdio server that fails to start ends with the last 2000 UTF-16 code units of its…PiG's own defectAll platforms0.5.0
BF-deadeb8A prompt in a long session no longer costs time in proportion to the session's history: the current branch…PiG's own defectAll platforms0.5.0
BF-deadeb8-2Extension session reads (getEntry, getLabel, getChildren, getBranch, buildSessionContext and the…PiG's own defectAll platforms0.5.0
BF-debe30eA keybindings.json key named like a property every JavaScript object inherits (constructor, toString…PiG's own defectAll platforms0.5.0
BF-dec54ebfix(tui): Container.Render is safe beside a frame that borrows the windowPiG's own defectAll platformsUnreleased
BF-df0b21cA Go, Rust or Python extension that calls parseFrontmatter or Date.parse while its factory runs, in a…PiG's own defectAll platforms0.5.0
BF-df96b6aFix in shipped codePiG's own defectAll platformsUnreleased
BF-dfa57b3An OpenAI Responses request for a conversation with no messages sends "input": [] instead of "input": nullPiG's own defectAll platforms0.5.0
BF-e098463A Gemini stream chunk with a member of an unexpected type is read as Pi's JavaScript reads it instead of…PiG's own defectAll platforms0.5.0
BF-e1764c3A live /skill: message renders as the collapsed [skill] name block instead of the raw <skill> XMLPiG's own defectAll platforms0.5.0
BF-e1ad272The xAI, Kimi Code, Meta, Radius, OpenAI Codex, Sign in with ChatGPT and GitHub Copilot OAuth requests send…PiG's own defectAll platforms0.5.0
BF-e24487efix(cli): PIG_VERSION_FORMAT=pi prints the pinned Pi package versionPiG's own defectAll platformsUnreleased
BF-e25a09fA tool argument or event payload with a number outside the double range (1e400), at any nesting depth, no…PiG's own defectAll platforms0.5.0
BF-e2db931On Windows, refreshing an MCP server's OAuth token no longer fails with EPERM when another pig process is…PiG's own defectWindows0.5.0
BF-e32c60cA Piglet or Package installed under a symlinked agent directory keeps the origin integrity recorded in its…PiG's own defectAll platforms0.5.0
BF-e3f39b7Automatic retry now triggers for the gateway error "Unable to forward upstream stream"PiG's own defectAll platforms0.5.0
BF-e42ae82SettingsManager structured getters (settings-manager.ts getBranchSummarySettings, getRetrySettings…PiG's own defectAll platforms0.5.0
BF-e48a02ffix(tui): filterable list drops the label when no cell remains (Pi select-list)PiG's own defectAll platformsUnreleased
BF-e51bb37OpenAI Responses streams follow Pi's processResponsesStream on malformed and edge events, checked against…PiG's own defectAll platforms0.5.0
BF-e54c2a7openai-decisions writes the image input's user message, text part and image parts with their members in…PiG's own defectAll platforms0.5.0
BF-e55895aDurable: opening a store that pi-durable 1.3.0 wrote no longer leaves you unable to go back to itPiG's own defectAll platforms0.5.2
BF-e55cdc2The Codex catalog entry for GPT-6.1 Sol reports the 500K context window the Codex models endpoint returns…PiG's own defectAll platforms0.5.0
BF-e59f3f6Fixed tool-call arguments losing a field and every field after it when the model wrote an invalid \u escape…PiG's own defectAll platforms0.5.0
BF-e5a345eAn Anthropic request holds a mid-conversation system update until an assistant message that is actually sent…A fix Pi shipped that PiG portedAll platforms0.5.3
BF-e5aae63The editor now enters bash mode (bash border color, Escape clears it) when the text starts with ! after any…PiG's own defectAll platforms0.5.0
BF-e5f9fddThe provider retry transport no longer replaces Body on the caller's request when it retriesPiG's own defectAll platforms0.5.0
BF-e63c61cfix(interactive): pump the owner loop while model selection awaits extensions (#208) (#209)PiG's own defectAll platformsUnreleased
BF-e64d2cffix(tui): a tool error result collapses like any other (Pi updateResult)PiG's own defectAll platformsUnreleased
BF-e69d018Closing an env connection, or losing it, now ends its daemon process with SIGTERM, as in Pi, instead of…PiG's own defectAll platforms0.5.0
BF-e73e536Codemode scripts no longer fail immediately when both codemode workers are busy, for example with parallel…A fix Pi shipped that PiG portedAll platforms0.5.2
BF-e75a8b9The external editor (ctrl+g) now edits prompt.md inside a private pi-editor-* directory under the…PiG's own defectAll platforms0.5.0
BF-e79c70efix(ai): Codex WebSocket, OpenRouter images, pi-messages and replaced Anthropic bodies write JSON.stringify…PiG's own defectAll platformsUnreleased
BF-e8489d4A key hint for an app action the user unbound shows no key, as in Pi, instead of the action's default keyPiG's own defectAll platforms0.5.0
BF-e90ac19Very long streamed answers no longer slow down with their length: each streamed frame renders only the last…PiG's own defectAll platforms0.5.0
BF-e9332c8fix(codingagent): session search re: reads patterns as V8 doesPiG's own defectAll platformsUnreleased
BF-e9392fdA Google Generative AI or Vertex request with no contents (a history of system messages only, or one user…PiG's own defectAll platforms0.5.0
BF-e973464A fused Go extension's createLocalBashOperations().exec (and createLocalPowerShellOperations) no longer…PiG's own defectWindows0.5.3
BF-e9e9cf8An extension that calls a pi-ai function such as completeSimple at top level or in its factory no longer…PiG's own defectAll platforms0.5.0
BF-ea6db99A provider request that fails in transport now records its cause: the failed message carries a…PiG's own defectAll platforms0.5.0
BF-ea90fb3A running !cmd block keeps only the last 2000 lines or 50 KB it shows instead of every byte the command…PiG's own defectAll platforms0.5.0
BF-ead285dTwo compact commands of one RPC input batch no longer cancel each other; they run one after another (D147)PiG's own defectAll platforms0.5.0
BF-eb34085The find tool trims each fd result line like JavaScript trim(): a name ending in U+FEFF loses it and a name…PiG's own defectAll platforms0.5.0
BF-ebb4c0eAn "m" operation whose permutation a Go caller builds as []int validates and applies again, as it did in…PiG's own defectAll platforms0.5.0
BF-ebb4c0e-2durable.DocumentContent decoded by encoding/json keeps the key order of the objects its operations carry…PiG's own defectAll platforms0.5.0
BF-ebbdfe7fix(extensions): load highlight.js grammars after the startup session_start, as Pi doesPiG's own defectAll platformsUnreleased
BF-ebec8e2A URL host with an xn-- label that is empty after IDNA mapping (ws://xn--。, https://..\ufeffxn--) or…PiG's own defectAll platforms0.5.0
BF-ebfeed0getSystemPromptOptions().hiddenTools lists hidden tools in the order the prepareLoadout hooks hid them…PiG's own defectAll platforms0.5.0
BF-ec2080aA subprocess extension's ctx.ui.custom component now learns when another component shown over it takes…PiG's own defectAll platforms0.5.0
BF-ed05099An extension's ui.setHiddenThinkingLabel now changes the label shown for hidden thinking in the chat…PiG's own defectAll platforms0.5.0
BF-ed05099-2Tab at the start of a line no longer forces file completion while the text before the cursor is a…PiG's own defectAll platforms0.5.0
BF-ed05099-3pi.exec called while an extension loads, before the session binds, no longer fails with `extension…PiG's own defectAll platforms0.5.0
BF-ed05099-4Tool-call input keeps its object members in the order the model sent them through events, tool execution…PiG's own defectAll platforms0.5.0
BF-edb73dbfix(codingagent): one owner-overflow forwarder at a time; the drain test waits for it, not for the flag's…PiG's own defectAll platformsUnreleased
BF-edd287cMigrating a version 1 session file that holds a line parsing to a number, string, array or true no longer…PiG's own defectAll platforms0.5.0
BF-eeac307Anthropic and Bedrock assistant messages write providerThinkingLevel after model, and write…PiG's own defectAll platforms0.5.0
BF-ef0f95aOpenCode Zen and OpenCode Go use the opencode.ai/inference endpoints, which fixes OpenCode Go Gemini models…PiG's own defectAll platforms0.5.0
BF-efded13Installing a Package or Piglet from a local git:file:// source on Windows no longer fails with `fatal…PiG's own defectWindows0.5.3
BF-f02269cWhen an extension's before_provider_request handler replaces an Anthropic request, or a Codex WebSocket…PiG's own defectAll platforms0.5.0
BF-f06ab7eA run that sendMessage({ triggerTurn: true }) starts while idle emits before_agent_start, checks…PiG's own defectAll platforms0.5.0
BF-f07d033A response stream dropped mid-run now reports terminated (cause: ...) instead of a bare terminated (D144…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-f0b910fExtensions' ui_prompt_start and ui_prompt_end notifications are recorded in order when the host is slow…PiG's own defectAll platforms0.5.3
BF-f12896bInteractive startup writes the terminal color query before its first frame, as Pi does: starting the renderer…PiG's own defectAll platforms0.5.0
BF-f19efb1A session_compact handler's passive custom message reaches the overflow retry (D138, Pi #9051)A Pi defect that PiG fixed firstAll platforms0.5.0
BF-f1a59f9An OpenAI Responses event's output_index matches an output item as a key of Pi's Map does: a string…PiG's own defectAll platforms0.5.0
BF-f20cc08A Node extension that calls pi.appendEntry many times in one handler no longer slows down with the size of…PiG's own defectAll platforms0.5.0
BF-f24a8eeFixed Markdown links not being clickable in Herdr: TERM_PROGRAM=herdr is now detected as supporting OSC 8…PiG's own defectAll platforms0.5.0
BF-f24a8ee-2Fixed the fullscreen text selection and multi-click state surviving a session switch or other transcript…PiG's own defectAll platforms0.5.0
BF-f25348fA turn_end or agent_before_settle handler in an extension process receives each…PiG's own defectAll platforms0.5.0
BF-f2790f9A tui input listener or light/dark listener that is added while input is dispatched now sees that input…PiG's own defectAll platforms0.5.0
BF-f288d69MCP OAuth token and client registration responses keep empty lists, read expires_in like JavaScript's…PiG's own defectAll platforms0.5.0
BF-f288d69-2On Windows, an npm self-update no longer fails when a virus scanner or indexer holds pig.exe open for a…A Pi defect that PiG fixed firstWindows0.5.0
BF-f288d69-3ModelRuntime.Close cancels the remote model catalog's refreshes and waits for them, so none runs on after…PiG's own defectAll platforms0.5.0
BF-f288d69-4Crash attribution reads Go goroutine traces as well as JavaScript stacks, so a crash in the PiG process…PiG's own defectAll platforms0.5.0
BF-f29e906OpenAI Completions, OpenAI Responses and Codex request bodies list their fields in the order Pi writes them…PiG's own defectAll platforms0.5.0
BF-f2a2b34An unmatched tool result row in the /tree selector shows the tool's name, as in Pi ([bash]), instead of…PiG's own defectAll platforms0.5.0
BF-f2c8b02After await ctx.reload(), a command's captured pi and ctx now throw Pi's stale message for every…PiG's own defectAll platforms0.5.0
BF-f34b545Durable: resolving harness settings no longer shares the default retry policy's max agent delay between…PiG's own defectAll platforms0.5.0
BF-f3b4045.gitignore, .ignore and .fdignore rules that skills, packages and extension entry discovery read now…PiG's own defectAll platforms0.5.0
BF-f4d85f8A large file written through a tool call no longer slows down as it streams: each streamed piece of the…PiG's own defectAll platforms0.5.0
BF-f5ecdc2Settings and models.json compat objects keep the members PiG does not model, as Pi 1.2.0 doesPiG's own defectAll platforms0.5.0
BF-f6305d9A new_session, fork, clone or switch_session in the same RPC batch as a compact aborts the…PiG's own defectAll platforms0.5.0
BF-f68aa40A delta.Tracker no longer keeps memory alive that Pi's tracker releasesPiG's own defectAll platforms0.5.0
BF-f68aa40-2delta.CloneJSON, chord.CopyJSON and draft placements report a Go struct or pointer with Pi's message for…PiG's own defectAll platforms0.5.0
BF-f6e7d49/tree opens and searches a 300 MB session faster than Pi: it reads the session's index and each message's…PiG's own defectAll platforms0.5.0
BF-f6e7d49-2A session without a file (--no-session) keeps the records of entries behind its latest compaction…PiG's own defectAll platforms0.5.0
BF-f6e7d49-3RPC get_entries, RPC get_tree and extension session reads now fail with an error naming the entry when…PiG's own defectAll platforms0.5.0
BF-f73c00cReplaying a thinking block that holds only U+FEFF drops it and one that holds only U+0085 keeps it, as…PiG's own defectAll platforms0.5.0
BF-f78b951fix(codingagent): cache the Markdown transformer list per theme and runner (TU-T6)PiG's own defectAll platformsUnreleased
BF-f7b245aA frontend member's main region now matches the transcript after an extension's custom entry is placed before…PiG's own defectAll platforms0.5.0
BF-f7c4b01pig extension upgrade no longer changes, breaks or rejects a Go extension that already builds against the…PiG's own defectAll platforms0.5.0
BF-f7d701eTool argument validation asserts the format keyword for the formats TypeBox checks (date, time, date-time…PiG's own defectAll platforms0.5.0
BF-f8673e6Tool argument conversion for TypeBox schemas lower-cases with JavaScript rules (an İ in UNDEFİNED no…PiG's own defectAll platforms0.5.0
BF-f90fedcfix(cli): satisfy errcheck and gocritic in the validate-only changePiG's own defectAll platformsUnreleased
BF-f9648c6On Windows, stopping a terminal reader whose input is a pipe no longer hangsPiG's own defectWindowsUnreleased
BF-f98d129Clearing the queued messages (RPC clear_queue, the SDK, the interactive restore-to-editor key) no longer…A Pi defect that PiG fixed firstAll platforms0.5.0
BF-f98d129-2Omitting a failed retry attempt no longer makes the context usage jump to an estimate of the whole historyA Pi defect that PiG fixed firstAll platforms0.5.0
BF-f98d129-3A request carries at most the model's inputLimits.images.maxPerRequest imagesA Pi defect that PiG fixed firstAll platforms0.5.0
BF-f992050fix(ai,durable): keep a value that is not JSON an error when a message or a nested result is encodedPiG's own defectAll platformsUnreleased
BF-fa3da97A wake the scheduler took at a change is still delivered to OnWake when the Session fails and seals before…PiG's own defectAll platforms0.5.2
BF-fa3da97-10A model reply with many tool calls no longer slows down as calls finish: finished calls are not parsed again…PiG's own defectAll platforms0.5.2
BF-fa3da97-11Durable task-graph and view watches no longer send or rebuild the whole graph or view on each commit: a…PiG's own defectAll platforms0.5.2
BF-fa3da97-12MCP: a server's tools, the tools it stopped offering, and the resource tools now reach the session as one…PiG's own defectAll platforms0.5.2
BF-fa3da97-13On the Cloudflare Durable host, an AsyncLocalStorage value set around a call stays visible in the work that…PiG's own defectAll platforms0.5.2
BF-fa3da97-14A Durable Session whose commit was cancelled while it waited for the Session line now closes instead of…PiG's own defectAll platforms0.5.2
BF-fa3da97-15An extension that registers many tools after load (pi.registerTool) no longer slows down with each…PiG's own defectAll platforms0.5.2
BF-fa3da97-16A context event with several extension handlers is encoded once per request instead of once per handler…PiG's own defectAll platforms0.5.2
BF-fa3da97-17Rust SDK: a context handler that returns a new list equal to the one it received now counts as a…PiG's own defectAll platforms0.5.2
BF-fa3da97-18A Durable core session with many live tasks recomputes its next wake without walking every live task on each…PiG's own defectAll platforms0.5.2
BF-fa3da97-19Idle and typing frames in a long interactive session no longer redraw from the header down: a frame costs the…PiG's own defectAll platforms0.5.2
BF-fa3da97-2env/daemon: listFiles with timeoutMs 0 truncates at once on Windows too; the deadline check no longer…PiG's own defectWindows0.5.2
BF-fa3da97-20Markdown with many inline $ signs renders in linear time, as in PiPiG's own defectAll platforms0.5.2
BF-fa3da97-21Extension SDKs: reading the session branch, an entry, a label or the leaf entry right after a new entry is…PiG's own defectAll platforms0.5.2
BF-fa3da97-22Choosing a bundled model no longer builds every model of its provider on each request, and providers in…PiG's own defectAll platforms0.5.2
BF-fa3da97-23chord/host: a host no longer sends on another host's connection while delivering notifications, and a…PiG's own defectAll platforms0.5.2
BF-fa3da97-24A Durable code mode tool call made just before the script launches another call or ends has started before…PiG's own defectAll platforms0.5.2
BF-fa3da97-25Fixed pig piglet build failing on a Piglet that carries a single-file extension (a lone .ts or .js…PiG's own defectAll platforms0.5.2
BF-fa3da97-26A provider stream could stop for good, with the turn waiting for events that never came, when the goroutine…PiG's own defectWindows, macOS, Linux0.5.2
BF-fa3da97-27PiG no longer hangs for 30 seconds at exit when two extensions share a Node process and one is still waiting…PiG's own defectAll platforms0.5.2
BF-fa3da97-28A Node extension whose factory failed, because it has no default export or throws while initializing, could…PiG's own defectAll platforms0.5.2
BF-fa3da97-29A tool that streams a growing partial result no longer sends the whole result to each subscribed extension on…PiG's own defectAll platforms0.5.2
BF-fa3da97-3done() of an overlay ui.custom component hides that component's own overlay instead of the topmost onePiG's own defectAll platforms0.5.2
BF-fa3da97-30A Durable core request costs the same however many requests are in flight: a notice is tested only against…PiG's own defectAll platforms0.5.2
BF-fa3da97-31Durable tool calls that the model issues in parallel or nested no longer slow down as the live state grows: a…PiG's own defectAll platforms0.5.2
BF-fa3da97-32With an extension turn_end handler, each tool result no longer copies the whole session history, and a…PiG's own defectAll platforms0.5.2
BF-fa3da97-33A Durable reference harness turn no longer copies the whole conversation history to extend its contextPiG's own defectAll platforms0.5.2
BF-fa3da97-34Durable: Submission.Wait, WaitForIdle and WaitForTask return only after every event-stream listener has…PiG's own defectAll platforms0.5.2
BF-fa3da97-35Selecting a go.work member module that is itself an exact factory package resolves that module's factory…PiG's own defectAll platforms0.5.2
BF-fa3da97-36A --print or --mode json run that SIGTERM or SIGHUP reaches while its bash tool is starting the shell no…PiG's own defectAll platforms0.5.2
BF-fa3da97-37chord/host: the Node facet loader no longer writes its child entry under one fixed name in the shared…PiG's own defectAll platforms0.5.2
BF-fa3da97-38A Durable core listing of all or terminal tasks (ScanTasks) reads one page from storage per call instead of…PiG's own defectAll platforms0.5.2
BF-fa3da97-39Edit diffs and the edit tool with many replacements on a large file use far less memory and time: a…PiG's own defectAll platforms0.5.2
BF-fa3da97-4A call through a remote Chord reference with a ctx from another host, or one not made by Chord, fails with…PiG's own defectAll platforms0.5.2
BF-fa3da97-40An extension that set its header or footer, or registered a sprite or a login, from its own goroutine painted…PiG's own defectAll platforms0.5.2
BF-fa3da97-41The terminal width and height the extension host tells extensions during /reload, and when one starts or…PiG's own defectAll platforms0.5.2
BF-fa3da97-42Deleting members of a large Durable core JSON object no longer costs time proportional to its size for each…PiG's own defectAll platforms0.5.2
BF-fa3da97-5Turning thinking off on Claude Haiku 5.5 and Opus 5 sends plain thinking: {type: "disabled"} without effort…PiG's own defectAll platforms0.5.2
BF-fa3da97-6The retry classifier treats "The service is temporarily unavailable" and Bedrock's "Service unavailable: ..."…PiG's own defectAll platforms0.5.2
BF-fa3da97-7A Progress pause longer than Node's timer maximum, or not a number, runs after 1 ms as a Node timer doesPiG's own defectAll platforms0.5.2
BF-fa3da97-8Anthropic and Claude on Bedrock no longer lose the images of an error tool result: Pi sends them after the…PiG's own defectAll platforms0.5.2
BF-fa3da97-9A Durable tool call aborted while the Harness was still resolving the tool to run could stay running for…PiG's own defectAll platforms0.5.2
BF-fac4703The extension editor dialog (ctx.ui.editor(), /tree summary prompt) shows the hint keys the user bound…PiG's own defectAll platforms0.5.0
BF-fac4703-2TestSettingsSelectorMatchesPi walks every row under rebound, multi-key and unbound app.message.followUp /…PiG's own defectAll platforms0.5.0
BF-fac4703-3TestSelectListInputMatchesPi also runs under the Kitty keyboard protocol (CSI-u enter, escape, ctrl+j…PiG's own defectAll platforms0.5.0
BF-fba9b47fix(extension): hold the theme colors map the wire palette memo is keyed onPiG's own defectAll platformsUnreleased
BF-fbd3640The legacy credential migration writes auth.json and the rewritten settings.json with the key order Pi…PiG's own defectAll platforms0.5.0
BF-fc5baafpi.exec's timeout starts once the command has been spawned, as in Pi, so time spent finding and starting…PiG's own defectAll platforms0.5.0
BF-fc749adA task list item whose first line looks like a heading, quote, rule, bullet, fence, table row or HTML tag…PiG's own defectAll platforms0.5.0
BF-fc886e3Google and Vertex request bodies list their members in the order the @google/genai SDK writes them, also…PiG's own defectAll platforms0.5.0
BF-fce99d3The "Your first ten minutes" and Quickstart pages now describe the first-time setup screen (theme, sprite and…PiG's own defectAll platforms0.5.0
BF-fce99d4An npm or git -e source that fails to install stops startup and /reload before any extension factory…PiG's own defectAll platforms0.5.0
BF-fd4be86OpenAI-compatible Chat Completions keeps the "text":"" member of a user text part whose text is only a lone…PiG's own defectAll platforms0.5.0
BF-fd7f660A resumed session keeps the tools that were active when it was saved (an optional or deferred tool a command…PiG's own defectAll platforms0.5.0
BF-fdfb748A surrogate pair split across two concatenated string halves is written as its character in session files…PiG's own defectAll platforms0.5.0
BF-fe16108Provider request bodies (OpenAI Completions, OpenAI Responses, Codex, Anthropic, Google and Mistral) write…PiG's own defectAll platforms0.5.0
BF-fe625bbMistral request bodies list their members in Pi's order (model, stream, messages, ..., then the renamed…PiG's own defectAll platforms0.5.0
BF-fe625bb-2When an extension's before_provider_request handler returns a replacement body for OpenAI Completions…PiG's own defectAll platforms0.5.0
BF-ffcd800Text after an empty heading follows the same space-token rule as after any other block: blank lines or spaces…PiG's own defectAll platforms0.5.0
BF-ffeb469A long session keeps less memory for each turn it has compacted awayPiG's own defectAll platforms0.5.0

Entries

BF-003f6f0

Ctrl+U, Ctrl+K and the word-delete keys leave an open autocomplete list open, as Pi's editor does

  • Source: PiG's own defect
  • Symptom: Ctrl+U, Ctrl+K and the word-delete keys leave an open autocomplete list open, as Pi's editor does.
  • Root cause: deleteToStartOfLine, deleteToEndOfLine, deleteWordBackwards and deleteWordForward (tui editor.ts 1624-1700) change the text and call onChange only; the list is updated by insertCharacter, handleBackspace, handleForwardDelete and moveCursor. PiG refreshed it after each kill, closing the popup Pi leaves. Found by the skills journey.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 003f6f0e70 (2026-10-09)
  • Tests: tui/editor_kill_keeps_autocomplete_test.go::TestKillCommandsLeaveTheOpenAutocompleteList
  • Release note: Ctrl+U, Ctrl+K and the word-delete keys leave an open autocomplete list open, as Pi's editor does.

BF-007c91d

Go, Python and Rust extensions: in a before_agent_start handler, the event's systemPrompt and the…

  • Source: PiG's own defect
  • Symptom: Go, Python and Rust extensions: in a before_agent_start handler, the event's systemPrompt and the context's system prompt getter now return the prompt the shared options render when read, as Pi and Node do.
  • Root cause: Each SDK now holds the prompt the host rendered for the options the handler received and, once the options differ, asks the host to render them with renderSystemPrompt, reusing the result until they change again: - Go: data["systemPrompt"] is an *sdk.SystemPrompt (Get, MarshalJSON), since a map cannot run a getter; ctx.GetSystemPrompt() returns the same value. - Python: the event dict's "systemPrompt" and ctx.get_system_prompt() render. The Host answers renderSystemPrompt without a UI bridge too (an SDK session): the render is a pure function of the options the call carries. Callers of the Go string form move to Get(); the known gaps are deleted; pig-go.json is regenerated by gointerfaces.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 007c91dcbb (2026-10-13), 2a055a7a3a (2026-10-13)
  • Tests: coding/extension/host/subprocess/ui_bridge_test.go::TestInteractiveDialogOpensOverFocusedOverlay, coding/extension/host/subprocess/ui_prompt_test.go::TestUIBridgePromptScopeDialogOverCustomAndNoUI, extensions/sdk-py/tests/test_system_prompt_render.py, extensions/sdk/system_prompt_render_test.go::TestBeforeAgentStartSystemPromptRenderErrorIsTheReadersError, extensions/sdk/system_prompt_render_test.go::TestBeforeAgentStartSystemPromptRendersTheCurrentOptions, internal/codingagent/ext_ui_dialog_focus_test.go::TestExtensionDialogTakesFocusFromFocusedCustomOverlay, internal/codingagent/ext_ui_dialog_focus_test.go::TestExtensionDialogTakesTerminalFromFocusedRemoteOverlay, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult and 9 more in docs/bug-ledger.json
  • Release note: Go, Python and Rust extensions: in a before_agent_start handler, the event's systemPrompt and the context's system prompt getter now return the prompt the shared options render when read, as Pi and Node do. A handler sees its own edit to systemPromptOptions at once, a later handler sees it, and after a handler returns systemPrompt later handlers read exactly that prompt; before, they read the prompt rendered before the handler ran, and the context getter returned the Session's prompt.

BF-02114ff

fix(tui): filterable list drops the label when no cell remains (Pi select-list)

  • Source: PiG's own defect
  • Symptom: fix(tui): filterable list drops the label when no cell remains (Pi select-list)
  • Root cause: select-list.ts renderItem computes maxWidth = width - 2 - 2 without a floor and truncateToWidth returns "" for maxWidth <= 0; Pig's FilterableList floored it at 1, drawing one label cell at widths 3-4. TestFilterableListShowsNoLabelWhenNoCellRemains is red without the fix.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 02114fff5b (2026-10-11)
  • Tests: internal/codingagent/settings_selector_oracle_test.go::TestSettingsSelectorMatchesPi, tui/select_filterable_test.go::TestFilterableListShowsNoLabelWhenNoCellRemains

BF-0225a43

A device-attributes reply delivered in slow reads no longer reaches the editor as input: an ESC[? prefix…

  • Source: A Pi defect that PiG fixed first (divergence D143)
  • Symptom: A device-attributes reply delivered in slow reads no longer reaches the editor as input: an ESC[? prefix waits for its final byte (D143, Pi #10657).
  • Root cause: No part of a split DA1 reply reaches input, and the color query's DA1 is forwarded whole. A bare ESC[ is still replayed after 150 ms (Pi's terminal.test.ts); the owed-reply absorber now covers only that case.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10657
  • Removed when Pi fixes it: Pi keeps a split private CSI reply buffered until its final byte.
  • Fix commits: 0225a43aa5 (2026-10-10), 35e46b114e (2026-10-10), 6ecf8a6a81 (2026-10-10)
  • Tests: codemode/nested_error_unreadable_test.go::TestNestedErrorsWithUnreadableNamesSerializeAsPiDoes, codemode/sandbox_test.go::TestEmbeddedSourcesMatchTheirRecordedHashes, tui/terminal_input_regression_test.go::TestTerminalNegotiationPrefixDeadlineBoundaries, tui/terminal_input_regression_test.go::TestTerminalNegotiationPrivateCSIPrefixWaitsForItsFinalByte, tui/terminal_split_reply_test.go::TestForwardTerminalConsumesAnOwedReplySplitAcrossSlowReads, tui/terminal_split_reply_test.go::TestReassemblesDelayedDA1RepliesForKeyboardAndColorQueries
  • Release note: A device-attributes reply delivered in slow reads no longer reaches the editor as input: an ESC[? prefix waits for its final byte (D143, Pi #10657).

BF-0225bbf

Pressing Escape twice with only whitespace in the editor now opens the tree or fork selector, as in Pi, which…

  • Source: PiG's own defect
  • Symptom: Pressing Escape twice with only whitespace in the editor now opens the tree or fork selector, as in Pi, which trims the editor text before the double-Escape check.
  • Root cause: onEscape runs the double-Escape shortcut when !editor.getText().trim(). PiG required an editor with no text at all.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0225bbfb95 (2026-10-13)
  • Tests: internal/codingagent/interactive_test.go::TestInteractiveMode_EscapeArmsTheDoubleEscapeOverAWhitespaceOnlyEditor
  • Release note: Pressing Escape twice with only whitespace in the editor now opens the tree or fork selector, as in Pi, which trims the editor text before the double-Escape check.

BF-022d99f

fix(tui): the login dialog's input renders at every prompt position

  • Source: PiG's own defect
  • Symptom: fix(tui): the login dialog's input renders at every prompt position
  • Root cause: login-dialog.ts adds the same Input to the content container for each prompt and replaceInputWithSubmittedText replaces every occurrence; a cancel leaves it in place. Pig rendered only the newest slot and blank for the others.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 022d99fb6d (2026-10-10)
  • Tests: None recorded.

BF-037b2e3

A session message whose members are in another order than Pi writes them, or that holds members PiG does not…

  • Source: PiG's own defect
  • Symptom: PiG rewrote such a message in its own member order and dropped unknown members.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e8cc072521 (2026-10-08), 433d232815 (2026-10-09), 037b2e327b (2026-10-10), 3b5673d924 (2026-10-10), 63222832e9 (2026-10-10), f9ef071708 (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/message_shape_test.go::TestMemberScanAcceptsMessagesPiWrites, agent/message_shape_test.go::TestMemberScanAcceptsOnlyTextTheFieldsEncodeTo, agent/message_source_test.go::TestDecodedMessageInPiOrderRetainsNoText, agent/message_source_test.go::TestDecodedMessageWritesTheObjectItWasDecodedFrom, agent/message_source_test.go::TestWithEmptyContentSpreadsContent and 19 more in docs/bug-ledger.json
  • Release note: A session message whose members are in another order than Pi writes them, or that holds members PiG does not model, now reaches RPC get_messages, extension events and the built session context exactly as Pi returns it: the object parsed from the file, written with JSON.stringify. Before, PiG rewrote such a message in its own member order and dropped unknown members. Context edits and handler replacements without content keep the content member in place, as the JavaScript spread does. A system message without content is kept and gets empty content, as in Pi; before, PiG dropped it. Messages Pi wrote are unaffected and retain no extra memory.

BF-03ad544

fix(tui): card of an unregistered tool is Pi's formatToolExecution

  • Source: PiG's own defect
  • Symptom: fix(tui): card of an unregistered tool is Pi's formatToolExecution
  • Root cause: tool-execution.ts updateDisplay draws a tool without a definition (hasRendererDefinition false, e.g. a hallucinated tool name) as one Text: toolTitle name, a blank line, JSON.stringify(args, null, 2) and the ANSI-stripped result on the next line. Pig drew a compact 'x:1' summary (with sorted keys, cut at 40/80 chars) and a blank row before the result. Found by running scripted replies through the real Pi and Pig (mock OpenAI-completions server, tmux, session files compared too). TestUnregisteredToolCardIsUpstreamFormatToolExecution is red without the change; the two component-only tests that used a definition-less card for a registered tool now give it a definition.…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 03ad544995 (2026-10-11)
  • Tests: tui/summary_output_pad_test.go::TestToolAndBashBlocksFollowOutputPad, tui/tool_execution_test.go::TestToolExecutionStreamingFallbackUsesLeadingPreview, tui/tool_execution_test.go::TestUnregisteredToolCardIsUpstreamFormatToolExecution

BF-040088e

ai.CloudflareStreams drops the deferred fetch and cancel operations of the API it wraps, as Pi's…

  • Source: PiG's own defect
  • Symptom: It passed them through without materializing the Cloudflare endpoint placeholders.
  • Root cause: Pi's cloudflareStreams (providers/cloudflare-stream.ts:21-29) returns an object with only stream and streamSimple. ProviderStreams declares fetchDeferred and cancelDeferred as optional members (types.ts:292-305), so the wrapper drops them and Models reports 'does not support deferred responses' (models.ts:925-927). Go copied the wrapped value, so the deferred operations passed through, and they ran without the Cloudflare placeholders materialized. An earlier change pinned the pass-through and said those operations have no Pi counterpart.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 040088eba4 (2026-10-10)
  • Tests: ai/classifier_http_error_name_test.go::TestClassifierHTTPErrorNameDistinguishesTimeoutFromHTTPFailure, ai/cloudflare_streams_test.go::TestCloudflareStreamsDropsDeferredOperationsAndLeavesTheWrappedValueUntouched
  • Release note: ai.CloudflareStreams drops the deferred fetch and cancel operations of the API it wraps, as Pi's cloudflareStreams does, so a Cloudflare-wrapped API reports that it does not support deferred responses. Before, it passed them through without materializing the Cloudflare endpoint placeholders.

BF-04ef0ac

Closing a Chord host no longer hangs when a hold arrives over a link that closes at the same time

  • Source: PiG's own defect
  • Symptom: Closing a Chord host no longer hangs when a hold arrives over a link that closes at the same time.
  • Root cause: Close's last snapshot of the objects goes through loop.run, which runs ahead of the tasks posted before it and drains them after. A hold the link had handed to the loop before it closed could therefore run after that snapshot and after the connection had released everything it held (Connection.closed). It created an object nothing disposed and nothing released, and the open of its resource, which waits for the abort a disposal raises, kept waitTasks blocked for good: windows native-4, TestAwaitingAGetTimesOutAfterReadyTimeoutWithReadyTimeoutAndReleasesItsHold, Close in waitTasks for 29 minutes with the gate's open in its select for as long.
  • User impact: Crash, hang or runaway process
  • Platform: Windows
  • Fixed in: Unreleased
  • Fix commits: 04ef0acc60 (2026-10-23)
  • Tests: chord/host/awaiting_upstream_test.go::TestAwaitingAGetTimesOutAfterReadyTimeoutWithReadyTimeoutAndReleasesItsHold, chord/host/close_queued_hold_test.go::TestCloseDropsAHoldQueuedBeforeItsLinkClosed
  • Release note: Closing a Chord host no longer hangs when a hold arrives over a link that closes at the same time. A frame the link had handed to the host loop before it closed could run after Close took its last look at the host's objects and after the connection had released what it held, so it created an object nothing disposed or released, and Close waited for that object's resource open for good (seen as a 30 minute chord/host test timeout on Windows after a ready_timeout get). A connection now ignores frames once its link closed, as Pi's link delivers none.

BF-051a79d

A Provider object registered by an extension (pi.registerProvider(provider)) now receives the session's…

  • Source: PiG's own defect
  • Symptom: A Provider object registered by an extension (pi.registerProvider(provider)) now receives the session's onResponse and onProviderStreamEvent callbacks in its stream options, as in Pi, so after_provider_response and provider_stream_event fire for its requests.
  • Root cause: Pi's session passes onPayload, onResponse and onProviderStreamEvent to modelRuntime.streamSimple (sdk.ts:385-433), and prepareRequest hands them to Provider.stream while it strips transformHeaders (model-runtime.ts: 671-689).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 051a79d333 (2026-10-09), 33ce911946 (2026-10-11, #206)
  • Tests: coding/extension/host/subprocess/native_provider_request_callbacks_test.go::TestNativeProviderSessionRequestHooks, coding/extension/host/subprocess/native_stream_callback_test.go::TestNativeStreamCallbackDispatchesByName, test/extension-conformance/provider_object_test.go::TestProviderObjectsAcrossSDKs, test/extension-conformance/provider_on_response_test.go::TestNativeProviderStreamsForwardOnResponse
  • Release note: A Provider object registered by an extension (pi.registerProvider(provider)) now receives the session's onResponse and onProviderStreamEvent callbacks in its stream options, as in Pi, so after_provider_response and provider_stream_event fire for its requests. The host answers each Provider callback by its name; before, every callback was treated as onPayload. A Go caller's StreamOptions.OnResponse reaches the Provider from ModelRuntime.Stream and StreamSimple the same way: the observer gets the response status, headers and model, the Provider awaits it, and an observer error ends the stream with that error. Reported by @JihongWEI, whose reproduction is now a regression test (#206).

BF-051a79d-2

A Provider object's stream options no longer carry a stray hasOnPayload value

  • Source: PiG's own defect
  • Symptom: A Provider object's stream options no longer carry a stray hasOnPayload value.
  • Root cause: Pi's session passes onPayload, onResponse and onProviderStreamEvent to modelRuntime.streamSimple (sdk.ts:385-433), and prepareRequest hands them to Provider.stream while it strips transformHeaders (model-runtime.ts: 671-689).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 051a79d333 (2026-10-09)
  • Tests: coding/extension/host/subprocess/native_provider_request_callbacks_test.go::TestNativeProviderSessionRequestHooks, coding/extension/host/subprocess/native_stream_callback_test.go::TestNativeStreamCallbackDispatchesByName, test/extension-conformance/provider_object_test.go::TestProviderObjectsAcrossSDKs
  • Release note: A Provider object's stream options no longer carry a stray hasOnPayload value.

BF-053d96d

RPC mode: when an extension command calls pi.sendUserMessage() or `pi.sendMessage(..., { triggerTurn: true…

  • Source: PiG's own defect
  • Symptom: PiG used to emit agent_start, turn_start and the run's first messages before the response.
  • Root cause: In Pi an extension command that calls pi.sendUserMessage() without awaiting it starts prompt() inside the handler: prompt() awaits _runInputHandlers and checkAuth, then emitBeforeAgentStart calls the before_agent_start handlers (agent-session.ts:1968-2112, 2116-2140), while the command's await command.handler() and await this._tryExecuteExtensionCommand(text) write the prompt response two microtasks after the handler returned (rpc-mode.ts:394-413). The run's preparation starts before the response, and agent_start follows it. Under D139 (Pi PR #10739) an idle triggerTurn sendMessage calls _startRun -> _prepareRun -> emitBeforeAgentStart before its first await, so its before_agent_start…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 053d96d1af (2026-10-12)
  • Tests: coding/cli/rpc_command_send_order_test.go::TestRPCCommandTurnStartOrderMatchesPi, coding/cli/rpc_command_send_order_test.go::TestRPCInlineCommandTurnStartOrderMatchesPi, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-switch.toml and 2 more in docs/bug-ledger.json
  • Release note: RPC mode: when an extension command calls pi.sendUserMessage() or pi.sendMessage(..., { triggerTurn: true }) without awaiting it, the run's before_agent_start handlers run before the command's prompt response, and agent_start follows the response, as in Pi. PiG used to emit agent_start, turn_start and the run's first messages before the response.

BF-056bbfb

Key matching follows Pi for three terminal inputs

  • Source: PiG's own defect
  • Symptom: Key matching follows Pi for three terminal inputs.
  • Root cause: A differential test runs Pi's parseKey and matchesKey in Node (testdata/keys.mjs, from the pinned .upstream/current source, WT_SESSION and SSH unset) over 7570 inputs with the Kitty protocol off and on: every legacy byte and ESC-prefixed byte, SS3/CSI cursor, editing and function sequences with modifiers, xterm modifyOtherKeys, Kitty CSI-u with shifted and base-layout alternates, modifier masks with lock bits, event types and text fields, and truncated and bit-flipped variants, each against 660 key ids. parseKey agreed on every input; matchesKey differed on 144, in three ways, all fixed in key_match.go: - keys.ts matchesKey returns false for 'escape' or 'esc' with any modifier (:832); Pig…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e8cc072521 (2026-10-08), 056bbfb74e (2026-10-09), 2c04c1445a (2026-10-09), 7c4d874438 (2026-10-09), da99854d1b (2026-10-09)
  • Tests: ai/authorization_input_oracle_test.go::TestAuthorizationInputParsersMatchPi, ai/githubcopilot_oracle_test.go::TestGitHubCopilotHelpersMatchPi, chord/delta/own_key_order_test.go::TestDiffRevisionsVisitsArrayIndexKeysFirstInNumericOrder, coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, internal/chord/chordjson/own_key_order_test.go::TestCopyFollowsPisKeyOrderAndMessages, tui/keys_oracle_test.go::TestParseAndMatchKeyMatchPiOnSeededInput
  • Release note: Key matching follows Pi for three terminal inputs. A modified escape (ctrl+escape, shift+alt+esc) matches no key binding, as in Pi, although the same input still parses to that name. An xterm modifyOtherKeys sequence (ESC [ 27 ; mod ; code ~) matches only when its modifier is exactly the binding's, so a Caps Lock or Num Lock bit no longer matches. An unmodified letter, digit or symbol binding no longer matches a modifyOtherKeys sequence.

BF-058e7a0

The Go, Rust and Python SDKs export Pi's compaction helpers calculateContextTokens, shouldCompact…

  • Source: PiG's own defect
  • Symptom: The Go, Rust and Python SDKs export Pi's compaction helpers calculateContextTokens, shouldCompact, getLastAssistantUsage, estimateTokens, findCutPoint, findTurnStartIndex and DEFAULT_COMPACTION_SETTINGS; the Rust SDK also exports UsageInfo.
  • Root cause: Pi's index.ts exports calculateContextTokens, shouldCompact, getLastAssistantUsage and DEFAULT_COMPACTION_SETTINGS (core/compaction/compaction.ts) for extensions. Only the Node SDK, which runs Pi's own bundle, had them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 058e7a0e4b (2026-10-11)
  • Tests: test/extension-conformance/compaction_usage_test.go::TestConformance_CompactionUsageHelpersAgreeAcrossSDKs
  • Release note: The Go, Rust and Python SDKs export Pi's compaction helpers calculateContextTokens, shouldCompact, getLastAssistantUsage, estimateTokens, findCutPoint, findTurnStartIndex and DEFAULT_COMPACTION_SETTINGS; the Rust SDK also exports UsageInfo.

BF-0630078

MCP tool calls no longer fail with "Invalid MCP tools/call structured content" when the server returns…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: MCP tool calls no longer fail with "Invalid MCP tools/call structured content" when the server returns structured content that is not an object (an array, null, or a primitive, as MCP 2026-07-28 allows), as Pi 67996ab7 now does.
  • Root cause: Ports Pi packages/mcp/src/client.ts validateCallToolResult: the "Invalid MCP tools/call structured content" rejection of a non-object is gone, so an array, null or a primitive reaches the caller (CallToolResult.structuredContent is any JSON value, protocol/content.ts).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0630078ccf (2026-10-12), 3fa6e63e8e (2026-10-12)
  • Tests: coding/mcpext/cli_upstream_test.go::TestMcpCommandListReportsThatAServerOffersResourcesWithoutCountingThem, coding/mcpext/connection_test.go::TestMCPConnectionsConnectsToServersWithResourcesWithoutListingThem, coding/mcpext/responsive_test.go::TestMcpDescribeStateShowsThatAServerOffersResources, coding/mcpext/tools_test.go::TestMCPToolsConvertsResultsPassingTheCallToolResultToScriptsAndFlaggingErrors, mcp/client_test.go::TestClientAcceptsStructuredContentThatIsNotAnObject, mcp/content_test.go::TestToLLMContentFallsBackToTheStructuredContentAsJSONWhenThereAreNoBlocks
  • Release note: MCP tool calls no longer fail with "Invalid MCP tools/call structured content" when the server returns structured content that is not an object (an array, null, or a primitive, as MCP 2026-07-28 allows), as Pi 67996ab7 now does.

BF-0631c73

Go, Python and Rust extensions now draw an overlay component (ctx.custom with overlay: true) at the…

  • Source: PiG's own defect
  • Symptom: Go, Python and Rust extensions now draw an overlay component (ctx.custom with overlay: true) at the overlay's width, 80 columns by default, as Pi and Node extensions do.
  • Root cause: Pi renders a ctx.ui.custom overlay component at the width TUI.resolveOverlayLayout resolves (tui.ts:1212-1233; parseSizeValue tui.ts:228-237): min(80, available) by default, or overlayOptions width, minWidth and margin. The Node runtime already did this (runtime.mjs resolveOverlayWidth). The Go, Python and Rust SDKs rendered the component at the terminal width, so the robust-notes picker drew "robust pick (100)" in a 100-column pane where Pi draws "(80)", and a full-width line was cropped at the overlay's edge.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0631c73a3f (2026-10-09), 7489735c3f (2026-10-09)
  • Tests: coding/extension/host/subprocess/builder_node_entry_test.go::TestResolveNodeEntrypointImportsAnIndexMjsDirectoryAsJiti, extensions/sdk-py/tests/test_overlay_width.py, extensions/sdk/overlay_width_test.go::TestRemoteRenderWidthFollowsUpstreamOverlayLayout, test/extension-conformance/api_append_entry_test.go::TestConformance_ExtensionAPIAppendEntry, test/extension-conformance/overlay_width_conformance_test.go::TestConformance_OverlayRenderWidth
  • Release note: Go, Python and Rust extensions now draw an overlay component (ctx.custom with overlay: true) at the overlay's width, 80 columns by default, as Pi and Node extensions do. They drew it at the full terminal width, so a full-width line was cut off at the overlay's edge.

BF-064181f

fix(extension): hold the theme colors map the wire palette memo is keyed on

  • Source: PiG's own defect
  • Symptom: fix(extension): hold the theme colors map the wire palette memo is keyed on
  • Root cause: ActiveExtensionThemeWire identified the terminal-resolved colors by the address of the map theme.Colors returned, stored as a uintptr. The theme drops that map when the terminal's reported colors change, so the collector could free it and give its address to a later colors map with other values; the memo would then return the old palette and the state push would skip the theme as unchanged. The memo now holds the map it is keyed on, so the address cannot be reused while it identifies the palette.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 064181fd0a (2026-10-11)
  • Tests: internal/codingagent/ext_ui_theme_palette_test.go::TestActiveExtensionThemeWireHoldsTheColorsMapItIsKeyedOn

BF-07151d0

In print and JSON mode, Node extensions in separate processes now keep Pi's order when a later extension's…

  • Source: PiG's own defect
  • Symptom: In print and JSON mode, Node extensions in separate processes now keep Pi's order when a later extension's session_shutdown or agent_settled handler awaits a timer, however busy the machine is.
  • Root cause: an ordering assumption, not a lost frame. Pi runs every extension on one event loop, so while the later handler awaits its timer the immediate and the due timer the earlier handler queued run first, whatever the OS does (runner.ts:1096-1123). With separate Node processes the yielding runtime sent loop_turned and went on at once ("Nothing waits for the notify"), so the earlier process's callbacks ran before the later handler's continuation only if that process was scheduled, read the forwarded turn, and reached its check and timers phases within the later handler's 100 ms timer. On a loaded host it was not: the later handler answered first, the host invalidated, and the earlier callbacks ran…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 07151d01a7 (2026-10-13)
  • Tests: coding/extension/host/subprocess/loop_turned_forward_test.go::TestLoopTurnIsTakenOnlyOnceEveryRecipientTookIt, coding/extension/host/subprocess/loop_turned_forward_test.go::TestLoopTurnedReachesOnlyOtherNodeProcesses, coding/extension/host/subprocess/quit_handler_yield_test.go::TestPrintModeQuitHandlerReportsItsYield, coding/extension/host/subprocess/runtime_node_loop_turn_stall_test.go::TestNodeLoopTurnWaitsUntilAStalledProcessTookIt, coding/extension/host/subprocess/runtime_node_prompt_end_test.go::TestNodePromptEndWaitEndsWhenAnotherProcessTurnsTheLoop
  • Release note: In print and JSON mode, Node extensions in separate processes now keep Pi's order when a later extension's session_shutdown or agent_settled handler awaits a timer, however busy the machine is. The immediate and the timer an earlier extension's handler queued ran before that handler resumed only if the earlier process read the forwarded loop_turned in time; a process the OS kept waiting ran them after the handler answered, with a stale ctx once the runner was invalidated. The yielding process now stays in its turn until every other Node process reports it took the turn (loop_turn_taken), after the immediates and due timers it queued before the turn ran.

BF-0790d5e

String.prototype.trim vs strings.TrimSpace: four Pi .trim() sites used Go's strings.TrimSpace, which…

  • Source: PiG's own defect
  • Symptom: String.prototype.trim vs strings.TrimSpace: four Pi .trim() sites used Go's strings.TrimSpace, which trims U+0085 and not U+FEFF where JavaScript trims U+FEFF and not U+0085 (jsstring.Trim is the JS rule, already used in 100 other places).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0790d5eae8 (2026-10-11)
  • Tests: agent/js_trim_test.go::TestReplayAndProxyLinesTrimLikeJavaScript, tui/js_trim_test.go::TestAssistantMessageComponentTrimsRunsLikeJavaScript, tui/js_trim_test.go::TestTextBlankCheckTrimsLikeJavaScript
  • Release note: String.prototype.trim vs strings.TrimSpace: four Pi .trim() sites used Go's strings.TrimSpace, which trims U+0085 and not U+FEFF where JavaScript trims U+FEFF and not U+0085 (jsstring.Trim is the JS rule, already used in 100 other places). Fixed where the difference is observable: Text blank check (tui/text.go, text.ts:57), assistant-message text/thinking runs (tui/assistant_message_block.go, assistant-message.ts:110-125: a leading BOM is now trimmed before the markdown renders, U+0085 is kept), replayed empty-thinking filter (agent/transform.go, transform-messages.ts:111) and the proxy data: line (agent/proxy.go, proxy.ts:181). Regressions TestTextBlankCheckTrimsLikeJavaScript, TestAssistantMessageComponentTrimsRunsLikeJavaScript, TestReplayAndProxyLinesTrimLikeJavaScript (each red with the old call, 3 sites checked by swapping the call back). parseStreamingJsonValue's guard (ai/streaming_json.go) was probed and left: the result is the same either way. Left for owners: stripThinkingSignatures (ai/anthropic.go, PiG's own D37 recovery, no Pi counterpart), the Pi .trim() sites in other providers if any still use TrimSpace. ./tui -race (1,219 s) failed only the flaky TestSurfaceRendersAnEditorBeforeAnythingAsksForItsTerminal (race in tui_surface_program_status_test.go:213, passes alone) and ./agent only TestAgentClassMatchesPi/TestAgentLoopMatchesPi (the oracle cannot import @earendil-works/pi-ai from durable/interop, a missing node_modules in this worktree).

BF-081e8c3

Bug report files against live Pi: new TestBugReportFilesMatchPi…

  • Source: PiG's own defect
  • Symptom: Bug report files against live Pi: new TestBugReportFilesMatchPi (internal/codingagent/bug_report_files_oracle_test.go; collectBugReportDiagnostics over a SessionManager opened on 500 generated session files plus a crash log, and bugReportFiles; bytes of diagnostics.json, member list, session.jsonl and summary.md).
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 081e8c34d2 (2026-10-11)
  • Tests: ai/diagnostic_empty_details_test.go::TestAssistantMessageDiagnosticKeepsAnEmptyDetailsObject, internal/codingagent/bug_report_files_oracle_test.go::TestBugReportFilesMatchPi
  • Release note: Bug report files against live Pi: new TestBugReportFilesMatchPi (internal/codingagent/bug_report_files_oracle_test.go; collectBugReportDiagnostics over a SessionManager opened on 500 generated session files plus a crash log, and bugReportFiles; bytes of diagnostics.json, member list, session.jsonl and summary.md). Two real fixes, both red first (128 of 500, then 13): (1) report.json and diagnostics.json went through json.Encoder, which writes U+2028/U+2029 as \u2028 where JSON.stringify writes them literally (and kept Go number text); bugReportJSON now canonicalises through stringifyIndented (internal/codingagent/bug_report.go); (2) a diagnostic's details: {} was dropped by omitempty (ai.AssistantMessageDiagnostic now has a MarshalJSON that keeps an empty, non-nil Details; regression TestAssistantMessageDiagnosticKeepsAnEmptyDetailsObject). 14 mutations red (crash notified strip needed the crash corpus to be a JSON array, as readCrashLog expects). Findings left for a ruling (questions/lg-help-4.md): PiG's AssistantMessage cannot tell an empty errorMessage/rawStopReason from an absent one (Pi keeps ""), and a diagnostic's details is a Go map, so non-index key order is sorted, not insertion order; both are excluded from the corpus and named in the test comment. Not compared: report.json content (metadata tests), the zip container (Pi writes a classic layout, Go's archive/zip a data-descriptor layout; the members' content is what a reader sees).

BF-082355f

The session picker checks its keys in Pi's order when bindings overlap (delete before rename, list actions…

  • Source: PiG's own defect
  • Symptom: The session picker checks its keys in Pi's order when bindings overlap (delete before rename, list actions up, down, page up, page down, confirm, cancel), and Enter typed into the search box selects the highlighted session even when tui.select.confirm is rebound.
  • Root cause: (1) tui.input.submit (ctrl+s here) must select through the search Input's onSubmit -> selectList.handleInput("\r"); Pig's host-owned TextInput only marked itself done. (2) Pig re-fired onSelect/onCancel on every later navigation key because it read the list's sticky Done state; Pi fires once per confirm/cancel key (new FilterableList.Reset, called by the selector after it dispatches). Scenario settings/06 (/thinking, m Down Left d -> '> dm') is red on the old selector ('> md') and byte-equal now.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d1f258f335 (2026-10-06), 082355f166 (2026-10-07), 0ae3fcf17c (2026-10-07), 4d041349a7 (2026-10-07), c8a90a61db (2026-10-07), ccaade27af (2026-10-07)
  • Tests: internal/codingagent/extension_dialog_remapping_test.go::TestExtensionDialogRemappingThroughOwnerLoop, internal/codingagent/remote_overlay_production_review_test.go::TestRemoteOverlayProductionRejectsCachedTerminalWidth, internal/codingagent/session_selector_oracle_test.go::TestSessionSelectorInputMatchesPi, internal/codingagent/thinking_selector_oracle_test.go::TestThinkingSelectorInputMatchesPi, internal/codingagent/w3_pi_test.go::TestW3SessionPiComparison, test/parity/scenarios/settings/06-thinking-selector-search-editing.toml, test/parity/scenarios/settings/08-settings-search-editing.toml, tui/bordered_loader_oracle_test.go::TestBorderedLoaderMatchesPi and 12 more in docs/bug-ledger.json
  • Release note: The session picker checks its keys in Pi's order when bindings overlap (delete before rename, list actions up, down, page up, page down, confirm, cancel), and Enter typed into the search box selects the highlighted session even when tui.select.confirm is rebound.

BF-082f350

markdown.codeBlockIndent: Pi's getter is markdown?.codeBlockIndent ?? " ", so an authored "" means no…

  • Source: PiG's own defect
  • Symptom: markdown.codeBlockIndent: Pi's getter is markdown?.codeBlockIndent ?? " ", so an authored "" means no indent and an authored null (which survives the project merge and overrides the global value) reads the default.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), da99854d1b (2026-10-09), 082f350aff (2026-10-10), 8001cc51af (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/streaming_json_resume_oracle_test.go::TestStreamingArgumentsParserMatchesPiAtEveryChunk and 10 more in docs/bug-ledger.json
  • Release note: markdown.codeBlockIndent: Pi's getter is markdown?.codeBlockIndent ?? " ", so an authored "" means no indent and an authored null (which survives the project merge and overrides the global value) reads the default. MarkdownSettings.CodeBlockIndent was a plain string, so "" read as the two-space default (lg-review-i1 note on r3). Now *string with an authored-null flag (custom Unmarshal/Marshal), merged, cloned and encoded; the r3 oracle TestSettingsManagerGettersMatchPi now compares "" and null (red before: 4+ mismatches per 1,500 cases), regression TestSettingsKeepAnAuthoredEmptyOrNullCodeBlockIndent (load, project merge, merged and global encode); 8 mutations red; one equivalent (none remaining).

BF-086395a

coding.DefaultAgentDir() (Pi's getAgentDir) now returns the directory named by PIG_CODING_AGENT_DIR…

  • Source: PiG's own defect
  • Symptom: coding.DefaultAgentDir() (Pi's getAgentDir) now returns the directory named by PIG_CODING_AGENT_DIR (PI_CODING_AGENT_DIR with PIG_USE_PI_DIRS=1), tilde expanded, as the pig binary does; before, SDK embedders, --skill loading, the default skills directory and the interactive default ignored the variable and used $PIG_HOME/agent.
  • Root cause: coding.DefaultAgentDir() (the SDK entry point for Pi's getAgentDir) and the internal DefaultAgentDir it wraps returned $PIG_HOME/agent and ignored PIG_CODING_AGENT_DIR (PI_CODING_AGENT_DIR in shared mode), while the pig binary's AgentDir() read it: an embedder, --skill loading, DefaultSkillsDir and the interactive default named a different directory. The variable check moves into the one resolver; AgentDir and DefaultAgentDir are now the same function.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 086395a8bd (2026-10-11)
  • Tests: coding/services_test.go::TestDefaultAgentDirHonorsTheAgentDirVariable
  • Release note: coding.DefaultAgentDir() (Pi's getAgentDir) now returns the directory named by PIG_CODING_AGENT_DIR (PI_CODING_AGENT_DIR with PIG_USE_PI_DIRS=1), tilde expanded, as the pig binary does; before, SDK embedders, --skill loading, the default skills directory and the interactive default ignored the variable and used $PIG_HOME/agent.

BF-08bf076

An extension login callback (onPrompt, onSelect, onManualCodeInput) whose prompt fails for a reason…

  • Source: PiG's own defect
  • Symptom: Every failure reached the flow as a cancel.
  • Root cause:
    • the registry composed the provider's auth from the built-in catalog, not from the object, so its OAuth method, loginLabel and own API-key login were never offered. Pi's composeProvider takes the native provider as the base (model-runtime.ts:301-303 nativeExtensionProviders.get(id) ?? builtins.get(id)); registryAuthConfig now does the same, so the methods come from the retained provider object on every register, refresh and publication path; - the host carrier did not map auth.apiKey.login, auth.oauth.login or loginLabel. It now does, and their prompt and notify reverse calls decode into the closed AuthPrompt and AuthEvent unions (auth/types.ts); any other type fails the login with an…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 08bf0761c9 (2026-10-09, #201), 26d302a2d7 (2026-10-09, #201), da99854d1b (2026-10-09)
  • Tests: coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, coding/extension/host/subprocess/native_provider_login_test.go::TestDecodeNativeLoginCallback, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeAuthInteractionAnswersTheLoginDialog, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeProviderCarrierCarriesDeclaredLogins, coding/extension/host/subprocess/native_provider_oauth_test.go::TestNativeProviderOAuthLoginCallbacks, coding/extension/host/subprocess/oauth_legacy_callbacks_test.go::TestOAuthProxy_LoginRelaysTheLegacyCallbackSurface, internal/codingagent/native_provider_login_test.go::TestNativeProviderAPIKeyLoginRunsDeclaredLogin, internal/codingagent/native_provider_login_test.go::TestNativeProviderAPIKeyLoginWithoutDeclaredLoginUsesGenericPrompt and 3 more in docs/bug-ledger.json
  • Release note: An extension login callback (onPrompt, onSelect, onManualCodeInput) whose prompt fails for a reason other than the user's cancel now reports that error to the flow. Before, every failure reached the flow as a cancel.

BF-093cf5c

Provider error text is written the way Pi writes it

  • Source: PiG's own defect
  • Symptom: Provider error text is written the way Pi writes it.
  • Root cause: It found two differences in Pig, fixed here: - SafeJsonStringify wrote encoding/json text: U+2028 and U+2029 escaped, -0 as -0, integer-like keys sorted lexically. JSON.stringify writes them raw, 0, and integer keys first. It now passes the encoded text through jsonstringify.Canonicalize (JSON.parse then stringify of validated JSON, which keeps lone surrogates), so raw provider bodies (json.RawMessage) and decoded values give Pi's text. - truncateUTF16 (slice(0, limit)) dropped the first half of a surrogate pair when the cut fell inside it; JavaScript keeps the lone high surrogate, U+FFFD once encoded as UTF-8. It now keeps U+FFFD. Callers: TruncateErrorText, the Radius gateway text cap and…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 093cf5c781 (2026-10-09), 97e178f7f6 (2026-10-09)
  • Tests: ai/error_body_oracle_test.go::TestErrorBodyHelpersMatchPiOnSeededProbes, ai/streaming_json_oracle_test.go::TestStreamingJSONMatchesPiOnSeededInput
  • Release note: Provider error text is written the way Pi writes it. A JSON error body keeps U+2028 and U+2029 as they are instead of \u2028, writes -0 as 0, keeps lone surrogates, and lists integer-like keys first (JSON.stringify rules). Text that is cut to a length limit (error bodies, Radius gateway text, Pi message text) keeps a replacement character where the cut falls inside an emoji or other pair of UTF-16 units, as Pi's slice leaves half of the pair, instead of dropping the half.

BF-098e33d

Extension ctx.ui.theme.fg, bg and getBgAnsi throw Unknown theme color: <token> in every SDK for a…

  • Source: PiG's own defect
  • Symptom: Extension ctx.ui.theme.fg, bg and getBgAnsi throw Unknown theme color: <token> in every SDK for a token the palette lacks, as Pi's theme does; a theme with no palette still leaves text unstyled.
  • Root cause: renderHighlightedHtml formats each non-empty line of a scoped run on its own, and the CLI highlight theme colors string substitutions with the text color (utils/syntax-highlight.ts, theme.ts, #10143).
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 38b0f0c036 (2026-10-05), 098e33da91 (2026-10-06), 70d13f2863 (2026-10-06)
  • Tests: extensions/sdk/surface_parity_test.go::TestHasUIAndUIThemeFollowReplicatedState, extensions/sdk/ui_theme_style_test.go::TestUIThemeFgAndBgPanicForAnUnknownToken, test/extension-conformance/theme_test.go::TestThemeFaintForegroundAcrossSDKs, tui/highlight_test.go::TestCliHighlightThemeColorsSubstitutionsWithTheTextColor, tui/highlight_test.go::TestHighlightCode_ColorsEachLineOfPythonDocstringsIndependently, tui/syntax_highlight_upstream_test.go::TestSyntaxHighlightInterpolationEndsWithTheString, tui/syntax_highlight_upstream_test.go::TestSyntaxHighlightInterpolationTakesTheTextColor, tui/theme_unknown_token_test.go::TestThemeTextAttributeMethodsWrapInChalkPairs and 1 more in docs/bug-ledger.json
  • Release note: Extension ctx.ui.theme.fg, bg and getBgAnsi throw Unknown theme color: <token> in every SDK for a token the palette lacks, as Pi's theme does; a theme with no palette still leaves text unstyled.

BF-0a3e8ce

In print and JSON mode, SIGTERM or SIGHUP no longer hangs when a Go, Python or Rust extension's…

  • Source: PiG's own defect
  • Symptom: In print and JSON mode, SIGTERM or SIGHUP no longer hangs when a Go, Python or Rust extension's session_shutdown handler makes a host call that waits for the agent, such as wait_for_idle.
  • Root cause: The test's wait is now bounded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a5524c8b01 (2026-10-11), 0a3e8cebe8 (2026-10-12)
  • Tests: coding/cli/print_signal_exit_test.go::TestPrintModeSignalDisposalRunsLikePisEventLoop
  • Release note: In print and JSON mode, SIGTERM or SIGHUP no longer hangs when a Go, Python or Rust extension's session_shutdown handler makes a host call that waits for the agent, such as wait_for_idle. Such a call lets the interrupted run continue, as a Node handler's await does.

BF-0aa31c4

Extensions that import @earendil-works/pi-ai/models (for example for clampThinkingLevel) load in the Node…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: Extensions that import @earendil-works/pi-ai/models (for example for clampThinkingLevel) load in the Node extension runtime, under both the @earendil-works and the legacy @mariozechner scope (Pi 22dc6c26, #11156).
  • Root cause: Ports Pi (#11156): loader shim table, register-loader and runtime-modules lists, a pi-ai models bundle entry, the host peer, and sdk-ts declarations. Needs make vendor-pi-dist and make generate to produce sdk-bundle/models.js.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 0aa31c48e4 (2026-10-20)
  • Tests: coding/extension/host/subprocess/node_pi_ai_models_test.go::TestNodeExtensionImportsPiAiModelsSubpath, coding/extension/host/subprocess/required_tools_test.go::TestRequiredSDKToolsFailWhenMissing
  • Release note: Extensions that import @earendil-works/pi-ai/models (for example for clampThinkingLevel) load in the Node extension runtime, under both the @earendil-works and the legacy @mariozechner scope (Pi 22dc6c26, #11156).

BF-0aba908

Extensions' ctx.ui.setHiddenThinkingLabel() now changes the collapsed thinking label of every assistant…

  • Source: PiG's own defect
  • Symptom: Extensions' ctx.ui.setHiddenThinkingLabel() now changes the collapsed thinking label of every assistant block (it was stored but never read) and is reset when the session is replaced.
  • Root cause: Entries removed; the members now resolve by name (gaps 5327 -> 5291, nothing new). Root cause 2 (a real Pi difference the same audit exposed): AssistantMessageComponent.setHiddenThinkingLabel had no Go form, and the extension API ctx.ui.setHiddenThinkingLabel only stored the label on the footer where nothing read it (GetHiddenThinkingLabel had no caller), so the label of collapsed thinking was always 'Thinking...'. tui.AssistantMessageComponent.SetHiddenThinkingLabel (the empty string is a label there, as in Pi) redraws; InteractiveMode.setHiddenThinkingLabel applies the extension's label to every assistant block, newAssistantMessageBlock starts new blocks with it, and replacing the session…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0aba908b97 (2026-10-07)
  • Tests: internal/codingagent/hidden_thinking_label_test.go::TestExtensionHiddenThinkingLabelReachesAssistantBlocks, tui/assistant_message_label_oracle_test.go::TestAssistantMessageHiddenThinkingLabelMatchesPi
  • Release note: Extensions' ctx.ui.setHiddenThinkingLabel() now changes the collapsed thinking label of every assistant block (it was stored but never read) and is reset when the session is replaced.

BF-0adc7a6

A provider that an extension registers while it loads no longer fails the load when the model registry…

  • Source: PiG's own defect
  • Symptom: A provider that an extension registers while it loads no longer fails the load when the model registry rejects it.
  • Root cause: The Extension Host asked the model registry at once for each provider an extension registered while it loaded, and failed the whole extension load with the registry's message when the registry rejected it ("Failed to load extension: Provider x, model y: no "api" specified."). Pi's loader queues the registration; the services that bind the model runtime apply the queue after every extension loaded and report a rejection as the diagnostic Extension "<path>" error: <message>, and the extension stays loaded (loader.ts:217-218, agent-session-services.ts:157-169, runner.ts:471-487).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 0adc7a6152 (2026-10-25)
  • Tests: coding/cli/extension_host_wiring_guard_test.go::TestBuildFlushesTheExtensionHostProviderRegistrations, test/extension-conformance/provider_registration_diagnostic_test.go::TestProviderRegistrationFailureIsADiagnosticAcrossSDKs, test/parity/scenarios/extensions-runtime/88-startup-provider-registration-failure.toml
  • Release note: A provider that an extension registers while it loads no longer fails the load when the model registry rejects it. The host asked the registry at once and reported Failed to load extension: Provider x, model y: no "api" specified., with the pig -ne hint. Pi queues the registration and applies it after every extension has loaded. PiG now does the same: the extension stays loaded, the other registrations apply, and startup reports Error: Extension "<path>" error: Provider x, model y: no "api" specified. Set it at model level or provider level. as Pi does. A registration that an extension makes after it loaded still applies at once and returns its error to the extension.

BF-0ae3fcf

The login provider list selects with Enter when tui.select.confirm is rebound, as Pi's search input submits…

  • Source: PiG's own defect
  • Symptom: The login provider list selects with Enter when tui.select.confirm is rebound, as Pi's search input submits it, and starts its search cursor at the beginning of a prefilled query.
  • Root cause: The search is now NewInput with OnSubmit = confirm; the redundant LF special case goes (the Input submits LF); a prefilled query puts the cursor at the start as Input.setValue does.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d1f258f335 (2026-10-06), 0ae3fcf17c (2026-10-07)
  • Tests: internal/codingagent/extension_dialog_remapping_test.go::TestExtensionDialogRemappingThroughOwnerLoop, test/parity/scenarios/settings/08-settings-search-editing.toml, tui/extension_dialog_bindings_test.go::TestExtensionDialogsMatchPiBindingsAndPalette, tui/oauth_selector_input_test.go::TestOAuthSelectorInputMatchesPi, tui/settings_list_input_test.go::TestSettingsListAnyDelegatedKeyResetsTheSelectionToTheFirstMatch, tui/settings_list_input_test.go::TestSettingsListBoundActionsRunInUpstreamOrder, tui/settings_list_input_test.go::TestSettingsListEmptyAndSingletonNavigationBoundaries, tui/settings_list_input_test.go::TestSettingsListSearchDelegatesEditingKeysToTheInput and 3 more in docs/bug-ledger.json
  • Release note: The login provider list selects with Enter when tui.select.confirm is rebound, as Pi's search input submits it, and starts its search cursor at the beginning of a prefilled query.

BF-0ae4683

PiG, the extension SDKs and the sprite selection use one configuration root: PIG_HOME, else…

  • Source: PiG's own defect
  • Symptom: PiG, the extension SDKs and the sprite selection use one configuration root: PIG_HOME, else $XDG_CONFIG_HOME/pig, else ~/.pig.
  • Root cause: Pi's JsonRevisionValidator validates object properties in Reflect.ownKeys order and throws the first failing property's TypeError (revision-validator.ts:26-33). So a state with several invalid properties always reports the same error. Go's normalize walked the map in random order, so Prepare's error changed from run to run. Example: NaN at a and a struct at b gave either 'non-finite number' or 'containers must be plain objects'.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08)
  • Tests: None recorded.
  • Release note: PiG, the extension SDKs and the sprite selection use one configuration root: PIG_HOME, else $XDG_CONFIG_HOME/pig, else ~/.pig. The Python SDK's config_home ignored XDG_CONFIG_HOME, the Rust SDK's config_home still read the retired GOPI_HOME, and the Go SDK, the sprite selection and several host paths each had their own copy of the rule, so an extension could read or write a different directory than the CLI. An empty PIG_HOME or XDG_CONFIG_HOME now falls through to the next choice, a leading ~ or ~/ expands to your home directory everywhere, and any other value is used as written. If the configuration root needs your home directory and PiG cannot find it, PiG reports an error and exits (pig --version and other commands that need no root still run) instead of writing under a path relative to the working directory. Pi-sharing mode (PIG_USE_PI_DIRS=1) still moves only the agent and project directories, so a fresh pig in that mode writes its documentation and stages the extension SDKs under $XDG_CONFIG_HOME/pig. Reported by @baggiiiie (#200).

BF-0b13d8b

pig auth --help names pig auth ... instead of pi auth ...

  • Source: PiG's own defect
  • Symptom: pig auth --help names pig auth ... instead of pi auth ....
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 0b13d8bc58 (2026-10-13)
  • Tests: None recorded.
  • Release note: pig auth --help names pig auth ... instead of pi auth ....

BF-0b13d8b-2

pig extension --help lists pig extension init --login

  • Source: PiG's own defect
  • Symptom: pig extension --help lists pig extension init --login.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 0b13d8bc58 (2026-10-13)
  • Tests: None recorded.
  • Release note: pig extension --help lists pig extension init --login.

BF-0b13d8b-3

pig --help lists pig login, pig logout, pig package, pig extension upgrade, pig reload, and `pig…

  • Source: PiG's own defect
  • Symptom: pig --help lists pig login, pig logout, pig package, pig extension upgrade, pig reload, and pig diagnose.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 0b13d8bc58 (2026-10-13)
  • Tests: None recorded.
  • Release note: pig --help lists pig login, pig logout, pig package, pig extension upgrade, pig reload, and pig diagnose.

BF-0b4e1e3

The OpenAI Codex base URL keeps the whitespace around it, as Pi does (only an all-blank value falls back to…

  • Source: PiG's own defect
  • Symptom: The OpenAI Codex base URL keeps the whitespace around it, as Pi does (only an all-blank value falls back to the default), and its WebSocket URL is built with the WHATWG URL parser.
  • Root cause: resolveCodexWebSocketUrl/resolveCodexUrl (openai-codex-responses.ts:641-654) and baseUrlIncludesApiVersion (google-vertex.ts:415) are cut out of Pi's sources (testdata/codex_vertex_urls.mjs) and run on 4512 seeded base URLs (schemes, hosts, userinfo, ports, Codex path suffixes, Vertex version segments, whitespace Go and JavaScript trim differently). Fixed with the JS-whitespace emptiness test. resolveCodexWebSocketURL now parses with nodeurl.ParseURL and applies the https:->wss:, http:->ws: setter on the serialized href (codexWebSocketHref); the unsupported-scheme error stays after it, since Pi's WebSocket constructor rejects those schemes later. vertexBaseURLIncludesAPIVersion reads the…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0b4e1e3376 (2026-10-09), 97e178f7f6 (2026-10-09), c4d51d5c1a (2026-10-09), d1b3ad0151 (2026-10-09), d80c1cd150 (2026-10-09)
  • Tests: ai/codex_account_id_oracle_test.go::TestCodexAccountIDMatchesPi, ai/codex_vertex_urls_oracle_test.go::TestCodexWebSocketAndVertexVersionURLsMatchPi, ai/radius_config_oracle_test.go::TestRadiusConfigMatchesPiOnSeededProbes, ai/streaming_json_oracle_test.go::TestStreamingJSONMatchesPiOnSeededInput, internal/evals/plan_oracle_test.go::TestPlanMatchesPiOnSeededProbes, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode, internal/nodeurl/url_oracle_test.go::TestParseMatchesNodeURL
  • Release note: The OpenAI Codex base URL keeps the whitespace around it, as Pi does (only an all-blank value falls back to the default), and its WebSocket URL is built with the WHATWG URL parser. The Vertex API-version check reads the parsed URL path.

BF-0b674b3

Gemini model-id checks follow Pi's lowercasing: an id spelled with a dotted capital İ or a long ſ is no…

  • Source: PiG's own defect
  • Symptom: Gemini model-id checks follow Pi's lowercasing: an id spelled with a dotted capital İ or a long ſ is no longer treated as a Gemini 3 thinking-level or version-matched model, and a Gemini major version too long for an integer (such as gemini-99999999999999999999) counts as version 3 or later, as Number.parseInt does.
  • Root cause: usesGoogleThinkingLevel, getGeminiMajorVersion, requiresToolCallId, supportsMultimodalFunctionResponse and supportsGoogleStrictToolSampling are cut from Pi's google-shared.ts (testdata/google_model_ids.mjs) and run on 4514 seeded model ids. 139 differed (red before): Go's strings.ToLower maps İ to 'i' where toLowerCase gives 'i' plus a combining dot (so 'GEMİNİ-3' matched in Go only), the (?i) regexps folded ſ and the Kelvin sign where Pi's lowercased-then-case-sensitive regexps do not, and a version longer than an int failed Atoi where parseInt keeps a large float (gemini-99999999999999999999 is >= 3 in Pi). The ids are lowercased with jsstring.ToLower, the regexps are case-sensitive…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 093cf5c781 (2026-10-09), 0b4e1e3376 (2026-10-09), 0b674b3855 (2026-10-09), 97e178f7f6 (2026-10-09), a1fa6d1a33 (2026-10-09), c4d51d5c1a (2026-10-09), d1b3ad0151 (2026-10-09)
  • Tests: ai/codex_account_id_oracle_test.go::TestCodexAccountIDMatchesPi, ai/codex_vertex_urls_oracle_test.go::TestCodexWebSocketAndVertexVersionURLsMatchPi, ai/error_body_oracle_test.go::TestErrorBodyHelpersMatchPiOnSeededProbes, ai/google_model_ids_oracle_test.go::TestGoogleModelIDHelpersMatchPi, ai/node_http_proxy_oracle_test.go::TestResolveHTTPProxyURLForTargetMatchesPiOracle, ai/radius_config_oracle_test.go::TestRadiusConfigMatchesPiOnSeededProbes, ai/streaming_json_oracle_test.go::TestStreamingJSONMatchesPiOnSeededInput, internal/evals/plan_oracle_test.go::TestPlanMatchesPiOnSeededProbes and 1 more in docs/bug-ledger.json
  • Release note: Gemini model-id checks follow Pi's lowercasing: an id spelled with a dotted capital İ or a long ſ is no longer treated as a Gemini 3 thinking-level or version-matched model, and a Gemini major version too long for an integer (such as gemini-99999999999999999999) counts as version 3 or later, as Number.parseInt does.

BF-0b85f55

The Rust SDK keeps every session entry when one entry of the session file holds an unpaired surrogate (it…

  • Source: PiG's own defect
  • Symptom: The Rust SDK keeps every session entry when one entry of the session file holds an unpaired surrogate (it read the whole file as empty).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 0b85f55b31 (2026-10-11)
  • Tests: None recorded.
  • Release note: The Rust SDK keeps every session entry when one entry of the session file holds an unpaired surrogate (it read the whole file as empty).

BF-0bbe365

fix(codingagent): footer context usage refreshes on a context_edit entry (Pi getContextUsage)

  • Source: PiG's own defect
  • Symptom: fix(codingagent): footer context usage refreshes on a context_edit entry (Pi getContextUsage)
  • Root cause: Pi's footer calls session.getContextUsage() on every render. Found by an end-to-end run of the same scripted OpenAI-completions server through real Pi and Pig; handleEntryAppended now refreshes on a context_edit entry. TestContextEditEntryRefreshesTheFooterContextUsage is red without the change.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 0bbe365cd4 (2026-10-11)
  • Tests: internal/codingagent/interactive_cache_warming_test.go::TestContextEditEntryRefreshesTheFooterContextUsage

BF-0bc05bf

Fixed ! and RPC bash output keeping fragments of color codes, such as a stray m, when a code was split…

  • Source: PiG's own defect
  • Symptom: Fixed ! and RPC bash output keeping fragments of color codes, such as a stray m, when a code was split across output chunks.
  • Root cause: Ports utils/ansi.ts splitIncompleteAnsiSuffix and bash-executor.ts pendingAnsi/flushOutput: an unfinished escape sequence at the end of a chunk is held for the next chunk and flushed at exit; empty chunks are not emitted. Shared by ! and RPC bash (coding.Session.ExecuteBash). Tests port agent-session-bash-persistence.test.ts:342-396.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0bc05bf8de (2026-10-07)
  • Tests: coding/session_bash_persistence_upstream_test.go::TestUpstreamBashEscapeSequencesSplitAcrossChunks, internal/codingagent/tools/sanitize_split_ansi_test.go::TestExecuteBashWithOperationsFlushesPendingANSIAtExit
  • Release note: Fixed ! and RPC bash output keeping fragments of color codes, such as a stray m, when a code was split across output chunks.

BF-0ce459e

/login offers a provider object's own login methods (Pi's registerNativeProvider, the…

  • Source: PiG's own defect
  • Symptom: /login offers a provider object's own login methods (Pi's registerNativeProvider, the registerProvider(provider) overload).
  • Root cause: The entry said the provider lists "Sign in with an account"; the tests (TestNativeProviderLoginOffersDeclaredMethods) and oauth/30 against Pi show the flow's own loginLabel replaces that label. The carrier's prompt and event decoding started from balcsida's branch (), so the entry says so besides crediting the report.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 0ce459e879 (2026-10-11)
  • Tests: internal/codingagent/native_provider_login_test.go::TestNativeProviderLoginOffersDeclaredMethods
  • Release note: /login offers a provider object's own login methods (Pi's registerNativeProvider, the registerProvider(provider) overload). A provider whose auth declares an OAuth method now offers its account login under the flow's own loginLabel (the generic "Sign in with an account" label is replaced), and it stays offered after the provider's catalog refresh. Reported and diagnosed by @balcsida, whose branch the login carrier started from (#201).

BF-0d0a3ef

A turn through OpenRouter (Chat Completions or Anthropic Messages) now reports the cost OpenRouter billed…

  • Source: PiG's own defect
  • Symptom: A turn through OpenRouter (Chat Completions or Anthropic Messages) now reports the cost OpenRouter billed, including the upstream cost of BYOK requests, instead of the catalog-rate estimate; the HTML export shows the recorded total.
  • Root cause: Ports pi-mono commit (branch open-router-cost): utils/openrouter-cost.ts, parseChunkUsage in openai-completions.ts, message_delta usage in anthropic-messages.ts, export-html template.js cost.total.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0d0a3efd10 (2026-10-09), 1cad63426b (2026-10-10), 6875edf3a8 (2026-10-10)
  • Tests: ai/openai_test.go::TestParseChunkUsage_PromptCacheHitTokens, ai/openai_test.go::TestParseChunkUsage_PromptTokensDetailsPreferred, ai/openrouter_cost_test.go::TestAnthropicMessagesUsesOpenRouterReportedCost, ai/openrouter_cost_test.go::TestApplyOpenRouterReportedCost, ai/openrouter_cost_test.go::TestIsOpenRouterModel, ai/openrouter_cost_test.go::TestOpenAICompletionsUsesOpenRouterReportedCost, internal/codingagent/export/export_cost_total_test.go::TestExportHTMLCostUsesRecordedTotals, internal/codingagent/export/upstream_test.go::TestExportHTMLPinnedAssets
  • Release note: A turn through OpenRouter (Chat Completions or Anthropic Messages) now reports the cost OpenRouter billed, including the upstream cost of BYOK requests, instead of the catalog-rate estimate; the HTML export shows the recorded total. This ports pi-mono's unmerged open-router-cost branch (D124, pi-mono #9980).

BF-0d13b01

SettingsManager.getTransport and fifteen sibling getters (settings-manager.ts: getSteeringMode…

  • Source: PiG's own defect
  • Symptom: SettingsManager.getTransport and fifteen sibling getters (settings-manager.ts: getSteeringMode, getFollowUpMode, getDefaultThinkingLevel, getTheme, getDefaultProvider, getDefaultModel, getHideThinkingBlock, getCompactionEnabled, getImageAutoResize, getBlockImages, getShowImages, getEditorPaddingX, getAutocompleteMaxVisible, getCodeBlockIndent, getEnableSkillCommands): new live-Pi oracle TestSettingsManagerGettersMatchPi (internal/codingagent/settings_getters_oracle_test.go, 1,500 global/project file pairs setting, omitting or conflicting on each key, nested objects merged one level) compares every getter's value, defaults included, with Pi's.
  • Root cause: Not recorded.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 0d13b01176 (2026-10-10)
  • Tests: internal/codingagent/settings_getters_oracle_test.go::TestSettingsManagerGettersMatchPi
  • Release note: SettingsManager.getTransport and fifteen sibling getters (settings-manager.ts: getSteeringMode, getFollowUpMode, getDefaultThinkingLevel, getTheme, getDefaultProvider, getDefaultModel, getHideThinkingBlock, getCompactionEnabled, getImageAutoResize, getBlockImages, getShowImages, getEditorPaddingX, getAutocompleteMaxVisible, getCodeBlockIndent, getEnableSkillCommands): new live-Pi oracle TestSettingsManagerGettersMatchPi (internal/codingagent/settings_getters_oracle_test.go, 1,500 global/project file pairs setting, omitting or conflicting on each key, nested objects merged one level) compares every getter's value, defaults included, with Pi's. No bug found. 8 mutations red (transport/steering/follow-up defaults, showImages default, compaction/blockImages/hideThinking inversions, editorPaddingX). Not compared: environment-reading getters (hardware cursor, clear on shrink), wrong-typed values and fractional numbers (Settings is typed), and a codeBlockIndent of "" (Go's string cannot tell "" from absent, so GetCodeBlockIndent returns the default where Pi keeps ""). Closes the flip:c2cc995c row (SettingsManager.GetTransport) at v1.1.0.

BF-0d1c973

tui.Box no longer invalidates its children when a child is added or removed or the box is cleared

  • Source: PiG's own defect
  • Symptom: tui.Box no longer invalidates its children when a child is added or removed or the box is cleared.
  • Root cause: box.ts addChild, removeChild and clear call only invalidateCache, which drops the box's own cache. Pi invalidates the children only from Box.invalidate. Go's Box.AddChild, RemoveChild and Clear called b.Invalidate(), which also invalidates every child. A child component, including an extension's remote component, therefore received invalidate calls that Pi never makes. Pig's Container already follows Pi here.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0d1c97335f (2026-10-10)
  • Tests: tui/box_semantics_test.go::TestBoxChangesMarkItDirty, tui/box_semantics_test.go::TestBoxStructureChangesDoNotInvalidateTheChildren
  • Release note: tui.Box no longer invalidates its children when a child is added or removed or the box is cleared. Pi's Box drops only its own render cache there, and an extension component inside a box no longer receives an invalidate call that Pi does not make. Box.Invalidate still invalidates every child.

BF-0d55b30

A panicking notify while MCP reports startup problems is shown as "MCP failed to load" instead of ending the…

  • Source: PiG's own defect
  • Symptom: A panicking notify while MCP reports startup problems is shown as "MCP failed to load" instead of ending the process, as Pi's startup chain catches it.
  • Root cause:
    • mcpext: let ctx.notify panics propagate everywhere except the "MCP failed to load" notify, but Pi's session_start chain .then(() => reportProblems(ctx)).catch(...) (mcp/index.ts:1138-1147) also catches a throw from reportProblems' notify. PiG runs that report on a background goroutine, so the panic ended the process. reportStartupProblems shows it as "MCP failed to load: <message>" and ignores a second throw. - sdk-py: the Go SDK now reports a SetInvalidate(nil) panic on overlay stop in the extension log; Python's _dispose_remote_component still swallowed a raising set_invalidate(None) silently. It reports it on stderr and still disposes.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0d55b30035 (2026-10-09), 448ff71f1f (2026-10-09)
  • Tests: automation/ci/divguard/divguard_test.go::TestBareRecoverScansEveryProductionPackage, coding/mcpext/notify_panic_internal_test.go::TestNotifyPanicPropagatesExceptOnTheLoadFailurePath, coding/mcpext/regression_test.go::TestStartupProblemsReportPanicShowsAsLoadFailure, coding/session_event_panic_test.go::TestReportExtensionErrorNamesAPanickingListener, extensions/sdk-py/tests/test_component_cleanup.py, extensions/sdk/recovered_panic_reporting_test.go::TestInvalidatorPanicOnStopIsReported, extensions/sdk/recovered_panic_reporting_test.go::TestSameOverlayComponentNeverPanicsOnUncomparableValues, extensions/sdk/recovered_panic_reporting_test.go::TestTerminalInputHandlerPanicIsReportedAndYieldsNoVerdict
  • Release note: A panicking notify while MCP reports startup problems is shown as "MCP failed to load" instead of ending the process, as Pi's startup chain catches it.

BF-0d55b30-2

The Python SDK reports a component whose set_invalidate(None) raises during cleanup in the extension log…

  • Source: PiG's own defect
  • Symptom: The Python SDK reports a component whose set_invalidate(None) raises during cleanup in the extension log, as the Go SDK does.
  • Root cause:
    • mcpext: let ctx.notify panics propagate everywhere except the "MCP failed to load" notify, but Pi's session_start chain .then(() => reportProblems(ctx)).catch(...) (mcp/index.ts:1138-1147) also catches a throw from reportProblems' notify. PiG runs that report on a background goroutine, so the panic ended the process. reportStartupProblems shows it as "MCP failed to load: <message>" and ignores a second throw. - sdk-py: the Go SDK now reports a SetInvalidate(nil) panic on overlay stop in the extension log; Python's _dispose_remote_component still swallowed a raising set_invalidate(None) silently. It reports it on stderr and still disposes.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0d55b30035 (2026-10-09), 448ff71f1f (2026-10-09)
  • Tests: automation/ci/divguard/divguard_test.go::TestBareRecoverScansEveryProductionPackage, coding/mcpext/notify_panic_internal_test.go::TestNotifyPanicPropagatesExceptOnTheLoadFailurePath, coding/mcpext/regression_test.go::TestStartupProblemsReportPanicShowsAsLoadFailure, coding/session_event_panic_test.go::TestReportExtensionErrorNamesAPanickingListener, extensions/sdk-py/tests/test_component_cleanup.py, extensions/sdk/recovered_panic_reporting_test.go::TestInvalidatorPanicOnStopIsReported, extensions/sdk/recovered_panic_reporting_test.go::TestSameOverlayComponentNeverPanicsOnUncomparableValues, extensions/sdk/recovered_panic_reporting_test.go::TestTerminalInputHandlerPanicIsReportedAndYieldsNoVerdict
  • Release note: The Python SDK reports a component whose set_invalidate(None) raises during cleanup in the extension log, as the Go SDK does.

BF-0dff068

An OpenAI Responses function call streamed with an empty call_id no longer makes every later request fail…

  • Source: A Pi defect that PiG fixed first (divergence D129)
  • Symptom: An OpenAI Responses function call streamed with an empty call_id no longer makes every later request fail with an empty call_id: it gets a generated id, and a Responses request omits a stored call without one (D129, pi-mono #10299, Responses path).
  • Root cause: Pi replays that call on every target except Responses: Chat Completions normalizes it to _fc_1 (openai-completions.ts:1210-1215) and Gemini sends it unchanged. Only the Responses replay splits the id and sends call_id "". The omission now runs in prepareResponsesTranscript after ID normalization (omitEmptyResponsesCallIDs), through the same D120 tool flow. D129 gets its divergences bundle row (docs-drift).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10299
  • Removed when Pi fixes it: Pi fills the id at stream end and omits unreplayable calls on replay, for the Responses id form too.
  • Fix commits: 0dff068cfd (2026-10-10), 66765600a6 (2026-10-10), ed55a199f8 (2026-10-10)
  • Tests: ai/openai_responses_call_id_oracle_test.go::TestOpenAIResponsesToolCallIDPartsMatchPi, ai/openai_responses_call_id_oracle_test.go::TestStoredEmptyCallPartReplaysToOtherTargetsAsPi, ai/openai_responses_idless_tool_call_test.go::TestResponsesEmptyCallIDOmissionIsConfinedToTheResponsesReplay, ai/openai_responses_idless_tool_call_test.go::TestResponsesToolCallWithoutCallIDDoesNotWedgeReplay
  • Release note: An OpenAI Responses function call streamed with an empty call_id no longer makes every later request fail with an empty call_id: it gets a generated id, and a Responses request omits a stored call without one (D129, pi-mono #10299, Responses path). A call streamed without a call_id is stored as undefined|<item id>, as in Pi.

BF-0e05901

A provider an extension registers with refreshModels and no streamSimple streams through its API, as in Pi

  • Source: PiG's own defect
  • Symptom: Every request failed with "Provider ... has no stream implementation".
  • Root cause: A registerProvider config with refreshModels is composed in the native model collection (provider-composer.ts:613). Two defects on that path:
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 051a79d333 (2026-10-09), 0e05901b4c (2026-10-09)
  • Tests: coding/extension/host/subprocess/native_provider_request_callbacks_test.go::TestNativeProviderSessionRequestHooks, coding/extension/host/subprocess/native_stream_callback_test.go::TestNativeStreamCallbackDispatchesByName, coding/provider_refresh_insecure_test.go::TestRefreshModelsRegistrationKeepsInsecure, test/extension-conformance/provider_object_test.go::TestProviderObjectsAcrossSDKs
  • Release note: A provider an extension registers with refreshModels and no streamSimple streams through its API, as in Pi. Before, every request failed with "Provider ... has no stream implementation".

BF-0e225b4

fix(runtimecell): keep the Python runner's _run callable without the built event

  • Source: PiG's own defect
  • Symptom: fix(runtimecell): keep the Python runner's _run callable without the built event
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 0e225b41ec (2026-10-21)
  • Tests: None recorded.

BF-0e3411c

Fixed CI on main after the 0.5.1 ledger trim: the reviewed-pin ledgers (upstream-v0.84.0.json…

  • Source: PiG's own defect
  • Symptom: Fixed CI on main after the 0.5.1 ledger trim: the reviewed-pin ledgers (upstream-v0.84.0.json, cli-v0.84.0.json, behavior-inputs-v0.84.0.json, behavior-input-mapping-v0.84.0.json) are back, since interface-delta reads them as the base of the current delta; the stale testprune ledger entries and bug ledger entries orphaned by the 0.5.0 squash merge are gone.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 0e3411c597 (2026-10-19, #224)
  • Tests: None recorded.
  • Release note: Fixed CI on main after the 0.5.1 ledger trim: the reviewed-pin ledgers (upstream-v0.84.0.json, cli-v0.84.0.json, behavior-inputs-v0.84.0.json, behavior-input-mapping-v0.84.0.json) are back, since interface-delta reads them as the base of the current delta; the stale testprune ledger entries and bug ledger entries orphaned by the 0.5.0 squash merge are gone.

BF-0e360a0

The tui package's default keybindings no longer depend on the platform: undo is ctrl+- and the alt-screen…

  • Source: PiG's own defect
  • Symptom: The tui package's default keybindings no longer depend on the platform: undo is ctrl+- and the alt-screen prompt and search keys are Pi's TUI_KEYBINDINGS everywhere.
  • Root cause: Pi's getKeybindings() falls back to TUI_KEYBINDINGS, which binds undo to ctrl+- and the alt-screen prompt/search keys the same on every platform; only coding-agent KEYBINDINGS overrides them on Windows and WSL, and the coding agent installs that table through its own manager.
  • User impact: Behavior differed from Pi
  • Platform: Windows, WSL
  • Fixed in: 0.5.0
  • Fix commits: 0e360a044a (2026-10-06)
  • Tests: internal/codingagent/tool_builtin_edit_shell_test.go::TestStockEditCardDrawsItsOwnShell, internal/codingagent/tool_builtin_renderers_pi104_test.go::TestBuiltInRenderersMatchPi104, tui/editor_oracle_test.go::TestEditorMatchesPiOracle, tui/keybindings_test.go::TestTUIDefaultKeybindingsIgnoreThePlatform, tui/login_dialog_pi_test.go::TestLoginDialogMaskDisabledMatchesPi
  • Release note: The tui package's default keybindings no longer depend on the platform: undo is ctrl+- and the alt-screen prompt and search keys are Pi's TUI_KEYBINDINGS everywhere. The Windows and WSL keys stay in the coding agent's table, as in Pi.

BF-0e8761b

A system message with an empty sections object is stored as "sections": {}, as Pi stores it after a reset

  • Source: A fix Pi shipped that PiG ported
  • Symptom: A system message with an empty sections object is stored as "sections": {}, as Pi stores it after a reset.
  • Root cause: Hidden declarations stay out of the tool list, rules and skills hint through the options' hiddenTools, and codemode shows each listed tool's prompt guidelines with its declaration. ToolLoadout gains getPromptGuidelines in the Go host and in the Go, Python, Rust and Node extension SDKs, with a conformance row that reads a distinct guideline list per SDK (system-prompt.ts hiddenTools, skills.ts formatSkillsForPrompt, agent-session.ts getPromptGuidelines, codemode tool.ts).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0e8761be47 (2026-10-05), b728733998 (2026-10-05)
  • Tests: ai/system_message_sections_test.go::TestSystemMessageWritesAnEmptySectionsObjectButNotAnAbsentOne, durable/examples/tasks_compaction_examples_test.go::TestExample24Compaction, durable/interop/interop_test.go::TestGoOpensAndResumesAStoreWrittenByPiDurable, durable/interop/interop_test.go::TestPiDurableOpensAndResumesAStoreWrittenByGo, durable/interop/interop_test.go::TestScriptedSessionJsonlFilesMatchPiDurable, durable/interop/interop_test.go::TestScriptedSessionStoresMatchPiDurable, durable/interop/interop_test.go::TestScriptedSessionsViewIdentically, durable/interop/interop_test.go::TestSecondScriptedSessionStoresMatchPiDurable and 2 more in docs/bug-ledger.json
  • Release note: A system message with an empty sections object is stored as "sections": {}, as Pi stores it after a reset.

BF-0ffb06a

fix(tui): diff rows with a CR are wrapped like Pi's Text

  • Source: PiG's own defect
  • Symptom: fix(tui): diff rows with a CR are wrapped like Pi's Text
  • Root cause: Random tool cards against Pi (TestBuiltinToolCardsRandomMatchPi, red before: edit cards 33 and 468): wrapTextWithAnsi splits on /\r\n|\r|\n/, but DiffComponent only wrapped rows wider than the card.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 0ffb06a7bf (2026-10-10)
  • Tests: internal/codingagent/builtin_tool_cards_oracle_test.go::TestBuiltinToolCardsMatchPi, internal/codingagent/builtin_tool_cards_oracle_test.go::TestBuiltinToolCardsRandomMatchPi

BF-116b36c

fix(extensions): answer the start-window report of a tool call that holds no window, where the report is read

  • Source: PiG's own defect
  • Symptom: fix(extensions): answer the start-window report of a tool call that holds no window, where the report is read
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 116b36c0cc (2026-10-21)
  • Tests: None recorded.

BF-11a6fcb

fix(extension): a restart adopts the new renderers; the late-registration row runs what it registered

  • Source: PiG's own defect
  • Symptom: fix(extension): a restart adopts the new renderers; the late-registration row runs what it registered
  • Root cause: ReplaceDeclarations replaced commands, flags and shortcuts on an isolated restart or packed recovery but left the message and entry renderers the held Extension had, so a custom type the restarted process registered did not render and one only the old process registered (at load or late through registerDeclaration) stayed and then failed with "unknown renderer". It now takes source's renderers too (TestReplaceDeclarationsAdoptsTheNewRenderers, red before).
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 11a6fcb8c5 (2026-10-10)
  • Tests: coding/extension/declaration_registry_test.go::TestReplaceDeclarationsAdoptsTheNewRenderers

BF-11b199d

Session lines (and every other JSON.stringify-mode encode) now write ill-formed UTF-8 the way Pi does: one…

  • Source: PiG's own defect
  • Symptom: PiG used to write one \ufffd escape per bad byte, so "a\xe2\x82b" became "a\ufffd\ufffdb" where Pi writes "a�b".
  • Root cause: JSON.stringify of a string decoded from ill-formed UTF-8 writes one raw U+FFFD per WHATWG maximal subpart; PiG's session lines wrote one \ufffd escape per bad byte. The codec now counts one replacement per maximal subpart in every mode, writes it raw in the JSON.stringify mode, and rewrites nested \ufffd escapes to raw there. Marshal and MarshalJS keep the escape form. Session files written before keep their form and read back the same.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 11b199d645 (2026-10-12), 4e17838ae9 (2026-10-12)
  • Tests: ai/errors_pool_test.go::TestSafeJsonStringifyPoolDropsLargeBuffers, extensions/sdk/json/append_string_swar_test.go::TestAppendStringMatchesBytewise, extensions/sdk/json/decode_trusted_test.go::TestUnmarshalTrustedMatchesUnmarshal, extensions/sdk/json/decode_trusted_test.go::TestUnmarshalTrustedVerifiesInTests, extensions/sdk/json/lw_budget_norace_test.go::TestAppendString200KBudget, extensions/sdk/json/lw_budget_norace_test.go::TestEncodeStatePoolDropsLargeBuffers, internal/jsonstringify/fast_node_test.go::TestFastPathKeyStackBound, internal/jsonstringify/large_object_test.go::TestIsCanonicalAcceptsLargeObjects and 10 more in docs/bug-ledger.json
  • Release note: Session lines (and every other JSON.stringify-mode encode) now write ill-formed UTF-8 the way Pi does: one raw U+FFFD per WHATWG maximal subpart (what a JavaScript string decoded from those bytes holds, and what JSON.stringify writes). PiG used to write one \ufffd escape per bad byte, so "a\xe2\x82b" became "a\ufffd\ufffdb" where Pi writes "a�b". Nested \ufffd escapes from PiG's own types are written raw as well. Session files written before this change keep the old form and read back the same strings. On the extension wire (the codec's Marshal/MarshalJS), ill-formed UTF-8 also counts one replacement per maximal subpart rather than per byte; the \ufffd escape form there is unchanged.

BF-1268115

fix(model runtime): a cancelled request to a caller-owned provider the runtime does not know is aborted, not…

  • Source: PiG's own defect
  • Symptom: fix(model runtime): a cancelled request to a caller-owned provider the runtime does not know is aborted, not a setup error
  • Root cause: It now waits for the update that carries the output (6 of 6, runs=3).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 1268115b91 (2026-10-10)
  • Tests: coding/model_runtime_setup_abort_upstream_test.go::TestCancelledRequestToAnUnregisteredCallerProviderIsAborted, test/parity/scenarios/providers-faux-streaming/14-provider-immediate-abort.toml, test/parity/scenarios/rpc/55-rpc-abort-during-tool-next-request.toml

BF-1356aae

fix(train): load pig_sdk.tui with the package, run the TS queue check with no runtime, pin three fixture…

  • Source: PiG's own defect
  • Symptom: fix(train): load pig_sdk.tui with the package, run the TS queue check with no runtime, pin three fixture modules to the root SDK version
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 1356aae997 (2026-10-21)
  • Tests: None recorded.

BF-1364791

Stopping a codemode script or reaching its deadline no longer waits up to half a second for a built-in that…

  • Source: PiG's own defect
  • Symptom: Stopping a codemode script or reaching its deadline no longer waits up to half a second for a built-in that does not poll the interrupt flag (#10748).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 1364791228 (2026-10-13)
  • Tests: codemode/job_poll_test.go::TestAMicrotaskSpinEndsAtTheJobPollNotTheHardStop, codemode/lifetime_test.go::TestCloseAbortsEveryRunningExecution, codemode/lifetime_test.go::TestEveryTerminalKindClosesItsVMWithoutHaltingTheInstance, codemode/worker_admission_test.go::TestAFailedModuleLoadAcquiresNoAdmission, codemode/worker_admission_test.go::TestAFailingOnWorkerStartedEndsTheWorker, codemode/worker_admission_test.go::TestAdmissionIsReleasedOnExitNotOnTheResult, codemode/worker_admission_test.go::TestDeniedAdmissionStartsNoWorker, codemode/worker_admission_test.go::TestReportsTheActualExitOfExecuteAndStartWorkers and 3 more in docs/bug-ledger.json
  • Release note: Stopping a codemode script or reaching its deadline no longer waits up to half a second for a built-in that does not poll the interrupt flag (#10748).

BF-1390c9d

fix(ai): the Anthropic event reader matches the type member exactly

  • Source: PiG's own defect
  • Symptom: fix(ai): the Anthropic event reader matches the type member exactly
  • Root cause: An earlier change read event.type through a Go struct field, which encoding/json matches case-insensitively, so {"TYPE":"message_start"} set sawMessageStart and the stream ended with 'Anthropic stream ended before message_stop', where Pi's event.type is undefined (anthropic-messages.ts:551-556). anthropicEventType reads the exact member from a map (last duplicate wins, as JSON.parse).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 1390c9d6a0 (2026-10-10)
  • Tests: None recorded.

BF-155d0f3

In RPC mode, SIGTERM or SIGHUP during a tool call likewise stops the run while the session_shutdown…

  • Source: PiG's own defect
  • Symptom: In RPC mode, SIGTERM or SIGHUP during a tool call likewise stops the run while the session_shutdown handlers finish without waiting, as Pi does: a handler that throws no longer lets the run persist the tool result and send another provider request before the exit.
  • Root cause: Pi's RPC signal handler kills tracked children and awaits runtimeHost.dispose on its one event loop (rpc-mode.ts:366-376,726-744). The killed bash call's exit is I/O, so the run reaches its tool result only after a session_shutdown handler awaits a timer or I/O. PiG's run went on concurrently: with a throwing handler it persisted the tool result and an assistant turn and sent a second provider request before exiting. RPC mode now registers the same termination prelude as print mode: Session.HoldAgentEvents from the moment the signal arrives until a handler reports its yield or the Session is disposed.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 155d0f33a0 (2026-10-11), 329c4e7f7e (2026-10-11), 34f7594aa5 (2026-10-11)
  • Tests: coding/cli/print_signal_shutdown_test.go::TestPrintModeSignalDeliversSessionShutdown, coding/cli/rpc_signal_disposal_test.go::TestRPCSignalDisposalHoldsTheRunLikePisEventLoop, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-switch.toml and 1 more in docs/bug-ledger.json
  • Release note: In RPC mode, SIGTERM or SIGHUP during a tool call likewise stops the run while the session_shutdown handlers finish without waiting, as Pi does: a handler that throws no longer lets the run persist the tool result and send another provider request before the exit.

BF-15a5c0b

The Kimi Code, Meta and xAI device logins judge the verification URI the way Pi does, and Meta and xAI open…

  • Source: PiG's own defect
  • Symptom: The Kimi Code, Meta and xAI device logins judge the verification URI the way Pi does, and Meta and xAI open the URL Pi opens.
  • Root cause: A differential test runs Pi's three private functions in Node (testdata/trusted_urls.mjs cuts them out of the pinned source) over 91 URLs and found 42 differences. Pig used url.Parse, which keeps the URL as received and accepts what the WHATWG parser rejects, and rejects forms the WHATWG parser accepts: - 'https:example.com', 'https:/example.com', 'https:///example.com' and backslash forms are valid in Pi and were refused; - ports above 65535, 'https://:443', numeric hosts out of range, a space in the host were refused by Pi and accepted; - Meta and xAI returned the URL as written, where Pi returns the href (lowercase scheme and host, default and empty port and empty userinfo dropped, empty…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 15a5c0b670 (2026-10-09), 7c4d874438 (2026-10-09)
  • Tests: ai/githubcopilot_oracle_test.go::TestGitHubCopilotHelpersMatchPi, ai/trusted_url_oracle_test.go::TestTrustedVerificationURLsMatchPi
  • Release note: The Kimi Code, Meta and xAI device logins judge the verification URI the way Pi does, and Meta and xAI open the URL Pi opens. A https:host or backslash form is read as a URL; a port above 65535, an out-of-range numeric host, or a space in the host is refused. The opened URL is normalized as new URL does (lowercase scheme and host, default port and empty userinfo dropped, an empty path becomes /) instead of being echoed as received.

BF-160d95a

In RPC mode, a run that an extension command starts with sendMessage or sendUserMessage now emits…

  • Source: PiG's own defect
  • Symptom: In RPC mode, a run that an extension command starts with sendMessage or sendUserMessage now emits agent_start after the command's response, as in Pi, when the command is written with the Go, Python or Rust SDK, not only on Node.
  • Root cause: Fix 2: rpcAdmission holds the start while a command's handler runs (rpcCommandRuns). The transport reports the handler's phase in frame order (invocation.RunPhase, Conn.reportRequestPhase): a sendMessage or sendUserMessage call resumes its parent request, because the runtime reports the call's own suspension before it sends the call, and a later blocked or suspended state suspends it. The start proceeds once the command answers (after its response is written) or awaits. In-process commands are not tracked and keep the hop placement, so TestRPCInlineCommandTurnStartOrderMatchesPi is unchanged. A Node handler that awaits something other than a host call after a send that follows its first…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 160d95a510 (2026-10-13), 773a868c84 (2026-10-13)
  • Tests: coding/cli/rpc_command_send_order_test.go::TestRPCCommandThatAwaitsTheRunItSentAnswersAfterIt, coding/cli/rpc_command_send_order_test.go::TestRPCCommandTurnStartOrderMatchesPi, coding/cli/rpc_command_send_order_test.go::TestRPCInlineCommandTurnStartOrderMatchesPi, coding/cli/rpc_ui_integration_test.go::TestRPCModeExtensionUIRoundTrip, coding/cli/session_wire_lifecycle_test.go::TestModeWireLifecycle, coding/cli/startup_alloc_budget_test.go::TestStartupAllocationBudget, coding/extension/host/subprocess/node_cell_memory_test.go::TestNodeCellMembersShareRegistryPublications, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult and 7 more in docs/bug-ledger.json
  • Release note: In RPC mode, a run that an extension command starts with sendMessage or sendUserMessage now emits agent_start after the command's response, as in Pi, when the command is written with the Go, Python or Rust SDK, not only on Node. A command that awaits after the send still lets the run start first.

BF-160d95a-2

In RPC mode, the Go, Python and Rust SDKs no longer let the host move on while a notify, setStatus…

  • Source: PiG's own defect
  • Symptom: In RPC mode, the Go, Python and Rust SDKs no longer let the host move on while a notify, setStatus, setWidget, setTitle or other immediate UI call is still in flight.
  • Root cause: Fix 2: rpcAdmission holds the start while a command's handler runs (rpcCommandRuns). The transport reports the handler's phase in frame order (invocation.RunPhase, Conn.reportRequestPhase): a sendMessage or sendUserMessage call resumes its parent request, because the runtime reports the call's own suspension before it sends the call, and a later blocked or suspended state suspends it. The start proceeds once the command answers (after its response is written) or awaits. In-process commands are not tracked and keep the hop placement, so TestRPCInlineCommandTurnStartOrderMatchesPi is unchanged. A Node handler that awaits something other than a host call after a send that follows its first…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 160d95a510 (2026-10-13), 773a868c84 (2026-10-13)
  • Tests: coding/cli/rpc_command_send_order_test.go::TestRPCCommandThatAwaitsTheRunItSentAnswersAfterIt, coding/cli/rpc_command_send_order_test.go::TestRPCCommandTurnStartOrderMatchesPi, coding/cli/rpc_command_send_order_test.go::TestRPCInlineCommandTurnStartOrderMatchesPi, coding/cli/rpc_ui_integration_test.go::TestRPCModeExtensionUIRoundTrip, coding/cli/session_wire_lifecycle_test.go::TestModeWireLifecycle, coding/cli/startup_alloc_budget_test.go::TestStartupAllocationBudget, coding/extension/host/subprocess/node_cell_memory_test.go::TestNodeCellMembersShareRegistryPublications, test/extension-conformance/rpc_nested_events_test.go::TestRPCNestedEventsCompleteInEverySDK
  • Release note: In RPC mode, the Go, Python and Rust SDKs no longer let the host move on while a notify, setStatus, setWidget, setTitle or other immediate UI call is still in flight. A nested handler's UI request is now written before the response of the command that triggered it, as in Pi.

BF-1631753

Queueing a steering or follow-up message while the agent streams prints no status line, as Pi does

  • Source: PiG's own defect
  • Symptom: Queueing a steering or follow-up message while the agent streams prints no status line, as Pi does.
  • Root cause: Pi queues a steering or follow-up message with session.prompt(text, { streamingBehavior }) and refreshes the pending-messages area (interactive-mode.ts handleSubmit, handleFollowUp 4491-4499); only a message queued during compaction shows a status (queueCompactionMessage, 4768). PiG flashed "Queued steering message" and "Queued follow-up message". Found by the queue journey.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1631753dc6 (2026-10-09)
  • Tests: internal/codingagent/interactive_queue_status_test.go::TestQueueingWhileStreamingShowsNoStatusLine
  • Release note: Queueing a steering or follow-up message while the agent streams prints no status line, as Pi does.

BF-163f7b7

Empty quoted prompt-template arguments ("" or '') are kept as empty arguments, so later positional…

  • Source: PiG's own defect
  • Symptom: Empty quoted prompt-template arguments ("" or '') are kept as empty arguments, so later positional arguments no longer shift and ${1:-default} defaults apply (carried Pi fix, Pi 9fe3f371, #10867).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 163f7b719f (2026-10-14)
  • Tests: internal/codingagent/prompt_args_random_oracle_test.go::TestPromptArgumentHandlingMatchesPiOnRandomInputs, internal/codingagent/prompt_templates_oracle_test.go::TestPromptArgumentHandlingMatchesPi, internal/codingagent/upstream_prompt_templates_test.go::TestUpstreamPromptTemplatesArguments
  • Release note: Empty quoted prompt-template arguments ("" or '') are kept as empty arguments, so later positional arguments no longer shift and ${1:-default} defaults apply (carried Pi fix, Pi 9fe3f371, #10867).

BF-165cf58

pig package publish --to npm and pig piglet publish --to npm no longer run an npm that the published…

  • Source: PiG's own defect
  • Symptom: Windows' cmd.exe could pick a Package's own npm.cmd ahead of the author's npm, and a relative npmCommand was resolved inside the Package directory.
  • Root cause: The bash tool on Windows puts the shell in a job object. When the shell exits, processGroupMayHoldOutput asks Windows whether the job still has processes (QueryInformationJobObject, JobObjectBasicAccountingInformation). If the answer is zero, childwait.waitIdle waits for the output pipe to reach EOF with no further bound, because an empty job means no process can still hold the pipe.
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: 2e0f88f794 (2026-10-06, #160), 9a95b36b11 (2026-10-06, #162), a4753ec61e (2026-10-06, #170), ba9c37b247 (2026-10-06, #171), c019ea9155 (2026-10-06, #163), 165cf5884d (2026-10-07, #180), 9b63b2bc4e (2026-10-07, #176), ce8587ef40 (2026-10-07, #179)
  • Tests: coding/cli/rpc_initial_tools_test.go::TestRPCFreshPromptDeclaresStructuredSystemOnce, coding/cli/rpc_shutdown_test.go::TestRPCInputEndAfterExtensionCommandComparedWithPi, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleBusyEntriesAreSkippedNotErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledBeforeStartTouchesNothing, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledDuringTheLastEntryReportsTheCancellation, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleEntryLeasedAfterClassificationIsSkippedNotAnError, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleOtherErrorsStayErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleStopsWhenItsContextIsCancelled and 96 more in docs/bug-ledger.json
  • Release note: pig package publish --to npm and pig piglet publish --to npm no longer run an npm that the published directory contains. PiG finds npm in the absolute directories on PATH, so publishing from inside a Package that ships its own npm works and uses your npm (or resolves a relative npmCommand against the directory you run pig in) and starts that file. Before, Windows' cmd.exe could pick a Package's own npm.cmd ahead of the author's npm, and a relative npmCommand was resolved inside the Package directory.

BF-165cf58-2

On Windows, publishing to npm now works when npm is an npm.cmd at a path with spaces, such as `C:\Program…

  • Source: PiG's own defect
  • Symptom: On Windows, publishing to npm now works when npm is an npm.cmd at a path with spaces, such as C:\Program Files\nodejs.
  • Root cause: The bash tool on Windows puts the shell in a job object. When the shell exits, processGroupMayHoldOutput asks Windows whether the job still has processes (QueryInformationJobObject, JobObjectBasicAccountingInformation). If the answer is zero, childwait.waitIdle waits for the output pipe to reach EOF with no further bound, because an empty job means no process can still hold the pipe.
  • User impact: An operation failed or was rejected
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: 2e0f88f794 (2026-10-06, #160), 56c3f85350 (2026-10-06, #172), 9a95b36b11 (2026-10-06, #162), ba9c37b247 (2026-10-06, #171), c019ea9155 (2026-10-06, #163), 165cf5884d (2026-10-07, #180), 9b63b2bc4e (2026-10-07, #176), ce8587ef40 (2026-10-07, #179)
  • Tests: agent/tool_argument_order_test.go::TestAgentLoopDeliversEditedToolArgumentsInTheHooksOrder, coding/cli/rpc_shutdown_test.go::TestRPCInputEndAfterExtensionCommandComparedWithPi, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleBusyEntriesAreSkippedNotErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledBeforeStartTouchesNothing, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledDuringTheLastEntryReportsTheCancellation, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleEntryLeasedAfterClassificationIsSkippedNotAnError, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleOtherErrorsStayErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleStopsWhenItsContextIsCancelled and 87 more in docs/bug-ledger.json
  • Release note: On Windows, publishing to npm now works when npm is an npm.cmd at a path with spaces, such as C:\Program Files\nodejs. Arguments now reach the shim the way Pi's package commands pass them. An npmCommand that names a script now fails with a hint to name its interpreter first.

BF-167dae3

fix(codingagent): the idle collection runs two GC cycles again so pool victims are freed (D172)

  • Source: PiG's own defect
  • Symptom: fix(codingagent): the idle collection runs two GC cycles again so pool victims are freed (D172)
  • Root cause: An earlier change dropped the runtime.GC() before debug.FreeOSMemory(), on the claim that the second cycle returned nothing. TestIdleCollectionFreesPoolVictims replaces TestIdleCollectionRunsOneGCCycle, which pinned the regression.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 167dae3dc1 (2026-10-14)
  • Tests: internal/codingagent/idle_collect_cost_test.go::TestIdleCollectionFreesPoolVictims, test/perfgate/memory_rows_test.go::TestTmuxIdleMemoryGate

BF-16f8d39

fix(codingagent): keep the clock allowance on the marker header's time.Now line

  • Source: PiG's own defect
  • Symptom: fix(codingagent): keep the clock allowance on the marker header's time.Now line
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 16f8d392cc (2026-10-20)
  • Tests: None recorded.

BF-1827831

The MCP extension's tool_call handler passes a tool's reachesTools the call's input in the member order…

  • Source: PiG's own defect
  • Symptom: The MCP extension's tool_call handler passes a tool's reachesTools the call's input in the member order the model wrote it, as Pi passes event.input, instead of sorted by member name.
  • Root cause: Pi's shutdown() stops the TUI, awaits runtimeHost.dispose() (session_shutdown), prints the resume command when the session file exists and calls process.exit(0); it never awaits the session_start, session_before_switch or session_before_fork handler that asked for the shutdown (interactive-mode.ts:2020-2024, 4251-4290).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 1827831043 (2026-10-13)
  • Tests: coding/mcpext/command_test.go::TestMcpToolCallPassesTheInputInItsMemberOrderToReachesTools, coding/mcpext/extension_test.go::TestAgentSessionMCPOtherToolsDoNotWaitForServers, coding/mcpext/extension_test.go::TestAgentSessionMCPToolSearchAndTheResourceToolsWaitForEveryServer, coding/mcpext/responsive_test.go::TestMcpDirectToolCallWaitsForItsServersReconnect, internal/codingagent/context_files_oracle_test.go::TestLoadProjectContextFilesCaseVariantNamesMatchPi, internal/codingagent/context_files_random_oracle_test.go::TestLoadProjectContextFilesRandomTreesMatchPi, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError and 7 more in docs/bug-ledger.json
  • Release note: The MCP extension's tool_call handler passes a tool's reachesTools the call's input in the member order the model wrote it, as Pi passes event.input, instead of sorted by member name.

BF-1848633

Extension widgets share one key namespace across extensions, as in Pi: an extension that sets a widget key…

  • Source: PiG's own defect
  • Symptom: Two extension processes setting the same key used to both show.
  • Root cause: Pi keeps one widget map per side keyed by the widget key alone (interactive-mode.ts setExtensionWidget removes the key from both maps, then sets it), whichever extension calls it. PiG keyed extension-process widgets by "extName:key" and kept the host-process widgets apart, so two extensions setting one key both showed.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1848633092 (2026-10-13)
  • Tests: coding/extension/host/subprocess/host_calls_test.go::TestReadLoopWidgetPushOrdersAgainstQueuedSlotCalls, coding/extension/host/subprocess/view_terminal_parity_test.go::TestViewsLeaveTheTerminalBytesUnchanged, coding/extension/host/subprocess/widget_key_namespace_test.go::TestTwoExtensionsSettingOneWidgetKeyShareIt, internal/codingagent/widget_key_namespace_test.go::TestHostAndProcessExtensionsShareOneWidgetKeyNamespace
  • Release note: Extension widgets share one key namespace across extensions, as in Pi: an extension that sets a widget key another extension (in the host process or in its own process) already uses replaces that widget, and clearing the key clears it whoever set it. Two extension processes setting the same key used to both show.

BF-184ed40

fix(hljs): a language's reach does not depend on the first text it highlights; red cases for resumeLimit

  • Source: PiG's own defect
  • Symptom: fix(hljs): a language's reach does not depend on the first text it highlights; red cases for resumeLimit
  • Root cause: Resume points were sound either way (the individual patterns, which compileMode records eagerly, bound every alternative), but which points a language offered depended on the process's history, and the over-count hid the margins: no test failed with resumeLimit's reach.lines+1 cut to 1, or with its white-space guard removed. The joined terminators now compile without recording (compileLangRe); every alternative is a pattern compileMode recorded. TestStreamedHighlightEqualsAWholeRun resumes 8,938 of 18,834 steps (7,847 before), all equal to a whole run; go test ./tui/internal/hljs and the tui highlight and Markdown tests pass. - TestResumeLimitHoldsBackPointsLaterTextDecides (apache '['…
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 184ed40f74 (2026-10-11)
  • Tests: tui/highlight_resume_test.go::TestStreamedHighlightEqualsAWholeRun, tui/internal/hljs/resume_test.go::TestLanguageReachDoesNotDependOnTheFirstText, tui/internal/hljs/resume_test.go::TestResumeLimitHoldsBackPointsLaterTextDecides

BF-1896331

Changing the thinking level in interactive mode (cycle key, /thinking, the settings selector) goes through…

  • Source: PiG's own defect
  • Symptom: Changing the thinking level in interactive mode (cycle key, /thinking, the settings selector) goes through the Session like Pi's session.setThinkingLevel: the level is clamped to the model, the thinking_level_select event carries the clamped level, and a handler that is slow to start no longer holds the input loop.
  • Root cause: applyThinkingLevel no longer sets the agent, appends the entry and emits thinking_level_select from a naked goroutine (emitThinkingLevelSelect is deleted).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 18963311a1 (2026-10-08)
  • Tests: coding/session_extension_events_upstream_test.go::TestSessionEmitsThinkingLevelSelectOnlyForAChangeWithTheEffectiveLevel, coding/session_extension_events_upstream_test.go::TestSessionEmitsTurnStartWithThePerRunTurnIndexAndATimestamp, coding/session_extension_events_upstream_test.go::TestSessionSetThinkingLevelOnMainDispatchesOnlyTheMutation, internal/codingagent/interactive_session_append_test.go::TestSelectThinkingLevelShowsAFailedSessionAppend, internal/codingagent/thinking_selector_test.go::TestSelectThinkingLevelDoesNotHoldTheInputLoopOnTheNotification, internal/codingagent/thinking_selector_test.go::TestThinkingLevelPersistsOnlyWhenSaved, test/extension-conformance/event_payload_test.go::TestConformance_EventPayloadsReachEverySDKUnchanged, test/extension-conformance/event_unsubscribe_every_test.go::TestConformance_EventUnsubscribeEveryEvent
  • Release note: Changing the thinking level in interactive mode (cycle key, /thinking, the settings selector) goes through the Session like Pi's session.setThinkingLevel: the level is clamped to the model, the thinking_level_select event carries the clamped level, and a handler that is slow to start no longer holds the input loop.

BF-18daf7f

The Go SDK's typed branch entry keeps the text, thinking and tool calls of an assistant message whose tool…

  • Source: PiG's own defect
  • Symptom: The Go SDK's typed branch entry keeps the text, thinking and tool calls of an assistant message whose tool calls carry object arguments (Pi's ToolCall), and the Rust SDK's BranchEntry decodes Pi's nested session entries (role, text, thinking, tool calls, provider, model and usage were all empty before) and gains id, first_kept_entry_id and UsageInfo::total_tokens.
  • Root cause: The host sends each session entry in Pi's shape ({"type":"message","message":{...}}), and an assistant message's content is a block array whose ToolCall blocks carry their arguments as an object (packages/ai/src/types.ts:423-431).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 18daf7fed2 (2026-10-11)
  • Tests: extensions/sdk/branch_entry_tool_call_test.go::TestBranchEntryDecodeMatchesTheRustSDK, extensions/sdk/branch_entry_tool_call_test.go::TestBranchEntryKeepsAnAssistantMessageWithAPiToolCall, test/extension-conformance/conformance_test.go::TestConformance_TransportsMatch, test/extension-conformance/typed_branch_entries_test.go::TestConformance_TypedBranchEntriesAgreeBetweenGoAndRust
  • Release note: The Go SDK's typed branch entry keeps the text, thinking and tool calls of an assistant message whose tool calls carry object arguments (Pi's ToolCall), and the Rust SDK's BranchEntry decodes Pi's nested session entries (role, text, thinking, tool calls, provider, model and usage were all empty before) and gains id, first_kept_entry_id and UsageInfo::total_tokens.

BF-18f7d39

Codemode @options errors report V8's JSON error text, tool and schema declarations keep unpaired…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: Codemode @options errors report V8's JSON error text, tool and schema declarations keep unpaired surrogates, and an explicitly empty global signature renders as Pi renders it.
  • Root cause: Pi packages/codemode/src/source.ts:parseOptions reports JSON.parse's SyntaxError text after the fixed prefix; PiG reported Go's. declarations.ts keeps lone surrogates in JSON string escapes (const, property names) and distinguishes an explicit empty signature (renderGlobal: signature !== undefined) from an absent one; PiG replaced the surrogate with U+FFFD and treated "" as absent.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 18f7d391a6 (2026-10-05), 97051b184f (2026-10-05), b728733998 (2026-10-05)
  • Tests: codemode/declarations_test.go::TestRenderDeclarationsRendersNamespacedGlobalsAndExplicitSignatures, codemode/upstream_oracle_test.go::TestParseCodemodeSourceMatchesPi, codemode/upstream_oracle_test.go::TestRenderDeclarationsMatchesPi, codemode/upstream_oracle_test.go::TestSchemaToTypeMatchesPi, codemode/upstream_oracle_test.go::TestToCodemodeIdentifierMatchesPi, codemode/upstream_oracle_test.go::TestToolSignatureAndSampleMatchPi, coding/extension/system_prompt_options_wire_test.go::TestBuildSystemPromptOptionsMarshalsCollectionComplete, coding/tool_definition_output_schema_test.go::TestToolDefinitionCarriesOutputSchemaOfBuiltinTools and 2 more in docs/bug-ledger.json
  • Release note: Codemode @options errors report V8's JSON error text, tool and schema declarations keep unpaired surrogates, and an explicitly empty global signature renders as Pi renders it.

BF-1971aae

A bash shell that starts at the moment pig handles an emergency exit, a crash or a signal is now killed…

  • Source: PiG's own defect
  • Symptom: A bash shell that starts at the moment pig handles an emergency exit, a crash or a signal is now killed with the others.
  • Root cause: A shell that had started but was not yet tracked could be missed and outlive pig.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1971aaebd8 (2026-10-06)
  • Tests: durable/env/node/watch_link_alias_test.go::TestNodeFileWatcherReportsALinkedTargetForAnEventNamedByTheResolvedFile, internal/codingagent/context_files_oracle_test.go::TestLoadProjectContextFilesMatchesPi, internal/codingagent/emergency_exit_children_test.go::TestEmergencyTerminalExitHelper, internal/codingagent/session_manager_test.go::TestDefaultSessionDirUsesAgentDirOverride, internal/codingagent/session_path_parity_test.go::TestDefaultSessionDirUsesUpstreamEncoding, internal/codingagent/tools/shell_tracked_test.go::TestKillTrackedDetachedChildrenKillsAShellStartedButNotYetTracked
  • Release note: A bash shell that starts at the moment pig handles an emergency exit, a crash or a signal is now killed with the others. A shell that had started but was not yet tracked could be missed and outlive pig.

BF-19a07f0

fix(sdk-rs): give the request-start wire test's RequestMsg the batch, child_session, tool_call_index and…

  • Source: PiG's own defect
  • Symptom: fix(sdk-rs): give the request-start wire test's RequestMsg the batch, child_session, tool_call_index and args_raw fields
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 19a07f0266 (2026-10-21)
  • Tests: None recorded.

BF-1a855cd

The /session block now follows a later theme change and shows the cache-warming countdown as of each…

  • Source: PiG's own defect
  • Symptom: The /session block now follows a later theme change and shows the cache-warming countdown as of each redraw, as in Pi.
  • Root cause: handleSessionCommand snapshots the statistics and adds new ThemedText(renderInfo, 1, 0), so the text is rebuilt on every render with the active theme and the cache-warming status as of that render. PiG built a static string once.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1a855cde32 (2026-10-13)
  • Tests: internal/codingagent/builtin_tool_cards_oracle_test.go::TestBuiltinToolCardsMatchPi, internal/codingagent/codemode_cards_oracle_test.go::TestCodemodeCardsMatchPi, internal/codingagent/session_info_themed_test.go::TestSessionCommandBlockFollowsThemeChanges, internal/codingagent/tool_codemode_timer_test.go::TestCodemodeRowShowsElapsedWhileRunningAndTookOnceFinished, tui/editor_status_test.go::TestEmbeddedCompactionStatusTakesTheEditorBorderColorLikeWorking, tui/terminal_image_test.go::TestHerdrWithGraphicsSignalRendersKittyImage
  • Release note: The /session block now follows a later theme change and shows the cache-warming countdown as of each redraw, as in Pi. It kept the colors and the status text from the moment the command ran.

BF-1b5540a

client.Client.Disconnect takes the reason string as Pi's Client.disconnect(reason) does (it was an…

  • Source: PiG's own defect
  • Symptom: client.Client.Disconnect takes the reason string as Pi's Client.disconnect(reason) does (it was an error); the connection fails with a DisconnectedError carrying it.
  • Root cause: Client.disconnect(reason = "Client disconnected") accepts a string and Connection.disconnect wraps a string in a DisconnectedError; Go's Client.Disconnect took an error, so a Radius reconnect client (Disconnect(string)) could not be the real Client. Tests: the disconnect-reason cases now assert the DisconnectedError message; a mutant that ignores the reason fails TestClientConnectionLifecycleAndStaleTransport and TestClientRequestPreconditionsAndDisposal.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1b5540aec7 (2026-10-07)
  • Tests: durable/storage/jsonl/jsonl_storage_reads_test.go::TestJsonlStorageReadsCommittedRecordsRecoversThemAndFailsAfterClose, durable/tools/bash_input_test.go::TestBashToolInputIsTheBashSchemaArguments
  • Release note: client.Client.Disconnect takes the reason string as Pi's Client.disconnect(reason) does (it was an error); the connection fails with a DisconnectedError carrying it.

BF-1c6b864

A Session that an extension creates with createAgentSession, or that coding.CreateAgentSession returns…

  • Source: PiG's own defect
  • Symptom: A Session that an extension creates with createAgentSession, or that coding.CreateAgentSession returns, now reports entry_appended to its listeners when an extension that its resource loader loaded calls pi.appendEntry, as Pi does.
  • Root cause: A Session whose resource loader started the extensions (a child session an extension creates, or coding.CreateAgentSession) wrote the entry that pi.appendEntry appended to the log and told no listener. Pi's appendEntry action also emits entry_appended (agent-session.ts:3321-3327), before the event the extension was handling reaches the listeners; ctx.sessionManager. appendCustomEntry emits none. The loader host's appendEntry action now emits the event the same way every mode's action does.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 1c6b8643ab (2026-10-25)
  • Tests: None recorded.
  • Release note: A Session that an extension creates with createAgentSession, or that coding.CreateAgentSession returns, now reports entry_appended to its listeners when an extension that its resource loader loaded calls pi.appendEntry, as Pi does. The event arrives before the event the extension was handling, and ctx.sessionManager.appendCustomEntry still writes the log without an event. The Session wrote the entry and told no listener.

BF-1ca0e2f

fix(codingagent): the terminal reader yields to the main loop it readied before it polls again

  • Source: PiG's own defect
  • Symptom: fix(codingagent): the terminal reader yields to the main loop it readied before it polls again
  • Root cause: The reader readies the main loop on its own P and then blocks in a poll system call that keeps the P, so the main loop waited for another thread to steal it.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 1ca0e2facc (2026-10-13)
  • Tests: None recorded.

BF-1cc7519

fix(sdk): a command, shortcut, flag or renderer registered after load reaches the host in every SDK

  • Source: PiG's own defect
  • Symptom: fix(sdk): a command, shortcut, flag or renderer registered after load reaches the host in every SDK
  • Root cause: Pi's registerCommand/registerShortcut/registerFlag/registerMessageRenderer/ registerEntryRenderer write the extension's own Map and every runner getter reads it live, so a registration from a handler takes effect. Node, Go and Python kept it in a local map and dropped it (probed: the late-registrations row failed with the late entries missing). Each SDK now sends registerDeclaration after connect (Go and Python under the registration lock, Node synchronously like registerTool); Rust, whose Extension is consumed by run, gains Context::register_command/_shortcut/_flag/ _message_renderer/_entry_renderer backed by per-connection late tables the dispatcher consults first. Go SDK lookups take…
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 1cc75198f6 (2026-10-10)
  • Tests: None recorded.

BF-1ce00b0

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: Cache schema pattern regexps (typebox.CompilePattern) for typebox and chord objectFits; read Description members from their own JSON text instead of a re-encode; encode nested result values in place; one wire marshal in WorkerToHostMessage; cap the entered dump-buffer pool; no Sprintf in the /session cost loop. Proven cold sites carry //perf:ok.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 1ce00b0a53 (2026-10-13)
  • Tests: None recorded.

BF-1d02c73

Filters that Pi writes with toLowerCase() (select lists, the config selector, file autocomplete, /model…

  • Source: PiG's own defect
  • Symptom: Filters that Pi writes with toLowerCase() (select lists, the config selector, file autocomplete, /model search, minimatch nocase) lowercase as JavaScript does (İ becomes i̇, a final sigma ς), not as Go's strings.ToLower.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 68a4ed2b54 (2026-10-09), 1d02c73a90 (2026-10-10)
  • Tests: internal/codingagent/fullscreen_modal_viewport_test.go::TestFullscreenSelectionKeepsItsPageKeys, internal/codingagent/fullscreen_modal_viewport_test.go::TestRegularModeSelectorPageKeyMovesItsSelection, internal/codingagent/llama/ui_test.go::TestHuggingFaceSearchPageKeysMoveTenResults, internal/codingagent/modal_viewport_input_test.go::TestEditorSlotSelectorPageKeyMovesSelectionInEveryMode, internal/codingagent/select_page_keys_test.go::TestCustomEditorGivesSelectionPageKeysToAnOpenAutocomplete, internal/codingagent/select_page_keys_test.go::TestFirstTimeSetupPageKeysMoveToTheEnds, internal/codingagent/select_page_keys_test.go::TestSessionSelectorCapturesSelectionPageKeys, internal/codingagent/select_page_keys_test.go::TestTrustSelectorPageKeysMoveToTheEnds and 9 more in docs/bug-ledger.json
  • Release note: Filters that Pi writes with toLowerCase() (select lists, the config selector, file autocomplete, /model search, minimatch nocase) lowercase as JavaScript does (İ becomes i̇, a final sigma ς), not as Go's strings.ToLower.

BF-1d1c8aa

RPC mode now ends by the signal when a termination signal arrives while a quit session_shutdown handler is…

  • Source: PiG's own defect
  • Symptom: RPC mode now ends by the signal when a termination signal arrives while a quit session_shutdown handler is pending.
  • Root cause: RPC mode exited 0 instead of by SIGTERM when the extension processes PiG stops before dying completed a pending quit dispose or reported a drained loop; no exit starts once the signal's default action begins. The core-skills fixture used skill.Path (Pi: filePath). The theme border test resolved the theme in the terminal's color mode, where 256-color collapses two levels as in Pi. The MCP trust test expanded Windows 8.3 short names that the process never sees. go fix applied; pig-go.json regenerated.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1d1c8aa106 (2026-10-06)
  • Tests: ai/union_constants_upstream_test.go::TestUpstreamStringUnionsMatchTheGoConstants, ai/utils_exports_upstream_test.go::TestGetOverflowPatternsMatchesUpstreamList, tui/theme_attribute_helpers_test.go::TestThemeBorderColorFunctions
  • Release note: RPC mode now ends by the signal when a termination signal arrives while a quit session_shutdown handler is pending. The extension processes PiG stops first could complete the pending shutdown or report a drained event loop, and the process exited 0 instead of by the signal.

BF-1d5b272

A text file passed as an @file argument that holds invalid UTF-8 now reaches the prompt as Pi sends it…

  • Source: PiG's own defect
  • Symptom: A text file passed as an @file argument that holds invalid UTF-8 now reaches the prompt as Pi sends it: each ill-formed byte sequence becomes one U+FFFD replacement character.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1d5b272feb (2026-10-09), 433d232815 (2026-10-09)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/streaming_json_resume_oracle_test.go::TestStreamingArgumentsParserMatchesPiAtEveryChunk and 3 more in docs/bug-ledger.json
  • Release note: A text file passed as an @file argument that holds invalid UTF-8 now reaches the prompt as Pi sends it: each ill-formed byte sequence becomes one U+FFFD replacement character. Pig previously copied the raw bytes, which later became one replacement character per byte.

BF-1dcaef7

Durable: a JSONL line that starts with a byte order mark now opens, as in Pi, instead of failing with…

  • Source: PiG's own defect
  • Symptom: Durable: a JSONL line that starts with a byte order mark now opens, as in Pi, instead of failing with "Malformed complete ... line N".
  • Root cause: Lineage through a missing parent conversation now fails as in Pi. Pi's findLatestHeadMarker, visibleEntries and visibleEntriesAscending (memory.ts:509, :695, :717) read .parent of a conversation that is not stored and throw "Cannot read properties of undefined (reading 'parent')"; commit does not check that a parent exists (memory.ts:266-287), so a caller or a dropped commit leaves one dangling. Pig stopped at the missing record and returned a truncated history (scanEntries even returned items). Each walk now returns that error at the same point, after the entries Pi yields first. The recovery differential no longer overwrites Pig's answer with Pi's TypeError; danglingParentPrograms pins…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1dcaef738b (2026-10-10), f71185dc7a (2026-10-10)
  • Tests: durable/storage/jsonl/recovery_rewrite_test.go::TestJsonlRecoveryIgnoresALeadingByteOrderMarkOnEachLine, durable/storage/jsonl/recovery_rewrite_test.go::TestJsonlRecoveryRewritesReclaimedSidecarRecordsCanonically, durable/storage/jsonl_pi_oracle_test.go::TestJsonlStorageMatchesPiOnSeededPrograms, durable/storage/jsonl_recovery_pi_oracle_test.go::TestJsonlRecoveryMatchesPiOnDamagedDirectories, durable/storage/memory_dangling_parent_test.go::TestMemoryStorageLineageThroughAMissingParentConversationFails, durable/storage/memory_pi_oracle_test.go::TestMemoryStorageMatchesPiOnSeededPrograms, durable/storage/memory_status_test.go::TestMemoryStorageRejectsAnUnknownTaskOrSubmissionStatus
  • Release note: Durable: a JSONL line that starts with a byte order mark now opens, as in Pi, instead of failing with "Malformed complete ... line N". When recovery reclaims a sidecar file it writes the records it keeps in canonical JSON, as Pi does, instead of copying their original bytes.

BF-1ed43b3

fix(train): load pig_sdk.tui with the package, run the TS queue check with no runtime, pin three fixture…

  • Source: PiG's own defect
  • Symptom: fix(train): load pig_sdk.tui with the package, run the TS queue check with no runtime, pin three fixture modules to the root SDK version
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 1ed43b3d96 (2026-10-21)
  • Tests: None recorded.

BF-1ee7dea

The Mistral request URL for a base URL with a non-special scheme or a host or port the WHATWG parser rejects…

  • Source: PiG's own defect
  • Symptom: The Mistral request URL for a base URL with a non-special scheme or a host or port the WHATWG parser rejects (for example a port above 65535) follows Pi's new URL: it fails where Pi throws and keeps the base path where Pi does.
  • Root cause: The URL expression of requestMistralStream (mistral-conversations.ts:303-305: new URL(baseUrl); pathname set to the trimmed path plus '/'; new URL('v1/chat/completions', baseUrl)) runs in Node (testdata/mistral_url.mjs) on 4514 seeded base URLs. It is now ParseURL, the pathname setter (WithPathname) and ResolveRelativePath; the hand-rolled authority normalizer and resolveDirectoryPath are deleted. The file drive-letter rule cannot trigger here because the directory path always ends in '/'.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), 0b4e1e3376 (2026-10-09), 15a5c0b670 (2026-10-09), 1ee7dea3e4 (2026-10-09), 23aa78b173 (2026-10-09), 66b0fc0539 (2026-10-09), 7c4d874438 (2026-10-09), a739b6dcf2 (2026-10-09), c4d51d5c1a (2026-10-09), d80c1cd150 (2026-10-09), da8a8704c6 (2026-10-09)
  • Tests: ai/anthropic_token_endpoint_oracle_test.go::TestAnthropicTokenEndpointRuleMatchesSDK, ai/bedrock_region_oracle_test.go::TestBedrockEndpointRegionMatchesPi, ai/chatgpt_manual_input_oracle_test.go::TestChatGPTManualInputMatchesPiOnSeededProbes, ai/codex_account_id_oracle_test.go::TestCodexAccountIDMatchesPi, ai/codex_vertex_urls_oracle_test.go::TestCodexWebSocketAndVertexVersionURLsMatchPi, ai/githubcopilot_oracle_test.go::TestGitHubCopilotHelpersMatchPi, ai/mistral_url_oracle_test.go::TestMistralChatCompletionsURLMatchesPiOnSeededProbes, ai/trusted_url_oracle_test.go::TestTrustedVerificationURLsMatchPi and 2 more in docs/bug-ledger.json
  • Release note: The Mistral request URL for a base URL with a non-special scheme or a host or port the WHATWG parser rejects (for example a port above 65535) follows Pi's new URL: it fails where Pi throws and keeps the base path where Pi does.

BF-1f7fa2f

In print and JSON mode, an extension in its own Node process no longer runs its callbacks with a stale ctx…

  • Source: PiG's own defect
  • Symptom: In print and JSON mode, an extension in its own Node process no longer runs its callbacks with a stale ctx when a later extension's handler awaits a timer or I/O.
  • Root cause: Pi's runner awaits each extension's handler in turn on one event loop (runner.ts:1089-1116). When a later extension's session_shutdown or agent_settled handler awaits a timer or I/O, that loop runs the immediate or timer an earlier extension's handler queued, with a live ctx, before print mode invalidates the runner (agent-session-runtime.ts:404-410, agent-session.ts:1393-1406). With isolated Node processes the earlier process waited for the host's next step after answering (), so its callbacks ran only after invalidate, or never, because the host stopped the process first. Packed processes share one loop and matched Pi.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 311609427b (2026-10-07), 382156cdb7 (2026-10-07), 49798a80d2 (2026-10-07), 8723a419d7 (2026-10-07), 8d990c5d54 (2026-10-07), a79de03e7f (2026-10-07), 1f7fa2fe22 (2026-10-09)
  • Tests: coding/cli/resource_loader_gaps_upstream_test.go::TestPrintModeInvalidDiscoveredFileURLFailsBind, coding/extension/host/inproc/upstream_scoped_models_test.go::TestContextThinkingLevelReadsTheBoundRuntimeLive, coding/extension/host/subprocess/runtime_node_loop_turn_test.go::TestNodeLoopTurnRunsQueuedImmediatesBeforeLaterFrames, coding/extension/host/subprocess/runtime_node_prompt_end_test.go::TestNodePromptEndWaitEndsWhenAnotherProcessTurnsTheLoop, coding/extension/widget_placement_test.go::TestPowerShellToolInputIsTheBashInputShape, coding/mcpext/extension_test.go::TestAgentSessionMCPHoldsTheFirstPromptForServersWithDirectToolsOnlyUpToTheStartupWait, coding/session_extend_resources_test.go::TestSessionExtendsItsLoaderAfterExtensionReplacement, coding/session_extend_resources_test.go::TestSessionExtendsItsLoaderFromResourcesDiscover and 10 more in docs/bug-ledger.json
  • Release note: In print and JSON mode, an extension in its own Node process no longer runs its callbacks with a stale ctx when a later extension's handler awaits a timer or I/O. Pi runs every extension on one event loop, so the immediate or timer that one extension's session_shutdown or agent_settled handler queues runs while the next extension's handler waits, before the runner is invalidated. The Node runtime now reports such a wait with a loop_turned notification, which the host forwards to every other Node process as its next step. Packed extensions already shared one loop and behave as before; RPC and interactive mode are unchanged.

BF-1fbdabb

A CRLF that a proxy splits across two network chunks no longer aborts an Anthropic turn with `Could not parse…

  • Source: A Pi defect that PiG fixed first (divergence D121)
  • Symptom: A CRLF that a proxy splits across two network chunks no longer aborts an Anthropic turn with Could not parse Anthropic SSE event message_start (D121, pi-mono #10390).
  • Root cause: Pi anthropic-messages.ts:454-466 consumeLine ends the line at a trailing CR, so the LF of a split CRLF flushes the record without data and the turn aborts. Wait for the next chunk.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10390
  • Removed when Pi fixes it: Pi holds back a trailing \r in consumeLine.
  • Fix commits: 1fbdabb354 (2026-10-09), 40bc651bae (2026-10-10), c3558fbe52 (2026-10-12)
  • Tests: ai/anthropic_sse_crlf_split_test.go::TestAnthropicStreamSurvivesCRLFSplitAcrossChunks, ai/anthropic_stream_test.go::TestAnthropicSSEReaderLineDecoding
  • Release note: A CRLF that a proxy splits across two network chunks no longer aborts an Anthropic turn with Could not parse Anthropic SSE event message_start (D121, pi-mono #10390).

BF-1fcf017

toUpperCase ports use a shared JavaScript jsstring.ToUpper (matches Node over every code point).…

  • Source: PiG's own defect
  • Symptom: toUpperCase ports use a shared JavaScript jsstring.ToUpper (matches Node over every code point). minimatch nocase folds ᾀ and ß as JavaScript does.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1d02c73a90 (2026-10-10), 1fcf01772a (2026-10-10), 79b035f49f (2026-10-10)
  • Tests: durable/core/history/js_space_agreement_test.go::TestIsJSSpaceAgreesWithJsstringIsSpace, internal/chord/chordjson/own_keys_index_bound_test.go::TestOwnKeysArrayIndexStopsBelowTwoToTheThirtyTwoMinusOne, internal/jsstring/upper_oracle_test.go::TestToUpperMatchesNode, internal/minimatch/canonicalize_upper_test.go::TestCanonicalizeUsesJavaScriptUpperCase, tui/select_page_keys_test.go::TestSelectListFilterLowercasesAsJavaScriptDoes, tui/widthx/js_space_agreement_test.go::TestIsJSSpaceAgreesWithJsstringIsSpace
  • Release note: toUpperCase ports use a shared JavaScript jsstring.ToUpper (matches Node over every code point). minimatch nocase folds ᾀ and ß as JavaScript does.

BF-202b8f1

On arm64 and other CPUs with a fused multiply-add, the OKHSL saturation of a color (used when generating the…

  • Source: PiG's own defect
  • Symptom: It differed from Pi's in the last bit for about 9% of sRGB colors.
  • Root cause: rgbToOkhsl assigns k1 := 0.8 c0 and adds it in k1 + (1 - k1/cMid) chroma. Assigning a product does not round it in Go: on arm64 the compiler fused 0.8 * c0 into that addition (FMADD), so ColorToOkhsl's saturation, which the system theme generator reads, differed from Pi's oklab.ts:225-226 in the last bit for 1523533 of the 16.7M sRGB colors (633339 of those then match Pi exactly; the others differ from Pi through Go's Pow/Cbrt/Atan2 as well).
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 202b8f1943 (2026-10-13), 7633906d03 (2026-10-13), a00dbe6901 (2026-10-13), bd3e8ab24b (2026-10-13)
  • Tests: ai/device_code_poll_oracle_test.go::TestDeviceCodePollDeadlineRoundsTheProductLikePi, ai/oauth_expiry_arithmetic_test.go::TestBuiltinOAuthExpiryUsesJavaScriptArithmetic, ai/openai_codex_http_error_test.go::TestCodexHTTPErrorResetMinutesRoundTheProductFirst, internal/codingagent/cache_warmer_test.go::TestCacheWarmingExpectedSavingsRoundsTheProductLikePi, internal/codingagent/pig_egg3d_test.go::TestPig3dFlightHomeOffsetsRoundLikePi, internal/codingagent/radius_login_oracle_test.go::TestRadiusLoginMenuRendersLikePi, internal/jsmath/log_oracle_test.go::TestLogMatchesNode, tui/colors_test.go::TestColorToOkhslRoundsTheChromaStopProduct
  • Release note: On arm64 and other CPUs with a fused multiply-add, the OKHSL saturation of a color (used when generating the system theme's colors) rounds 0.8 * c0 before adding it, as Pi does. Before, it differed from Pi's in the last bit for about 9% of sRGB colors.

BF-205933e

fix(ai): a Gemini model outside the catalog carries the provider's API, so replay's isSameModel matches Pi…

  • Source: PiG's own defect
  • Symptom: fix(ai): a Gemini model outside the catalog carries the provider's API, so replay's isSameModel matches Pi (providers-registry/04 googleToolIds and googleThinkingSig were red since the catalog dropped gemini-3-pro: the fallback Model had no api, which made an assistant message without one the same model and one with the provider's api another model).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 205933ee87 (2026-10-10)
  • Tests: ai/google_uncataloged_model_test.go::TestGoogleUncatalogedModelHasTheProvidersAPIForReplay

BF-20718f7

fix(coding-agent): resolve model references through one provider selection policy (Pi)

  • Source: PiG's own defect
  • Symptom: fix(coding-agent): resolve model references through one provider selection policy (Pi)
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 20718f71ec (2026-10-14)
  • Tests: coding/cli/credential_print_test.go::TestCredentialPrintModelWithoutProviderUsesCatalogOfConfiguredProviders, internal/codingagent/model_reference_upstream_test.go::TestResolveCliModelDuplicateRawIDsMatchesUpstream, internal/codingagent/model_reference_upstream_test.go::TestResolveModelReferenceMatchesUpstream, internal/codingagent/model_reference_upstream_test.go::TestResolveModelScopeAmbiguousProvidersMatchesUpstream

BF-20aee29

The crash notice prints the year of a crash time the way Node's toLocaleString does: unpadded, and counted…

  • Source: PiG's own defect
  • Symptom: The crash notice prints the year of a crash time the way Node's toLocaleString does: unpadded, and counted back from 1 for years before 1 CE.
  • Root cause: interactive-mode.ts:1222 prints new Date(crash.timestamp).toLocaleString(). The Go notice used the layout "1/2/2006, 3:04:05 PM", whose year is zero padded to four digits and signed before 1 CE; en-US toLocaleString prints the Gregorian era year unpadded and, before 1 CE, counted back from 1 without a sign ("4/15/151", "10/13/1721" for 1721 BCE). enUSDateTime now builds the string. A crash record's year is the current one in practice, so this shows only for a forged or damaged record. The locale stays en-US: Node uses the process locale (LANG=de_DE gives "31.12.1969, 16:00:00"), which is not modelled and not recorded as a divergence pending the owner.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 20aee29744 (2026-10-11)
  • Tests: internal/codingagent/crash_notice_date_oracle_test.go::TestCrashNoticeTimeMatchesToLocaleString
  • Release note: The crash notice prints the year of a crash time the way Node's toLocaleString does: unpadded, and counted back from 1 for years before 1 CE.

BF-211af49

/hotkeys lists extension shortcuts, and the extension shortcut listener tries them, in the order of Pi's…

  • Source: PiG's own defect
  • Symptom: /hotkeys lists extension shortcuts, and the extension shortcut listener tries them, in the order of Pi's getShortcuts Map (load order, registration order, a re-registered key keeps its first position) instead of path then key / Go map order (coding/extension/host/inproc/runner.go OrderedShortcuts, internal/codingagent/interactive_hotkeys.go, interactive_helpers.go; TestRunnerShortcutsMatchPi now compares Map order, regression TestHotkeysCommandListsExtensionShortcutsInRegistrationOrder).
  • Root cause: Runner.OrderedShortcuts returns getShortcuts' entries in Map iteration order (first-registration position, last value); Shortcuts is the map view. /hotkeys sorted by extension path then key and the listener ranged a Go map, so Pi's registration order and first-match-wins for overlapping spellings of one key were lost.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 211af490e0 (2026-10-11), 4ae159f3cd (2026-10-11), a2755058da (2026-10-11), c4ad739eb8 (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, coding/discover_extensions_oracle_test.go::TestDiscoverExtensionPathsMatchPi, coding/extension/host/inproc/registries_test.go::TestShortcuts_DiagnosticMessageMatchesUpstream, coding/extension/host/inproc/shortcuts_oracle_test.go::TestRunnerShortcutsMatchPi, internal/codingagent/armin_test.go::TestArminFramesMatchPinnedPi and 13 more in docs/bug-ledger.json
  • Release note: /hotkeys lists extension shortcuts, and the extension shortcut listener tries them, in the order of Pi's getShortcuts Map (load order, registration order, a re-registered key keeps its first position) instead of path then key / Go map order (coding/extension/host/inproc/runner.go OrderedShortcuts, internal/codingagent/interactive_hotkeys.go, interactive_helpers.go; TestRunnerShortcutsMatchPi now compares Map order, regression TestHotkeysCommandListsExtensionShortcutsInRegistrationOrder).

BF-2165463

On Windows, saving credentials to auth.json, models.json or the MCP OAuth store no longer fails at once…

  • Source: PiG's own defect
  • Symptom: On Windows, saving credentials to auth.json, models.json or the MCP OAuth store no longer fails at once with EPERM: operation not permitted, mkdir '….lock' when another process has just released the lock and Windows still holds the directory pending deletion.
  • Root cause: Not recorded.
  • User impact: Credential or permission exposure
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: 21654635e7 (2026-10-13)
  • Tests: internal/codingagent/config_schemas_runtime_upstream_test.go::TestConfigSchemasAcceptCustomCompatibilitySettingsAndRejectMalformedKnownFields, internal/codingagent/config_schemas_runtime_upstream_test.go::TestConfigSchemasAcceptSchemaInKeybindingsJSONWithoutTreatingItAsAnAction, internal/codingagent/config_schemas_runtime_upstream_test.go::TestConfigSchemasAcceptSchemaInSettingsJSON, internal/codingagent/config_schemas_runtime_upstream_test.go::TestConfigSchemasValidateSchemaInModelsJSONAtRuntime, internal/pilock/delete_pending_windows_test.go::TestAcquireRetriesLockBeingRemovedAtMkdirThenSurfacesIt, internal/pilock/delete_pending_windows_test.go::TestAcquireSurfacesLockBeingRemovedAtStatThatIsStaleOrALink, internal/pilock/delete_pending_windows_test.go::TestAuthAcquireRetriesLockBeingRemovedAtStatThatIsStaleOrALink, internal/pilock/delete_pending_windows_test.go::TestAuthAcquireWaitsOutLockBeingRemovedAtMkdir and 4 more in docs/bug-ledger.json
  • Release note: On Windows, saving credentials to auth.json, models.json or the MCP OAuth store no longer fails at once with EPERM: operation not permitted, mkdir '….lock' when another process has just released the lock and Windows still holds the directory pending deletion. As in Pi 1.2.0, the lock is retried: the synchronous path within its ten attempts, the cancellable path up to five times. Settings and trust locks still fail on the first EPERM, as in Pi.

BF-224dbd9

On Linux and macOS, the bash tool, a ! command and an extension's exec no longer report no output for a…

  • Source: PiG's own defect
  • Symptom: On Linux and macOS, the bash tool, a ! command and an extension's exec no longer report no output for a command that printed, when the machine is too busy to run PiG's pipe reader within 100 ms of the command's exit.
  • Root cause: childwait.Wait ends the post-exit wait when the 100 ms grace of Pi's waitForChildProcess elapses and mayHoldOutput says a descendant may hold the pipe, which on Unix is always true (a process that left the group can). The grace measures a quiet writer, but the reader that was to take the output had not run for that long on a busy machine, so what the shell wrote before it exited was still unread in the pipe and Wait closed it and dropped it: the bash tool, user bash and an extension's exec reported no output for a command that printed. A reader that had taken a chunk but not yet handed it over lost it the same way when the grace released a quiet held pipe in that gap.
  • User impact: Lost or altered data
  • Platform: macOS, Linux
  • Fixed in: Unreleased
  • Fix commits: 224dbd923f (2026-10-24)
  • Tests: internal/childwait/pipe_blocked_unix_test.go::TestPollPipeAnswersWhileAReaderIsBlockedInReadingIt, internal/childwait/pipe_unix_test.go::TestPipesWaitDeliversEveryPipeWhenOneEndedAndAHolderKeepsTheOther, internal/childwait/pipe_unix_test.go::TestPipesWaitDeliversWhatWaitsUnreadInAPipeAHolderKeepsOpen, internal/childwait/pipe_unix_test.go::TestPipesWaitReadsToEOFAfterTheGraceWhenEveryWriteEndIsClosed, internal/childwait/pipe_unix_test.go::TestPollPipeReportsTheWriteEndsAndTheBytesOfAPipe, internal/childwait/pipe_unix_test.go::TestPollPipeTakesNothingAndLeavesTheReaderWorking, internal/childwait/wait_test.go::TestPipesWaitDeliversAChunkAReaderTookBeforeTheGraceReleasedTheHeldPipe, internal/childwait/wait_test.go::TestWaitIdleArmsTheGraceAgainWhileAPipeHoldsUnreadOutput and 3 more in docs/bug-ledger.json
  • Release note: On Linux and macOS, the bash tool, a ! command and an extension's exec no longer report no output for a command that printed, when the machine is too busy to run PiG's pipe reader within 100 ms of the command's exit. PiG ended the wait for the output pipe after Pi's 100 ms post-exit grace even though the command had written its output before it exited and the reader had not yet taken it, so the output was lost; on a loaded machine echo answer came back as (no output). PiG now asks the pipe itself when the grace elapses: a pipe that no process holds is read to its end, a pipe that holds output nobody has read yet keeps the wait going, and only a pipe that a background process still holds and that is quiet is released after the grace, as in Pi.

BF-2280bf8

The per-API stream entry points (ai.AnthropicMessagesAPI() through ai.PiMessagesAPI() and the deprecated…

  • Source: PiG's own defect
  • Symptom: The per-API stream entry points (ai.AnthropicMessagesAPI() through ai.PiMessagesAPI() and the deprecated ai.StreamX/ai.StreamSimpleX aliases) end a failure inside the API's own stream the way Pi's API module does: the final message carries the module's api (its own API for azure-openai-responses, bedrock-converse-stream, google-generative-ai, google-vertex and openai-codex-responses, the model's api otherwise), and a request aborted before it starts stops with aborted instead of error.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), 75c06b6556 (2026-10-09), 2280bf8320 (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/anthropic_messages_payload_oracle_test.go::TestAnthropicMessagesPayloadMatchesPi and 4 more in docs/bug-ledger.json
  • Release note: The per-API stream entry points (ai.AnthropicMessagesAPI() through ai.PiMessagesAPI() and the deprecated ai.StreamX/ai.StreamSimpleX aliases) end a failure inside the API's own stream the way Pi's API module does: the final message carries the module's api (its own API for azure-openai-responses, bedrock-converse-stream, google-generative-ai, google-vertex and openai-codex-responses, the model's api otherwise), and a request aborted before it starts stops with aborted instead of error. Only streamSimple's missing-key check stays a setup error carrying the model's api.

BF-2309604

Tool schemas in OpenAI Completions, OpenAI Responses, Codex and Mistral request bodies keep the members in…

  • Source: PiG's own defect
  • Symptom: Tool schemas in OpenAI Completions, OpenAI Responses, Codex and Mistral request bodies keep the members in the order the tool declares them.
  • Root cause: Pi passes a tool's parameters object through to the body (openai-completions.ts convertTools :1506-1514, openai-responses-shared.ts :386-390), so JSON.stringify writes the schema's members in declaration order; a strict schema is the same object rebuilt member by member (constrained-sampling.ts makeJsonSchemaNodeStrict: required, then additionalProperties are assigned after the existing members, and an optional property becomes {anyOf: [property, {type: "null"}]}). PiG copied ToolSchema.Parameters as a map[string]any and dropped the recorded parameterOrder, so every schema object was written in sorted order.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2309604d80 (2026-10-10), c10cde302d (2026-10-10), f29e906cb3 (2026-10-10), fe16108cc7 (2026-10-10)
  • Tests: agent/assistant_member_order_oracle_test.go::TestAssistantLateMembersFollowPiCreationOrder, ai/anthropic_messages_payload_oracle_test.go::TestAnthropicMessagesPayloadMatchesPi, ai/constrained_sampling_oracle_test.go::TestConstrainedSamplingMatchesPi, ai/openai_request_key_order_oracle_test.go::TestOpenAICompletionsRequestKeysFollowPiAssignmentOrder, ai/openai_responses_request_key_order_oracle_test.go::TestOpenAIResponsesRequestKeysFollowPiAssignmentOrder, ai/perf_gate_norace_test.go::TestProviderRequestHistoryAllocationGate, ai/request_body_escaping_test.go::TestProviderRequestBodiesWriteCharactersAsJSONStringifyDoes, coding/cli/rpc_assistant_member_order_test.go::TestRPCAssistantMessageMembersMatchTheSessionLineOrder and 2 more in docs/bug-ledger.json
  • Release note: Tool schemas in OpenAI Completions, OpenAI Responses, Codex and Mistral request bodies keep the members in the order the tool declares them. Strict schemas add required and then additionalProperties, as Pi does. cache_control comes after the members of the tool or text part that carries it.

BF-2316fe3

The Piglet JSON Schema that pig piglet schema prints rejected powershell in tools, which `pig piglet…

  • Source: PiG's own defect
  • Symptom: The Piglet JSON Schema that pig piglet schema prints rejected powershell in tools, which pig piglet validate accepts as a built-in tool, and stated nothing for most of the other closed vocabulary.
  • Root cause: The schema listed seven built-in tools while the parser accepts eight, so it rejected a Piglet that used powershell. An audit of every enum and field found more drift: the agentEnv runtime modes and policy presets, the strip IDs, the name, build target, output name, release version and secret patterns, the required keys of a mount, and the exactly-one agentEnv form were all unstated.
  • User impact: Credential or permission exposure
  • Platform: Windows
  • Fixed in: 0.5.3
  • Fix commits: 2316fe38fb (2026-10-22)
  • Tests: coding/piglet/internal/genstripids/main_test.go::TestCommittedSchemaStripEnumsMatchRegistrations, coding/piglet/schema_test.go::TestAC1SchemaParserAgreement, coding/piglet/schema_vocabulary_test.go::TestSchemaAcceptsEveryPigletTheRepositoryShips, coding/piglet/schema_vocabulary_test.go::TestSchemaAgreesWithParserOnEveryValueOfASet, coding/piglet/schema_vocabulary_test.go::TestSchemaEnumsMatchParserSets, coding/piglet/schema_vocabulary_test.go::TestSchemaFieldsMatchParserFields, coding/piglet/schema_vocabulary_test.go::TestSchemaNeverRejectsWhatTheParserAccepts
  • Release note: The Piglet JSON Schema that pig piglet schema prints rejected powershell in tools, which pig piglet validate accepts as a built-in tool, and stated nothing for most of the other closed vocabulary. It now lists all eight built-in tools, the strip IDs of each strip list, the agentEnv runtime modes (inject, image) and policy presets (minimal, standard, elevated), and carries the parser's name, build target, output name, release version and secret patterns, the required source and target of a mount, and the rule that agentEnv names exactly one of image, devContainer and source. make piglet-strip-ids writes the strip ID enums, and tests that read the parser's sets from code fail when the schema and the parser disagree.

BF-234316d

On Windows, a Go extension's source hash no longer fails, or makes a build beside it fail, with `The process…

  • Source: PiG's own defect
  • Symptom: On Windows, a Go extension's source hash no longer fails, or makes a build beside it fail, with The process cannot access the file because it is being used by another process.
  • Root cause: both source hash walks (subprocess walkDirSources and runtimecell hashGoModuleDir) listed directories with os.ReadDir, whose open on Windows shares read and write but not delete. A handle without FILE_SHARE_DELETE has two effects on a build running beside the hash, which empties and fills the same trees: any delete or rename of the directory while the walk has it open fails with a sharing violation, and a directory that another process holds open for delete (os.RemoveAll does, for an instant) makes the walk's own open fail with one. A throwaway Windows run of the untagged churn tests failed 100 of 100 and 96 of 100 on that ("open ...\rmeta27: The process cannot access the file because it is…
  • User impact: An operation failed or was rejected
  • Platform: Windows
  • Fixed in: Unreleased
  • Fix commits: 234316d435 (2026-10-24)
  • Tests: coding/extension/host/runtimecell/hash_acl_windows_test.go::TestHashGoModuleFailsClosedOnALiveACLDeniedDirectory, coding/extension/host/runtimecell/readdir_shared_test.go::TestWalkDirSharedMatchesFilepathWalkDir, coding/extension/host/runtimecell/readdir_shared_test.go::TestWalkDirSharedReportsListingErrorsToFn, coding/extension/host/runtimecell/vanished_dir_test.go::TestHashGoModuleIgnoresConcurrentChurnOfUntaggedDirectories, coding/extension/host/subprocess/hash_acl_windows_test.go::TestHashSourceDirFailsClosedOnALiveACLDeniedDirectory, coding/extension/host/subprocess/hash_build_output_test.go::TestHashSourceDirIgnoresConcurrentChurnInReplacedModule
  • Release note: On Windows, a Go extension's source hash no longer fails, or makes a build beside it fail, with The process cannot access the file because it is being used by another process. The hash listed every directory of the extension and of the modules its go.mod replaces through a handle that did not share delete, so a build that deleted or renamed a directory in those trees while the hash had it open (Cargo and rustc remove temp directories under a replaced repository root) failed with a sharing violation, and the hash failed in turn when it opened a directory that build was deleting. The hash now lists directories with every share mode. A directory that is there and denies the listing (an ACL deny) still fails the hash.

BF-24872ab

Extensions receive each model (ctx.model, model_select, ctx.modelRegistry reads, scoped models…

  • Source: PiG's own defect
  • Symptom: Extensions receive each model (ctx.model, model_select, ctx.modelRegistry reads, scoped models, virtual-model routes and provider-object calls) as Pi's model object, with its members in Pi's order, instead of sorted by member name with null for members the model does not set.
  • Root cause: ctx.model, model_select, ctx.modelRegistry (find/getAll/typed models, getAvailableOfType), scoped models, virtual-model routes and provider-object calls carried extension.ModelInfo: a map[string]any that json.Marshal sorted, with null for absent members and PiG's aliases (modelId, displayName, maxOutputTokens, *CostPer1M). Pi hands extensions the runtime's model object itself (runner.ts:609): its catalog record's member order for a built-in or stored model, provider-composer.ts:230-248's for a composed one.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 24872ab41e (2026-10-13), 48a28fe06a (2026-10-13)
  • Tests: coding/cli/model_projection_test.go::TestModelObjectPreservesPiModelShape, coding/extension/host/subprocess/native_provider_models_test.go::TestNativeProviderModelsKeepTheirMemberOrder, coding/extension/model_object_oracle_test.go::TestModelObjectsMatchPisModelObjects, coding/extension/model_object_test.go::TestMarshalModelObjects, coding/extension/model_object_test.go::TestModelObjectDefaults, coding/extension/model_object_test.go::TestModelObjectKeepsARecordsEmptyTiers, coding/extension/model_object_test.go::TestModelObjectOfAComposedModelHasPisMembersInPisOrder, coding/extension/model_object_test.go::TestModelObjectOfImageAndClassifierModels
  • Release note: Extensions receive each model (ctx.model, model_select, ctx.modelRegistry reads, scoped models, virtual-model routes and provider-object calls) as Pi's model object, with its members in Pi's order, instead of sorted by member name with null for members the model does not set.

BF-24a821d

The openai stream decoder follows openai 7.19.0, the version packages/ai declares: the stream ends at [DONE]…

  • Source: PiG's own defect
  • Symptom: The openai stream decoder follows openai 7.19.0, the version packages/ai declares: the stream ends at [DONE] without waiting for the connection to close, the last event is delivered when the server omits its blank line, [DONE] must be the whole data, and an event named error throws an APIError.
  • Root cause: Differential: 6000 seeded response bodies (every line ending, comments, fields with and without the optional space, repeated data, [DONE] and [DONE] tails, JSON errors, malformed JSON, BOM, invalid UTF-8, unterminated last event, split at arbitrary bytes) through the pinned openai 7.19.0 Stream.fromSSEResponse and Pig's decoder: items, API error messages and malformed-JSON errors compared. 631 of 6000 differed.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 24a821d908 (2026-10-10)
  • Tests: ai/openai_stream_decoder_test.go::TestOpenAIStreamDecoderDoneEndsTheStream, ai/openai_stream_decoder_test.go::TestOpenAIStreamDecoderEOFAndDoneAreNotValues, ai/openai_stream_decoder_test.go::TestOpenAIStreamDecoderErrorEvent, ai/openai_stream_decoder_test.go::TestOpenAIStreamDecoderSSEAndLineSemantics, ai/openai_test.go::TestParseSSEUnknownFinishEmitsError, ai/sdk_sse_oracle_test.go::TestOpenAIStreamDecoderMatchesSDK, ai/sse_error_parity_test.go::TestProvidersHandleInStreamErrorsLikePinnedSDKs, ai/sse_parity_test.go::TestProviderSSEFramingAndEOFRules
  • Release note: The openai stream decoder follows openai 7.19.0, the version packages/ai declares: the stream ends at [DONE] without waiting for the connection to close, the last event is delivered when the server omits its blank line, [DONE] must be the whole data, and an event named error throws an APIError.

BF-24a9b69

fix(hljs): a resumed run counts the scan iterations before its checkpoint

  • Source: PiG's own defect
  • Symptom: fix(hljs): a resumed run counts the scan iterations before its checkpoint
  • Root cause: A resumed run started counting at 0, so a whole run could throw (Pi prints the escaped code) where the resumed run did not (PiG printed highlighted code); maxCheckpointIterations only narrowed that window. A checkpoint now records the iterations before it, ResumePoint carries them, tui's resume state keeps them, and the resumed run continues the count, so both runs see the same counter at every match. maxCheckpointIterations goes: points past it are now exact.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 24a9b69c2e (2026-10-11)
  • Tests: tui/highlight_resume_test.go::TestStreamedHighlightEqualsAWholeRun, tui/internal/hljs/resume_test.go::TestHighlightResumableEqualsAFreshRun, tui/internal/hljs/resume_test.go::TestHighlightResumableFollowsLaterText, tui/internal/hljs/resume_test.go::TestResumedRunCountsTheIterationsOfAWholeRun

BF-25aa7fd

A subprocess extension's markdown transform that was busy past the five-second renderer inactivity boundary…

  • Source: PiG's own defect
  • Symptom: A subprocess extension's markdown transform that was busy past the five-second renderer inactivity boundary no longer leaves the message untransformed until something else draws it again: the host asks again once the extension answers the abandoned transform, or after a backoff that starts at five seconds and doubles to one minute for an extension that keeps missing the boundary, and the message transforms again on its next frame.
  • Root cause: A synchronous extension markdown transform the renderer inactivity boundary gave up on returned its input, and the message stayed untransformed until something else drew it again; Pi runs transformers in process, so a Pi user never sees that. The proxy now keeps the abandoned transform by message text and context, asks the extension again with the same backoff as render proxies (renderRetry: 5 s doubling to 60 s per abandonment in a row; the late answer to the first abandonment ends the backoff early), and marks the message changed through the transform context's Retry, so the next frame transforms it and repaints. A retry is the only request for its transform in flight; messages with the…
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 84e70fc701 (2026-10-13), 25aa7fd80b (2026-10-14), 5119ce3155 (2026-10-14), d7855fd99b (2026-10-14)
  • Tests: coding/extension/host/subprocess/markdown_transform_retry_test.go::TestMarkdownTransformAsksTheRestartedExtensionForAnAbandonedTransform, coding/extension/host/subprocess/markdown_transform_retry_test.go::TestMarkdownTransformBacksOffFromATransformerThatAlwaysAnswersLate, coding/extension/host/subprocess/markdown_transform_retry_test.go::TestMarkdownTransformBacksOffFromAnExtensionThatNeverAnswers, coding/extension/host/subprocess/markdown_transform_retry_test.go::TestMarkdownTransformDropsARetryNoMessageTakes, coding/extension/host/subprocess/markdown_transform_retry_test.go::TestMarkdownTransformRendersAgainOnceTheExtensionAnswersAnAbandonedTransform, coding/extension/host/subprocess/markdown_transform_retry_test.go::TestMarkdownTransformRetriesEveryMessageWithTheSameText, coding/extension/host/subprocess/render_retry_backoff_test.go::TestRenderRetryBackoffDoublesUpToItsCap, coding/extension/host/subprocess/render_retry_test.go::TestRenderProxyBacksOffFromARendererThatAlwaysAnswersLate and 6 more in docs/bug-ledger.json
  • Release note: A subprocess extension's markdown transform that was busy past the five-second renderer inactivity boundary no longer leaves the message untransformed until something else draws it again: the host asks again once the extension answers the abandoned transform, or after a backoff that starts at five seconds and doubles to one minute for an extension that keeps missing the boundary, and the message transforms again on its next frame.

BF-25c854d

Session directories (the agent directory, sessions and each project's directory) are created owner-only…

  • Source: A Pi defect that PiG fixed first (divergence D132)
  • Symptom: Session directories (the agent directory, sessions and each project's directory) are created owner-only (0700) instead of world-readable (D132, pi-mono #10770).
  • Root cause: the sessions are private data, and the report calls the default a security problem.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10770
  • Removed when Pi fixes it: Pi creates session directories with mode 0700.
  • Fix commits: 25c854d4cd (2026-10-10)
  • Tests: internal/codingagent/session_dir_mode_test.go::TestSessionDirectoriesAreCreatedOwnerOnly
  • Release note: Session directories (the agent directory, sessions and each project's directory) are created owner-only (0700) instead of world-readable (D132, pi-mono #10770).

BF-2699291

The serializeConversation parity row records that Rust session entries still read an unpaired surrogate as…

  • Source: PiG's own defect
  • Symptom: The serializeConversation parity row records that Rust session entries still read an unpaired surrogate as U+FFFD, and is partial until that is decided.
  • Root cause: A 6,000-message differential against Pi's own serializeConversation (node + dist) now has 0 differences in Go and Python. Before the fix it had 53 in Go and 33 in Python. The conformance row cannot see this, because the harness decodes reports with encoding/json, which turns every lone surrogate into U+FFFD.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 058e7a0e4b (2026-10-11), 2699291af5 (2026-10-11), a178939d6a (2026-10-11), a4e5b071cb (2026-10-11), e8473d13c5 (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, coding/cli/extension_host_wiring_binary_test.go::TestHeadlessExtensionSessionActionsMatchPi, extensions/sdk-py/tests/test_compaction_estimate.py, extensions/sdk-py/tests/test_serialize_conversation_surrogates.py, extensions/sdk/compaction_estimate_test.go::TestEstimateTokensCountsUTF16UnitsAndJSONStringifyLength and 8 more in docs/bug-ledger.json
  • Release note: The serializeConversation parity row records that Rust session entries still read an unpaired surrogate as U+FFFD, and is partial until that is decided.

BF-26b6705

The RPC test harness no longer races on its recorded output error (found by a go test -race ./coding/...…

  • Source: PiG's own defect
  • Symptom: The RPC test harness no longer races on its recorded output error (found by a go test -race ./coding/... run); test-only.
  • Root cause: rpcProcess.outputErr is set by the output-reading goroutine on a decode error or at end of stream while tests read it to report a failure. TestRPCModeSessionReplacementAndExport read it after its last response, without waiting for the stream to close: go test -race ./coding/... reported the write (rpc_ui_integration_test.go:135) against that read (rpc_resume_integration_test.go:89). The error is behind a mutex now (setOutputErr, outputError) for the writer and the three readers.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 26b6705d36 (2026-10-11)
  • Tests: coding/cli/rpc_resume_integration_test.go::TestRPCModeSessionReplacementAndExport
  • Release note: The RPC test harness no longer races on its recorded output error (found by a go test -race ./coding/... run); test-only.

BF-26d302a

A login flow's select prompt opens a selector in the login dialog, as Pi's does, instead of ending the…

  • Source: PiG's own defect
  • Symptom: A login flow's select prompt opens a selector in the login dialog, as Pi's does, instead of ending the login as "Login cancelled".
  • Root cause:
    • the registry composed the provider's auth from the built-in catalog, not from the object, so its OAuth method, loginLabel and own API-key login were never offered. Pi's composeProvider takes the native provider as the base (model-runtime.ts:301-303 nativeExtensionProviders.get(id) ?? builtins.get(id)); registryAuthConfig now does the same, so the methods come from the retained provider object on every register, refresh and publication path; - the host carrier did not map auth.apiKey.login, auth.oauth.login or loginLabel. It now does, and their prompt and notify reverse calls decode into the closed AuthPrompt and AuthEvent unions (auth/types.ts); any other type fails the login with an…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 26d302a2d7 (2026-10-09, #201)
  • Tests: coding/extension/host/subprocess/native_provider_login_test.go::TestDecodeNativeLoginCallback, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeAuthInteractionAnswersTheLoginDialog, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeProviderCarrierCarriesDeclaredLogins, coding/extension/host/subprocess/native_provider_oauth_test.go::TestNativeProviderOAuthLoginCallbacks, internal/codingagent/native_provider_login_test.go::TestNativeProviderAPIKeyLoginRunsDeclaredLogin, internal/codingagent/native_provider_login_test.go::TestNativeProviderAPIKeyLoginWithoutDeclaredLoginUsesGenericPrompt, internal/codingagent/native_provider_login_test.go::TestNativeProviderLoginOffersDeclaredMethods, internal/codingagent/native_provider_login_test.go::TestNativeProviderLoginRejectsUnknownInteractionTypes and 1 more in docs/bug-ledger.json
  • Release note: A login flow's select prompt opens a selector in the login dialog, as Pi's does, instead of ending the login as "Login cancelled". Cancelling the selector cancels the login and returns to the menu it was started from. This applies to provider objects and to config.oauth providers.

BF-26d302a-2

A provider object's own auth.apiKey.login runs in the login dialog, as Pi's showApiKeyLoginDialog runs…

  • Source: PiG's own defect
  • Symptom: A provider object's own auth.apiKey.login runs in the login dialog, as Pi's showApiKeyLoginDialog runs it, so a provider that asks for its base URL before the key stores both.
  • Root cause:
    • the registry composed the provider's auth from the built-in catalog, not from the object, so its OAuth method, loginLabel and own API-key login were never offered. Pi's composeProvider takes the native provider as the base (model-runtime.ts:301-303 nativeExtensionProviders.get(id) ?? builtins.get(id)); registryAuthConfig now does the same, so the methods come from the retained provider object on every register, refresh and publication path; - the host carrier did not map auth.apiKey.login, auth.oauth.login or loginLabel. It now does, and their prompt and notify reverse calls decode into the closed AuthPrompt and AuthEvent unions (auth/types.ts); any other type fails the login with an…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 26d302a2d7 (2026-10-09, #201)
  • Tests: coding/extension/host/subprocess/native_provider_login_test.go::TestDecodeNativeLoginCallback, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeAuthInteractionAnswersTheLoginDialog, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeProviderCarrierCarriesDeclaredLogins, coding/extension/host/subprocess/native_provider_oauth_test.go::TestNativeProviderOAuthLoginCallbacks, internal/codingagent/native_provider_login_test.go::TestNativeProviderAPIKeyLoginRunsDeclaredLogin, internal/codingagent/native_provider_login_test.go::TestNativeProviderAPIKeyLoginWithoutDeclaredLoginUsesGenericPrompt, internal/codingagent/native_provider_login_test.go::TestNativeProviderLoginOffersDeclaredMethods, internal/codingagent/native_provider_login_test.go::TestNativeProviderLoginRejectsUnknownInteractionTypes and 1 more in docs/bug-ledger.json
  • Release note: A provider object's own auth.apiKey.login runs in the login dialog, as Pi's showApiKeyLoginDialog runs it, so a provider that asks for its base URL before the key stores both. Each answered prompt stays visible in the dialog. A provider object without its own API-key login keeps the generic API-key prompt.

BF-26d302a-3

A provider object's login that sends a prompt or event type Pi does not define fails with an error that names…

  • Source: PiG's own defect
  • Symptom: A provider object's login that sends a prompt or event type Pi does not define fails with an error that names the type, instead of being answered as a text prompt or shown as progress.
  • Root cause:
    • the registry composed the provider's auth from the built-in catalog, not from the object, so its OAuth method, loginLabel and own API-key login were never offered. Pi's composeProvider takes the native provider as the base (model-runtime.ts:301-303 nativeExtensionProviders.get(id) ?? builtins.get(id)); registryAuthConfig now does the same, so the methods come from the retained provider object on every register, refresh and publication path; - the host carrier did not map auth.apiKey.login, auth.oauth.login or loginLabel. It now does, and their prompt and notify reverse calls decode into the closed AuthPrompt and AuthEvent unions (auth/types.ts); any other type fails the login with an…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 26d302a2d7 (2026-10-09, #201)
  • Tests: coding/extension/host/subprocess/native_provider_login_test.go::TestDecodeNativeLoginCallback, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeAuthInteractionAnswersTheLoginDialog, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeProviderCarrierCarriesDeclaredLogins, coding/extension/host/subprocess/native_provider_oauth_test.go::TestNativeProviderOAuthLoginCallbacks, internal/codingagent/native_provider_login_test.go::TestNativeProviderAPIKeyLoginRunsDeclaredLogin, internal/codingagent/native_provider_login_test.go::TestNativeProviderAPIKeyLoginWithoutDeclaredLoginUsesGenericPrompt, internal/codingagent/native_provider_login_test.go::TestNativeProviderLoginOffersDeclaredMethods, internal/codingagent/native_provider_login_test.go::TestNativeProviderLoginRejectsUnknownInteractionTypes and 1 more in docs/bug-ledger.json
  • Release note: A provider object's login that sends a prompt or event type Pi does not define fails with an error that names the type, instead of being answered as a text prompt or shown as progress.

BF-27a1650

The keybindings manager's key text and key matching now cover every keybinding the merged table defines…

  • Source: PiG's own defect
  • Symptom: A tui.* id had no key text and matched nothing through the coding-agent manager, and an unknown id echoed its user keys.
  • Root cause: PiG's KeyText and Matches read km.resolved (app.* only): keyText("tui.select.confirm") was "" and matches("\r", "tui.select.confirm") false; getKeys echoed the user keys of an unknown id (TUI bridge). They now share keysOf over the merged table, which returns nothing for an undefined id.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e1abc6c615 (2026-10-11), 27a1650048 (2026-10-12)
  • Tests: internal/codingagent/keybindings_user_oracle_test.go::TestUserKeybindingsMatchPi
  • Release note: The keybindings manager's key text and key matching now cover every keybinding the merged table defines, including the tui.* ones, and no keys for an id no definition names, as Pi's KeybindingsManager.getKeys/keyText/matches do. Before, a tui.* id had no key text and matched nothing through the coding-agent manager, and an unknown id echoed its user keys.

BF-27ddc30

A tool card shows the arguments of a tool call while they stream, as Pi does: a streamed write card shows its…

  • Source: PiG's own defect
  • Symptom: A tool card shows the arguments of a tool call while they stream, as Pi does: a streamed write card shows its first lines and the "(N more lines, M total" hint as the content arrives, instead of a bare write ... header until the last delta.
  • Root cause: AppendOutput converted each chunk to bytes for StripANSI and back to a string, two copies per chunk of ! bash output that the executor had already stripped. StripANSIString returns clean text as it is, and the CR normalization copies only when a chunk has a carriage return.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 27ddc30867 (2026-10-13), 5d2eb77efe (2026-10-13), a98ed4d6ae (2026-10-13)
  • Tests: internal/codingagent/interactive_events_stream_args_test.go::TestInteractiveStreamedToolArgsDeltaCostIsFlat, internal/codingagent/interactive_events_write_stream_oracle_test.go::TestInteractiveStreamedWriteCardMatchesPiOracle, internal/codingagent/tool_builtin_renderers_args_cost_test.go::TestEditRenderersDoNotReencodeUnchangedArgs, tui/bash_execution_append_cost_test.go::TestBashExecutionAppendOutputDoesNotCopyCleanChunks, tui/tool_execution_streamed_args_test.go::TestStreamedArgsEncodeOncePerDraw
  • Release note: A tool card shows the arguments of a tool call while they stream, as Pi does: a streamed write card shows its first lines and the "(N more lines, M total" hint as the content arrives, instead of a bare write ... header until the last delta. The card reads the arguments the provider's streaming JSON parser has already decoded and encodes them only when it draws, so a delta costs the same however long the arguments are (10 allocations and 1.7 KB per delta at 1 KB and at 504 KB received).

BF-27f3324

The npm release packer reads npm pack --json output from npm 12, which keys the result by package name, as…

  • Source: PiG's own defect
  • Symptom: The npm release packer reads npm pack --json output from npm 12, which keys the result by package name, as well as npm 11's array (Pi 6fb2e781).
  • Root cause: #10415: Pi main.ts:80-97 readPipedStdin waits for EOF; PiG ported it, so an open idle pipe hangs startup. --no-stdin skips the read; default unchanged (D-PIBUG-10415). pack_npm.py: npm 12 prints pack --json keyed by package name; port Pi 's shape handling.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 27f33240ee (2026-10-09)
  • Tests: coding/cli/no_stdin_test.go::TestNoStdinStartsWithAnOpenInputPipe, coding/cli/no_stdin_test.go::TestParseArgsNoStdin
  • Release note: The npm release packer reads npm pack --json output from npm 12, which keys the result by package name, as well as npm 11's array (Pi 6fb2e781).

BF-28b8425

Blank-key detection for compat API keys, the Vertex API key and the Vertex custom base URL trim whitespace…

  • Source: PiG's own defect
  • Symptom: Blank-key detection for compat API keys, the Vertex API key and the Vertex custom base URL trim whitespace the way JavaScript does: a value that is only a byte-order mark counts as blank, and a leading or trailing U+0085 is kept, as in Pi.
  • Root cause: hasExplicitApiKey (compat.ts:220), resolveApiKey/isPlaceholderApiKey (google-vertex.ts:429-437) and resolveCustomBaseUrl (google-vertex.ts:407) are cut from Pi's sources (testdata/trim_sites.mjs) and run on 4500 values padded with every character that String.prototype.trim and strings.TrimSpace treat differently (U+FEFF, U+0085, U+180E, U+200B, U+2028/9, U+00A0, U+3000...). Go's TrimSpace strips U+0085 and keeps U+FEFF, JS the opposite, so a BOM-only key counted as present and a BOM-wrapped URL kept its BOM (red before). The three sites use trimJSWhitespace.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 093cf5c781 (2026-10-09), 0b4e1e3376 (2026-10-09), 28b8425efa (2026-10-09), 90882933f6 (2026-10-09)
  • Tests: ai/azure_config_oracle_test.go::TestAzureBaseURLNormalizationMatchesPi, ai/codex_vertex_urls_oracle_test.go::TestCodexWebSocketAndVertexVersionURLsMatchPi, ai/error_body_oracle_test.go::TestErrorBodyHelpersMatchPiOnSeededProbes, ai/trim_sites_oracle_test.go::TestJSTrimSitesMatchPi, internal/evals/plan_oracle_test.go::TestPlanMatchesPiOnSeededProbes
  • Release note: Blank-key detection for compat API keys, the Vertex API key and the Vertex custom base URL trim whitespace the way JavaScript does: a value that is only a byte-order mark counts as blank, and a leading or trailing U+0085 is kept, as in Pi.

BF-28f8b3b

fix(tui): editor wrap of a one-grapheme segment wider than the width no longer overflows the stack (D158)

  • Source: PiG's own defect
  • Symptom: fix(tui): editor wrap of a one-grapheme segment wider than the width no longer overflows the stack (D158)
  • Root cause: Pi's wordWrapLine re-wraps a segment wider than maxWidth with the same input when it is a single grapheme (a tab at width <= 2, a double-width character at width 1) and throws RangeError; the Go stack overflow is fatal and kills the process. The grapheme now stays one chunk. Found by the new FuzzEditorInputKeepsRenderValid (seed kept); red: TestEditorWrapsOverWideGraphemeWithoutRecursion dies with 'fatal error: stack overflow' without the fix.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 28f8b3bf2e (2026-10-10)
  • Tests: ai/sse_fuzz_test.go::FuzzSSEDecoderIsIndependentOfChunking, tui/editor_fuzz_test.go::FuzzEditorInputKeepsRenderValid, tui/editor_wide_grapheme_test.go::TestEditorWrapsOverWideGraphemeWithoutRecursion, tui/markdown_fuzz_test.go::FuzzMarkdownRenderStaysWithinWidth

BF-2a055a7

An extension dialog (select, confirm, input, editor) opened while a focused ctx.ui.custom component…

  • Source: PiG's own defect
  • Symptom: A subprocess extension's dialog waited until the custom component closed.
  • Root cause: Ruling 2. With a focused custom overlay, tui.ts:598-665 records the dialog as blocking the overlay's focus restore and, once the hidden dialog is unmounted, resumes the overlay, so the custom overlay has focus and the next key again. An editor-slot custom component is cleared out of the slot by the dialog, so focus goes to the editor. The custom component receives no input while the dialog is up. Two custom components never wait for each other in Pi (showExtensionCustom has no cross-call mutex); that is unchanged here (subprocess custom components still serialize, the known gap in docs/findings/fix-overlay-shim.md).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 1848633092 (2026-10-13), 2a055a7a3a (2026-10-13)
  • Tests: coding/extension/host/subprocess/host_calls_test.go::TestReadLoopWidgetPushOrdersAgainstQueuedSlotCalls, coding/extension/host/subprocess/ui_bridge_test.go::TestInteractiveDialogOpensOverFocusedOverlay, coding/extension/host/subprocess/ui_prompt_test.go::TestUIBridgePromptScopeDialogOverCustomAndNoUI, coding/extension/host/subprocess/view_terminal_parity_test.go::TestViewsLeaveTheTerminalBytesUnchanged, coding/extension/host/subprocess/widget_key_namespace_test.go::TestTwoExtensionsSettingOneWidgetKeyShareIt, internal/codingagent/ext_ui_dialog_focus_test.go::TestExtensionDialogTakesFocusFromFocusedCustomOverlay, internal/codingagent/ext_ui_dialog_focus_test.go::TestExtensionDialogTakesTerminalFromFocusedRemoteOverlay, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult and 7 more in docs/bug-ledger.json
  • Release note: An extension dialog (select, confirm, input, editor) opened while a focused ctx.ui.custom component holds the terminal now shows at once and takes focus, as in Pi. Closing the dialog gives focus back to a custom overlay, which receives the next key. A custom component in the editor slot that the dialog replaced stays off screen, and focus goes to the editor. Before, a subprocess extension's dialog waited until the custom component closed. A dialog from an in-process extension opened at once, but the custom component kept focus, and a subprocess custom component kept reading the keys meant for the dialog.

BF-2a0fb96

/cost lists its tools in a stable order: formatCostSummary ranged over snap.ToolTotals, so the Tools:…

  • Source: PiG's own defect
  • Symptom: /cost lists its tools in a stable order: formatCostSummary ranged over snap.ToolTotals, so the Tools: lines came out in a different order on every call (Go map iteration).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2a0fb968de (2026-10-11)
  • Tests: internal/codingagent/slash_cost_order_test.go::TestFormatCostSummaryListsToolsInStableNameOrder
  • Release note: /cost lists its tools in a stable order: formatCostSummary ranged over snap.ToolTotals, so the Tools: lines came out in a different order on every call (Go map iteration). They are now sorted by name. Regression TestFormatCostSummaryListsToolsInStableNameOrder (red before: fails on the first run with 24 tools). /cost has no Pi counterpart, so the name order is PiG's choice. Found by scanning production code for map ranges that feed output; the same pattern (a map range whose first failing entry names the error) remains in coding/packagecontent (validateMCPDocument, validateComposeClosure) and coding/hookconfig.Parse, which belong to other lanes.

BF-2a919a4

An indented continuation line of a list item loses up to four spaces, a lazy line after a quoted list item…

  • Source: PiG's own defect
  • Symptom: An indented continuation line of a list item loses up to four spaces, a lazy line after a quoted list item that ends in spaces is a paragraph of its own, the last list item's text is trimmed, and a text of only link definitions or an empty quote followed by blank lines renders its empty row.
  • Root cause: TestParseMatchesTheLibraryOnMutatedCorpus and TestLexerMatchesTheLibraryOnMutatedCorpus compare eemeli/yaml 2.9.0 with Parse/lex on 6000 mutated corpus texts (40 differed before, 15-17 per 20000 after the first fixes). Fixed: an unterminated quote (buffer.substring(0, -1) is empty: the error is at the end of input, not after the first line); a line starting with a non-ASCII character reduced the continuation indent because the lexer looked at the second byte, not the second UTF-16 unit (charAt(1)); Invalid escape sequence raw text and the 1024-unit implicit key limit counted bytes; anchors reject JavaScript whitespace (RE2 \s is narrower); -0 is 0 as an omap/set key; Buffer.from(.., base64)…
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2a919a4c3c (2026-10-10), 4e5b684ecd (2026-10-10), ac0dc3bee4 (2026-10-10), b5b7b43f82 (2026-10-10), cdf0be76e4 (2026-10-10)
  • Tests: internal/yaml12/parse_mutation_test.go::TestLexerMatchesTheLibraryOnMutatedCorpus, internal/yaml12/parse_mutation_test.go::TestParseMatchesTheLibraryOnMutatedCorpus, internal/yaml12/parse_mutation_test.go::TestParseMatchesTheLibraryOnUTF16Offsets, internal/yaml12/parse_test.go::TestParseMatchesTheLibrary, tui/markdown_render_oracle_test.go::TestMarkdownRenderMatchesPi
  • Release note: An indented continuation line of a list item loses up to four spaces, a lazy line after a quoted list item that ends in spaces is a paragraph of its own, the last list item's text is trimmed, and a text of only link definitions or an empty quote followed by blank lines renders its empty row.

BF-2ab79ce

An extension provider whose streamSimple hands its request to pi-ai's compat streamSimple (or stream)…

  • Source: PiG's own defect
  • Symptom: An extension provider whose streamSimple hands its request to pi-ai's compat streamSimple (or stream) for a stock API, as pi-commandcode-provider does, no longer re-enters its own streamSimple without bound.
  • Root cause: pi-commandcode-provider registers a provider whose streamSimple forwards to pi-ai's streamSimple for the model's real wire. Pi dispatches that call on model.api (compat.ts:252-293). PiG looked the provider up by name, found the extension's own streamSimple and called it again, without end, until the packed Node process ran out of memory, missed its heartbeat and was killed, taking the healthy members that shared it down too.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2ab79ce358 (2026-10-06, #166)
  • Tests: coding/custom_provider_stream_upstream_test.go::TestRegisteredProviderCallbackServesOnlyItsAPI, coding/delegating_provider_subprocess_test.go::TestRegisteredProviderDelegatingToCompatStreamSimpleReachesTheAPI, coding/model_api_request_test.go::TestIndependentAPIRequestSkipsRegisteredProviderCallback
  • Release note: An extension provider whose streamSimple hands its request to pi-ai's compat streamSimple (or stream) for a stock API, as pi-commandcode-provider does, no longer re-enters its own streamSimple without bound. The request now reaches the stock API's endpoint, as under Pi, instead of growing the Go and Node processes until the extension stopped answering heartbeats and the packed cell was killed (#164, reported by @xinaps-dev).

BF-2ab79ce-2

A provider registered with its own streamSimple runs that callback only for its models whose api is the…

  • Source: PiG's own defect
  • Symptom: A provider registered with its own streamSimple runs that callback only for its models whose api is the provider's registered api, as under Pi.
  • Root cause: pi-commandcode-provider registers a provider whose streamSimple forwards to pi-ai's streamSimple for the model's real wire. Pi dispatches that call on model.api (compat.ts:252-293). PiG looked the provider up by name, found the extension's own streamSimple and called it again, without end, until the packed Node process ran out of memory, missed its heartbeat and was killed, taking the healthy members that shared it down too.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2ab79ce358 (2026-10-06, #166)
  • Tests: coding/custom_provider_stream_upstream_test.go::TestRegisteredProviderCallbackServesOnlyItsAPI, coding/delegating_provider_subprocess_test.go::TestRegisteredProviderDelegatingToCompatStreamSimpleReachesTheAPI, coding/model_api_request_test.go::TestIndependentAPIRequestSkipsRegisteredProviderCallback
  • Release note: A provider registered with its own streamSimple runs that callback only for its models whose api is the provider's registered api, as under Pi. A model of that provider that names another API, such as openai-completions, now reaches that API's implementation instead of the callback. This applies to extension registrations and to ModelRuntime.RegisterProvider.

BF-2ad7249

fix(ledger): close the getJsonSchemaToolParameters and google convertTools rows

  • Source: PiG's own defect
  • Symptom: fix(ledger): close the getJsonSchemaToolParameters and google convertTools rows
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 2ad72495fe (2026-10-10)
  • Tests: ai/google_convert_tools_upstream_test.go::TestGoogleSharedConvertToolsUpstream

BF-2be0883

fix(extensions): parallel Node runtime linking reports the walk-order first failure under every schedule

  • Source: PiG's own defect
  • Symptom: fix(extensions): parallel Node runtime linking reports the walk-order first failure under every schedule
  • Root cause: When i would also have failed, the reported error was the later one, not the walk-order first one the commit claims. The new TestLinkInWalkOrderRunsEveryIndexBelowTheFirstFailure fails under that rule in 2 of 3 -race runs and passes 5/5 -race runs with this fix (TestNodeRuntimeLinkReportsTheWalkOrderFirstFailure stays green under both). The workers now record the lowest failing index (a CAS minimum) and skip only indices above it, so every index below the lowest failure runs and its error is the first in errs. The loop moves into linkInWalkOrder so the schedule is testable without a filesystem; TestLinkInWalkOrderStopsOnACancelledContext pins the context check.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 2be08834a3 (2026-10-11)
  • Tests: coding/extension/host/subprocess/node_runtime_cache_test.go::TestLinkInWalkOrderRunsEveryIndexBelowTheFirstFailure, coding/extension/host/subprocess/node_runtime_cache_test.go::TestLinkInWalkOrderStopsOnACancelledContext, coding/extension/host/subprocess/node_runtime_cache_test.go::TestNodeRuntimeLinkReportsTheWalkOrderFirstFailure

BF-2c1959c

A Mistral stream is no longer aborted mid-generation by the request timeout: the timeout now covers only the…

  • Source: PiG's own defect
  • Symptom: A Mistral stream is no longer aborted mid-generation by the request timeout: the timeout now covers only the wait for response headers (D127, pi-mono #10609, ported from Pi commit 6b07b4e5).
  • Root cause: Port of pi-mono: a long Mistral stream was aborted when timeoutMs elapsed. The timeout now ends when headers arrive.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2c1959cb36 (2026-10-09)
  • Tests: ai/mistral_header_timeout_test.go::TestMistralTimeoutCoversOnlyResponseHeaders, ai/mistral_regression_test.go::TestMistralRequestLifetimeClosesBodyOnce
  • Release note: A Mistral stream is no longer aborted mid-generation by the request timeout: the timeout now covers only the wait for response headers (D127, pi-mono #10609, ported from Pi commit 6b07b4e5).

BF-2c5db8b

A Piglet Binary with a fused Go extension starts within a few milliseconds of Stock PiG

  • Source: PiG's own defect
  • Symptom: A Piglet Binary with a fused Go extension starts within a few milliseconds of Stock PiG.
  • Root cause: A Piglet Binary with one fused Go extension took 72.1 ms for -p with the faux provider against 31.7 ms for Stock PiG. The extension host encoded the whole model catalog (more than 1 MB of JSON) three times while it wired model operations and sent it to every extension, though the Go, Rust and Python SDKs answer ctx.modelRegistry with a blocking getModelRegistryState call and discard the snapshot.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2c5db8b20a (2026-10-06)
  • Tests: None recorded.
  • Release note: A Piglet Binary with a fused Go extension starts within a few milliseconds of Stock PiG. On an Apple M4 Pro (median of 20, faux provider), -p took 72.1 ms against 31.7 ms for Stock PiG and now takes 33.0 ms, and the first typed key showed in the editor after 71.0 ms against 33.6 ms and now after 33.4 ms. The extension host encoded the whole model catalog (more than 1 MB of JSON) three times at startup for every extension. Now it builds and sends that snapshot only to Node extensions, which answer ctx.modelRegistry reads from it. Go, Rust and Python extensions read the registry from the host when they ask, as before, so Stock PiG with such extensions starts faster too. See D19 in docs/additive-features.md.

BF-2c88c5f

Ill-formed UTF-8 in a string that PiG re-encodes as JSON (an extension's raw JSON, tool schemas, code-mode…

  • Source: PiG's own defect
  • Symptom: Ill-formed UTF-8 in a string that PiG re-encodes as JSON (an extension's raw JSON, tool schemas, code-mode keys, Bedrock and Google request members) is written as JSON.stringify writes the decoded text: one literal U+FFFD for each ill-formed sequence, instead of one \ufffd escape per byte.
  • Root cause: Design step 1 ("internal/jsnumber.String, jsstring and jsonstringify's string writer become callers"): - internal/jsstring.AppendString is jsparity.AppendString. It was an ASCII fast path plus MarshalJSON through the SDK encoder. - internal/jsnumber.String writes finite numbers with jsparity.AppendNumber instead of a safe-integer branch plus json.Marshal. - internal/jsonstringify's canonicalize writes numbers with jsparity.AppendNumber. stringToken is JSON.stringify(JSON.parse(token)): jv.Parse decodes to WTF-8 and jsparity.AppendString writes it, replacing a hand-written escape rewriter.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2c88c5fd2e (2026-10-10), 80c797da93 (2026-10-10), afa1dd5185 (2026-10-10), f02269c6e1 (2026-10-10), fe625bb5dd (2026-10-10)
  • Tests: ai/anthropic_replaced_payload_order_oracle_test.go::TestAnthropicReplacedPayloadKeepsPiMemberOrder, ai/bedrock_request_body_oracle_test.go::TestBedrockRequestBodyMatchesPi, ai/bedrock_wire_metadata_order_test.go::TestBedrockStringMapEnumeratesLikeAJavaScriptObject, ai/google_request_body_oracle_test.go::TestGoogleRequestBodyMatchesPi, ai/mistral_request_body_oracle_test.go::TestMistralRequestBodyMatchesPi, ai/mistral_request_body_oracle_test.go::TestMistralTypedWirePayloadMatchesTheGeneralLowering, ai/openai_codex_websocket_frame_oracle_test.go::TestCodexWebSocketFramesMatchPi, ai/provider_ecmascript_whitespace_test.go::TestMistralAssistantBlankUsesECMAScriptTrim and 17 more in docs/bug-ledger.json
  • Release note: Ill-formed UTF-8 in a string that PiG re-encodes as JSON (an extension's raw JSON, tool schemas, code-mode keys, Bedrock and Google request members) is written as JSON.stringify writes the decoded text: one literal U+FFFD for each ill-formed sequence, instead of one \ufffd escape per byte.

BF-2d05dc8

A models.json cost that sets tiers: [] (a custom model's cost, or a modelOverrides cost) keeps…

  • Source: PiG's own defect
  • Symptom: A models.json cost that sets tiers: [] (a custom model's cost, or a modelOverrides cost) keeps cost.tiers: [] in an extension's ctx.model and model lists and in RPC get_state and get_available_models, as Pi keeps the authored cost, instead of dropping the member.
  • Root cause: Pi's ModelCost.tiers is optional: provider-composer.ts:233 keeps a models.json model's authored cost and :193 an override's override.cost.tiers ?? model.cost.tiers, so ctx.model, the model registry and RPC get_state/get_available_models show an authored [] and no member when none was written. ModelCost.Tiers dropped [] (omitempty), and the copies on the composer path (append to nil) turned [] into nil.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2d05dc8572 (2026-10-13), 3789a93b5c (2026-10-13)
  • Tests: ai/model_cost_tiers_presence_test.go::TestBuiltinFallbackCostsWithoutTiersHaveNone, ai/model_cost_tiers_presence_test.go::TestComposedModelRecordKeepsTiersPresence, ai/model_cost_tiers_presence_test.go::TestModelCostKeepsTiersPresence, cmd/gen-models/ai_gateway_models_test.go::TestAiGatewayListingWindowsAreWholeTokenCounts, cmd/gen-models/main_test.go::TestAllowedFallbackModelsLiteralKeepsTiersPresence, coding/cli/rpc_model_cost_tiers_test.go::TestRPCModelValueKeepsCostTiersPresence, coding/model_object_cost_tiers_test.go::TestModelObjectKeepsAnAuthoredEmptyCostTiers
  • Release note: A models.json cost that sets tiers: [] (a custom model's cost, or a modelOverrides cost) keeps cost.tiers: [] in an extension's ctx.model and model lists and in RPC get_state and get_available_models, as Pi keeps the authored cost, instead of dropping the member. A cost without tiers still has none.

BF-2d71556

fix(codingagent): queued-message lines use the dim foreground; hint separator closes only the foreground

  • Source: PiG's own defect
  • Symptom: fix(codingagent): queued-message lines use the dim foreground; hint separator closes only the foreground
  • Root cause: Pi interactive-mode.ts:4729-4737 renders queued steering/follow-up lines and the dequeue hint as theme.fg("dim", ...) and :1056 joins the compact startup hints with theme.fg("muted", " · "). Pig used the muted colour (not dim) and a full reset (ESC[0m) for the queued lines, and a full reset after the separator. Tests TestPendingDisplayLinesUseTheDimForeground and TestStartupKeybindHintsSeparatorIsTheMutedForeground red before the fix.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 2d71556f55 (2026-10-10)
  • Tests: internal/codingagent/pending_display_test.go::TestPendingDisplayLinesUseTheDimForeground, internal/codingagent/startup_hints_test.go::TestStartupKeybindHintsSeparatorIsTheMutedForeground

BF-2d99958

A terminal's reply to the startup color query now applies the colors and ends the startup wait before the…

  • Source: PiG's own defect
  • Symptom: A terminal's reply to the startup color query now applies the colors and ends the startup wait before the next input is read, as in Pi.
  • Root cause: Pi settles the color query inside the input handler, and its .then(apply) and the await continuation run as microtasks before the next input chunk. The owner loop now receives the query's completion itself: the input that completes the query applies its colors before it returns, a task posted at the timeout applies a timed-out query, and the startup color wait returns before it reads more input once the latest query applied.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2d99958e18 (2026-10-13), f12896bcc6 (2026-10-13)
  • Tests: internal/codingagent/interactive_init_state_pty_test.go::TestInitBuildsTheInteractiveStateBeforeTheSessionBindChild, internal/codingagent/startup_color_wait_render_test.go::TestRenderRequestedWhileStartupWaitsForTheTerminalColorsPaints, internal/codingagent/startup_query_order_pty_test.go::TestStartupWritesTheTerminalColorQueryBeforeTheFirstFrame, internal/codingagent/startup_query_order_pty_test.go::TestStartupWritesTheTerminalColorQueryBeforeTheFirstFrameChild, internal/codingagent/startup_submit_test.go::TestColorReplyCompletesTheStartupWaitBeforeTheNextInput, internal/codingagent/startup_submit_test.go::TestPromptAfterTheColorReplyReachesTheSubmitHandler
  • Release note: A terminal's reply to the startup color query now applies the colors and ends the startup wait before the next input is read, as in Pi. A prompt typed right after the terminal answered, for example by a client that types as soon as the editor shows, reaches the ordinary submit handler instead of reporting "Startup is still in progress".

BF-2ea8ad2

The openai-completions, openai-responses and openrouter-images requests build their URL as the openai SDK…

  • Source: PiG's own defect
  • Symptom: The openai-completions, openai-responses and openrouter-images requests build their URL as the openai SDK does (base query rewritten through qs, one slash dropped, WHATWG normalisation) and a URL with credentials is refused as fetch refuses it ("Connection error."), where Pig sent the credentials as Basic auth.
  • Root cause: Not recorded.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2ea8ad24ae (2026-10-10), a8ff0c7afe (2026-10-10)
  • Tests: ai/sdk_request_url_oracle_test.go::TestSDKProvidersRefuseCredentialsLikeFetch, ai/sdk_request_url_oracle_test.go::TestSDKProvidersRequestURLMatchesSDK
  • Release note: The openai-completions, openai-responses and openrouter-images requests build their URL as the openai SDK does (base query rewritten through qs, one slash dropped, WHATWG normalisation) and a URL with credentials is refused as fetch refuses it ("Connection error."), where Pig sent the credentials as Basic auth.

BF-2f881ba

"Your first extension" now says that pig install --validate-only exits 1 when the extension does not build

  • Source: PiG's own defect
  • Symptom: It said 0.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 2f881baf82 (2026-10-11)
  • Tests: None recorded.
  • Release note: "Your first extension" now says that pig install --validate-only exits 1 when the extension does not build. Before, it said 0.

BF-2fc2307

A scalar that names a collection tag (!!map 1, !!seq true) inside a complex mapping key stays plain in…

  • Source: PiG's own defect
  • Symptom: A scalar that names a collection tag (!!map 1, !!seq true) inside a complex mapping key stays plain in the key's text, as the yaml library prints it; it used to be quoted (!!map "1") as a string scalar is, so the object key differed.
  • Root cause: getTagObject picks the tag by name, and the collection tags have no stringify, so the scalar reaches stringifyString with the caller's context: a text that would read as a number stays plain in a stringified complex key ('? [!!map 1]' keys the object '[ !!map 1 ]', the library; Pig quoted it). Found by the yaml12 mutated-corpus seed sweep (class 3 of the earlier note). The blank-line indentation inside stringified flow-map keys (class 2) remains.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2fc2307be4 (2026-10-11)
  • Tests: internal/yaml12/parse_mutation_test.go::TestParseMatchesTheLibraryOnCollectionTagsOnScalars
  • Release note: A scalar that names a collection tag (!!map 1, !!seq true) inside a complex mapping key stays plain in the key's text, as the yaml library prints it; it used to be quoted (!!map "1") as a string scalar is, so the object key differed.

BF-305bd8b

A durable task that panics with a value whose String method panics now faults with the message `[object…

  • Source: PiG's own defect
  • Symptom: A durable task that panics with a value whose String method panics now faults with the message [object Object], as Pi's errorMessage gives for a thrown value that cannot be formatted, instead of Go's %!v(PANIC=...) text.
  • Root cause: 92 Pi cases (seven files, all new in 1.2.0). 85 ported, 7 skipped. Every Go test cites '<file>.test.ts:<line> it(...)'.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 305bd8be1f (2026-10-13)
  • Tests: durable/harness/harness_listener_errors_test.go::TestAThrownValueThatCannotBeFormatted, durable/harness/tool_nested_test.go::TestAReplaySafeCallerReattachesToTheNestedCallsItMadeBeforeARestart, durable/harness/tool_nested_test.go::TestTheLeftRunningNestedCallsOfACallerInterruptedWhileItCleanedThemUpAreAborted, durable/harness/tool_nested_test.go::TestToolTaskMigratesVersion1InputToAModelIssuedCall
  • Release note: A durable task that panics with a value whose String method panics now faults with the message [object Object], as Pi's errorMessage gives for a thrown value that cannot be formatted, instead of Go's %!v(PANIC=...) text.

BF-30d4a6d

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 30d4a6d805 (2026-10-13)
  • Tests: None recorded.

BF-30e47f4

fix(tui): SetThemeInstance notifies the active theme observers, as Pi's setThemeInstance runs its callback

  • Source: PiG's own defect
  • Symptom: fix(tui): SetThemeInstance notifies the active theme observers, as Pi's setThemeInstance runs its callback
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 30e47f4408 (2026-10-21)
  • Tests: None recorded.

BF-3154dfb

Starting a Go extension no longer fails with `hash <dir> after its build: open …/release/deps/rmeta…: no…

  • Source: PiG's own defect
  • Symptom: Starting a Go extension no longer fails with hash <dir> after its build: open …/release/deps/rmeta…: no such file or directory when a Cargo build runs in a directory its go.mod replaces.
  • Root cause: walkDirSources excluded build output only by directory name (target, dist, hidden, vendor, node_modules), so a target directory named anything else (rust-target) was walked as source, and any error from reading a directory another process had just deleted was fatal. runtimecell's hashGoModule already treats a tree marked with CACHEDIR.TAG as build output and a vanished directory as holding no source; the subprocess builder's walk never got the same rule.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 633152df76 (2026-10-23)
  • Tests: coding/extension/host/subprocess/hash_build_output_test.go::TestHashSourceDirFailsClosedOnUnreadableSourceFile, coding/extension/host/subprocess/hash_build_output_test.go::TestHashSourceDirIgnoresConcurrentChurnInReplacedModule, coding/extension/host/subprocess/hash_build_output_test.go::TestHashSourceDirSkipsCacheDirectoryTreesOfReplacedModules, test/extension-conformance/provider_stream_test.go::TestProviderProducersAcrossSDKs
  • Release note: Starting a Go extension no longer fails with hash <dir> after its build: open …/release/deps/rmeta…: no such file or directory when a Cargo build runs in a directory its go.mod replaces. The source hash that guards a build against an edit made while it ran walked every replaced module in full, including a Cargo target directory not named target, and a temp directory rustc deleted between the listing and the read failed the whole hash. The walk now skips a tree marked with a CACHEDIR.TAG signature, as Cargo marks every target directory it creates, and a directory that vanishes mid-walk, as the packed-cell hash already did. A source file that cannot be read still fails the hash.

BF-31740ec

A waitForIdle() waiter registered during a run stays pending through agent_settled handlers and the…

  • Source: A Pi defect that PiG fixed first (divergence D142)
  • Symptom: A waitForIdle() waiter registered during a run stays pending through agent_settled handlers and the prompts they defer, and an abort while settlement is in progress discards those prompts (D142, Pi #10704, #10705).
  • Root cause: A host that waits for idle must not report completion while work is still queued, and a stopped Session must not start provider requests.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10704
  • Removed when Pi fixes it: Pi keeps registered waiters pending through settlement and discards deferred prompts on abort.
  • Fix commits: 31740ec1fc (2026-10-10), 35e46b114e (2026-10-10)
  • Tests: coding/session_settled_idle_test.go::TestAbortDuringADeferredContinuationDiscardsTheRest, coding/session_settled_idle_test.go::TestAbortFromAgentSettledSubscriberDiscardsQueuedContinuation, coding/session_settled_idle_test.go::TestCustomMessageRunSettledHandlerAwaitsCompaction, coding/session_settled_idle_test.go::TestSettledHandlerCompactionKeepsTheContinuationQueuedBeforeIt, coding/session_settled_idle_test.go::TestWaitForIdleStaysPendingThroughEveryDeferredPrompt, coding/session_settled_idle_test.go::TestWaitForIdleStaysPendingThroughSettledHandlerCompactionAndContinuation
  • Release note: A waitForIdle() waiter registered during a run stays pending through agent_settled handlers and the prompts they defer, and an abort while settlement is in progress discards those prompts (D142, Pi #10704, #10705). An agent_settled handler that awaits a compaction no longer deadlocks on the Session lock.

BF-31a7d77

SessionBeforeTreeResultSummary.Usage is typed as ai.Usage

  • Source: PiG's own defect
  • Symptom: SessionBeforeTreeResultSummary.Usage is typed as ai.Usage.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 31a7d77703 (2026-10-06)
  • Tests: coding/session_tree_test.go::TestNavigateTreeExtensionSummary
  • Release note: SessionBeforeTreeResultSummary.Usage is typed as ai.Usage.

BF-31cde9f

A Node extension that calls pi.appendEntry now sees the new entry in ctx.sessionManager.getEntries() in…

  • Source: PiG's own defect
  • Symptom: A Node extension that calls pi.appendEntry now sees the new entry in ctx.sessionManager.getEntries() in the same handler, as in Pi.
  • Root cause: Pi's pi.appendEntry calls sessionManager.appendCustomEntry on the in-process log and then emits entry_appended (agent-session.ts:3406-3411), so ctx.sessionManager.getEntries() in the same handler holds the entry. The Node runtime sent the append to the host without waiting and read only the replicated log, so the robust-notes /robust-note command counted 0 notes where Pi counts 1.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 382156cdb7 (2026-10-07), 6f9132537e (2026-10-07), 8d990c5d54 (2026-10-07), a79de03e7f (2026-10-07), 31cde9f009 (2026-10-09), 7489735c3f (2026-10-09)
  • Tests: coding/cli/config_command_test.go::TestRunConfigCommandLocalRequiresTrustedProject, coding/cli/rpc_append_entry_visible_test.go::TestRPCNodeAppendEntryIsVisibleToTheSameHandlerAndEmitsEntryAppended, coding/extension/host/inproc/upstream_scoped_models_test.go::TestContextThinkingLevelReadsTheBoundRuntimeLive, coding/extension/host/subprocess/builder_node_entry_test.go::TestResolveNodeEntrypointImportsAnIndexMjsDirectoryAsJiti, internal/codingagent/interactive_test.go::TestHasAssistantUsageAfterCompaction, internal/packagemanager/package_manager_test.go::TestPackageManagerPersistsInstallAndRemoval, internal/packagemanager/package_manager_test.go::TestPackageManagerReportsInstalledPathsPerScope, internal/packagemanager/package_manager_test.go::TestPackageManagerReportsProgressToTheInstalledCallback and 4 more in docs/bug-ledger.json
  • Release note: A Node extension that calls pi.appendEntry now sees the new entry in ctx.sessionManager.getEntries() in the same handler, as in Pi. RPC mode and interactive mode still report the append (entry_appended).

BF-31d09fa

Fixed OpenRouter router models such as openrouter/auto showing a cost of -$1,000,000 per 1M tokens…

  • Source: PiG's own defect
  • Symptom: Fixed OpenRouter router models such as openrouter/auto showing a cost of -$1,000,000 per 1M tokens; OpenRouter's negative placeholder prices are now 0.
  • Root cause: OpenRouter's catalog is the user's own list: each network refresh passes the bundled models plus a newer pi.dev catalog through ai.PersonalizeOpenRouterCatalog and saves the result, which replaces the catalog (also offline); an empty saved list falls back to the bundled models, a failed personalization saves nothing. The registry refreshes through the provider's configured base URL (models.json or an extension's), as Models passes baseUrl in RefreshModelsContext. newCatalogRequest, readRemoteCatalog and isNewerCatalog factor out the shared parts of withRemoteCatalog. Pi's two personalized-catalog tests are ported; registry and provider mutants (nine) killed. docs/site/docs/providers.md…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 31d09fa092 (2026-10-12), 40dd5646a1 (2026-10-12), 5db6926738 (2026-10-12)
  • Tests: ai/models_runtime_refresh_upstream_test.go::TestModelsRuntimeRefreshUpstream, ai/openrouter_api_test.go::TestOpenRouterCostConvertsPricesAndFallsBackOnMissingInvalidAndNegativeRates, ai/openrouter_user_models_test.go::TestPersonalizeOpenRouterCatalogKeepsOnlyTheKeysChatModelsWithTheKeysLimitsAndPrices, ai/openrouter_user_models_test.go::TestPersonalizeOpenRouterCatalogRejectsOtherFailuresAndSkipsTheRequestWithoutAKey, ai/openrouter_user_models_test.go::TestPersonalizeOpenRouterCatalogReturnsTheCatalogUnchangedWhenTheEndpointRejectsTheKeyOrIsMissing, cmd/gen-models/openrouter_catalog_test.go::TestOpenRouterCostSerializesNegativeZeroAsZero, internal/codingagent/remote_catalog_personalized_test.go::TestPersonalizedCatalogProviderKeepsTheSavedListAndSavesNothingWhenPersonalizationFails, internal/codingagent/remote_catalog_personalized_test.go::TestPersonalizedCatalogProviderPersonalizesTheBuiltInModelsPlusANewerPiDevCatalogOnEveryRefresh and 4 more in docs/bug-ledger.json
  • Release note: Fixed OpenRouter router models such as openrouter/auto showing a cost of -$1,000,000 per 1M tokens; OpenRouter's negative placeholder prices are now 0.

BF-31d85ab

fix duplicate jsstring import in tui/text.go

  • Source: PiG's own defect
  • Symptom: fix duplicate jsstring import in tui/text.go
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 31d85abb57 (2026-10-11)
  • Tests: None recorded.

BF-31f4517

Radius gateway and OAuth authorization URLs are parsed and serialized with the WHATWG URL parser, as Node's…

  • Source: PiG's own defect
  • Symptom: Go's net/url accepted or reshaped many of these.
  • Root cause: 411 of the first 5000 differed after a first pass and 3937 of 5000 with net/url (red before): hosts not lowercased or IDN/IPv4/IPv6-normalized, userinfo not percent-encoded, schemeless and bad-port inputs accepted, default ports kept, an empty search dropping the '?' that Pi keeps. resolveGatewayPath and the new radiusAuthorizeURL (extracted from loginWithBrowser) now use nodeurl.ParseURL with two new methods, URL.ResolveAbsolutePath (opaque-path bases throw) and URL.WithSearch (the setter: '' removes the query, one leading '?' is dropped, the query percent-encode set with the special-scheme ' extra); the unused parseGatewayURL is gone.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 093cf5c781 (2026-10-09), 15a5c0b670 (2026-10-09), 2c04c1445a (2026-10-09), 31f4517fbe (2026-10-09), 7c4d874438 (2026-10-09), d1b3ad0151 (2026-10-09), d80c1cd150 (2026-10-09)
  • Tests: ai/authorization_input_oracle_test.go::TestAuthorizationInputParsersMatchPi, ai/error_body_oracle_test.go::TestErrorBodyHelpersMatchPiOnSeededProbes, ai/githubcopilot_oracle_test.go::TestGitHubCopilotHelpersMatchPi, ai/radius_config_oracle_test.go::TestRadiusConfigMatchesPiOnSeededProbes, ai/radius_urls_oracle_test.go::TestRadiusURLsMatchPiOnSeededProbes, ai/trusted_url_oracle_test.go::TestTrustedVerificationURLsMatchPi, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode, internal/nodeurl/url_oracle_test.go::TestParseMatchesNodeURL
  • Release note: Radius gateway and OAuth authorization URLs are parsed and serialized with the WHATWG URL parser, as Node's new URL does. Hosts are lowercased and converted from IDN, IPv4 and IPv6 forms, userinfo is percent-encoded, a URL without a scheme or with a bad port is rejected, and an empty search keeps a ?. Before, Go's net/url accepted or reshaped many of these.

BF-3270e5f

llama-cpp-classify answers match Pi bit for bit: the softmax uses jsmath.Exp, V8's fdlibm Math.exp…

  • Source: PiG's own defect
  • Symptom: llama-cpp-classify answers match Pi bit for bit: the softmax uses jsmath.Exp, V8's fdlibm Math.exp (Go's math.Exp differs in the last bit for some arguments on amd64).
  • Root cause: D119, D125, D127, D133, D139 and D141 are retired in the text. Pages that state the 1.1.0 behaviour next to the change are recorded in version-records.toml. Output-sensitive walkthrough steps were re-run on an int-050 build: version strings, hashes and the closure-path error are updated. The llama-cpp-classify fragment no longer claims identical requests.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 3270e5f205 (2026-10-13)
  • Tests: None recorded.
  • Release note: llama-cpp-classify answers match Pi bit for bit: the softmax uses jsmath.Exp, V8's fdlibm Math.exp (Go's math.Exp differs in the last bit for some arguments on amd64). Its requests are written with their members in Pi's order, and a failed lookup no longer cancels the sibling requests of the same Promise.all, as in Pi.

BF-32ae04c

Durable (Pi 89e5c2c0): a generation waiting on a blocking threshold compaction fails with model_error…

  • Source: PiG's own defect
  • Symptom: Durable (Pi 89e5c2c0): a generation waiting on a blocking threshold compaction fails with model_error (Compaction failed: <error>) when that compaction failed, faulted, was orphaned, or was aborted directly, instead of sending a request over a context it judged too large; a compaction that found nothing to compact or was declined still lets the request go ahead.
  • Root cause: scenarios/storage-failure.mjs runs one conversation (a submit with a tool call, an event stream, a follow-up) on a Storage whose commit number k throws once, and prints what each caller saw: the call that hit it, later calls and waits, the stream's end, the Harness's end. 22 diff rows (gate k=2..20, node k=3,9,15) compare it with Pi 42a (Session.closed/SessionEnd, SessionFailed, harness-storage-failure.test.ts) at every level.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1595741593 (2026-10-10), 761ed6e951 (2026-10-10), b3ba0af150 (2026-10-10), 15c9cc458c (2026-10-11), 32ae04cc76 (2026-10-11), 34f7594aa5 (2026-10-11), 38a3abb15b (2026-10-11), a0c51e0655 (2026-10-11)
  • Tests: durable/contract/test/setup.test.mjs, durable/core/driver/compaction_failure_core_test.go::TestCoreAFailedBlockingCompactionFailsTheRunAndIsRecorded, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-switch.toml and 1 more in docs/bug-ledger.json
  • Release note: Durable (Pi 89e5c2c0): a generation waiting on a blocking threshold compaction fails with model_error (Compaction failed: <error>) when that compaction failed, faulted, was orphaned, or was aborted directly, instead of sending a request over a context it judged too large; a compaction that found nothing to compact or was declined still lets the request go ahead. pi.live.compactionFailure (LiveState.CompactionFailure) keeps the newest failure until a compaction produces a summary, SnapshotEvent.CompactionFailure carries it, and CompactionEndEvent.Error is set for a failed compaction. GenerationCheckpoint.Threshold marks the prepare phase that waits for the compaction.

BF-32b5367

A running !command that prints a lot, the /resume search over many long sessions, and streamed answers…

  • Source: PiG's own defect
  • Symptom: A running !command that prints a lot, the /resume search over many long sessions, and streamed answers without Mermaid diagrams cost less per frame or keystroke, with the same output.
  • Root cause: Each keystroke in /resume's search rebuilt every session's search text (id, name, all message text, cwd) and lowered and converted it to UTF-16 for each fuzzy token, so a keystroke allocated several copies of all text of all sessions. Pi does the same (session-selector-search.ts getSessionSearchText per match, fuzzyMatch lowers the text). The selector now keeps, per listed session, the search text with the forms the query modes read: the lowercase UTF-16 units for fuzzy tokens, the whitespace-normalized lowercase text for phrases, and the unit runes for regex. Each form is prepared when a query first needs it.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 32b5367dd9 (2026-10-10), 9f76da76d0 (2026-10-10), ab5be97bbe (2026-10-10), c4fef6c21a (2026-10-10), e892003a81 (2026-10-10)
  • Tests: internal/codingagent/complexity_scaling_mermaid_norace_test.go::TestScalingMermaidTransformOfAStreamedMessage, internal/codingagent/complexity_scaling_norace_test.go::TestScalingSessionSearchKeystroke, internal/codingagent/mermaid_word_test.go::TestMermaidTransformSkipsOnlyMessagesWithoutTheWord, internal/codingagent/session_search_cache_test.go::TestCachedSessionSearchEqualsAFreshSearch, internal/codingagent/session_search_cache_test.go::TestCachedSessionSearchReadsAChangedSession, tui/complexity_scaling_norace_test.go::TestScalingBashOutputFrame, tui/highlight_cache_bound_test.go::TestHighlightMemoKeepsOnlyTheNewestPrefixOfAStreamedBlock, tui/markdown_streaming_reuse_test.go::TestStreamedMarkdownEqualsAFreshRender and 1 more in docs/bug-ledger.json
  • Release note: A running !command that prints a lot, the /resume search over many long sessions, and streamed answers without Mermaid diagrams cost less per frame or keystroke, with the same output.

BF-32ce7ad

The Termux page shows the [Context] line as PiG prints it, ~/.pig/agent/AGENTS.md

  • Source: PiG's own defect
  • Symptom: The Termux page shows the [Context] line as PiG prints it, ~/.pig/agent/AGENTS.md.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: Android and Termux
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 32ce7ad1c7 (2026-10-11)
  • Tests: None recorded.
  • Release note: The Termux page shows the [Context] line as PiG prints it, ~/.pig/agent/AGENTS.md.

BF-32e86a8

Go, Python and Rust SDK: theme.bold, italic, underline, inverse and strikethrough now draw as Pi's…

  • Source: PiG's own defect
  • Symptom: They wrapped the whole text once, so a multi-line or nested string lost the style past the first line or inner close, and an empty string came back as two escape sequences.
  • Root cause: TestThemeQueriesAcrossSDKs reaches every remaining ctx.ui.theme member in all four SDKs and every placement (sourcePath, getColorMode, bold, italic, underline, inverse, strikethrough, bg, getFgAnsi, getBgAnsi, getThinkingBorderColor, getBashModeBorderColor) against Pi's Theme (theme.ts:242-436) over the host's palette, with modifiers on and off, both color modes, an unknown token and an unknown thinking level.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 32e86a8af3 (2026-10-10)
  • Tests: test/extension-conformance/theme_test.go::TestThemeQueriesAcrossSDKs
  • Release note: Go, Python and Rust SDK: theme.bold, italic, underline, inverse and strikethrough now draw as Pi's chalk does: an empty text stays empty, a line break closes and re-opens the style, and an inner close of the same style re-opens it. Before, they wrapped the whole text once, so a multi-line or nested string lost the style past the first line or inner close, and an empty string came back as two escape sequences.

BF-334bb4b

A Gemini or Mistral stream chunk with a member of an unexpected type no longer sorts the members of a tool…

  • Source: PiG's own defect
  • Symptom: A Gemini or Mistral stream chunk with a member of an unexpected type no longer sorts the members of a tool call's object arguments: the call keeps the order the model wrote them in, as Pi's JSON.parse read does.
  • Root cause: The Gemini, Mistral, openai-completions, Responses and Anthropic normalizers re-encoded the parsed record with json.Marshal, which sorts object members. A Gemini functionCall's args (google-generative-ai.ts:207) and Mistral object arguments (mistral-conversations.ts:744-747) then reached the tool call sorted. Encode with orderedjson.MarshalInSourceOrder against the chunk text.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 334bb4b72a (2026-10-13)
  • Tests: ai/google_chunk_member_type_oracle_test.go::TestGoogleChunkMistypedMembersMatchPi, ai/mistral_chunk_member_type_oracle_test.go::TestMistralChunkMistypedMembersMatchPi
  • Release note: A Gemini or Mistral stream chunk with a member of an unexpected type no longer sorts the members of a tool call's object arguments: the call keeps the order the model wrote them in, as Pi's JSON.parse read does.

BF-3351167

The OAuth callback server reads an absolute-form request target with a scheme other than http or https, a…

  • Source: PiG's own defect
  • Symptom: The OAuth callback server reads an absolute-form request target with a scheme other than http or https, a relative reference against a base that has a path and query, and a \ path the way Node's new URL(request.url, base) does, instead of falling back to Go's net/url.
  • Root cause: The URL expression of callback-server.ts:80 (new URL(request.url ?? '/', 'http://localhost')) runs in Node (testdata/resolve_request.mjs) on 5000 seeded request targets and eight bases with paths, queries and fragments: origin-form paths with dot segments, encoded dots, backslashes, repeated slashes, queries of every shape, absolute-form targets with other schemes, '*' and authority-looking text. ResolveURL is the same relative-resolution state machine over ParseURL and ResolveRelativePath, for any scheme in the input and a special base; ResolveHTTPURL is gone, and requestPathAndQuery loses its net/url fallback.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2c04c1445a (2026-10-09), 31f4517fbe (2026-10-09), 335116710e (2026-10-09), d1cbde3321 (2026-10-09), d80c1cd150 (2026-10-09), da8a8704c6 (2026-10-09)
  • Tests: ai/authorization_input_oracle_test.go::TestAuthorizationInputParsersMatchPi, ai/chatgpt_manual_input_oracle_test.go::TestChatGPTManualInputMatchesPiOnSeededProbes, ai/radius_urls_oracle_test.go::TestRadiusURLsMatchPiOnSeededProbes, ai/service_urls_oracle_test.go::TestProviderServiceURLsMatchPiOnSeededProbes, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode, internal/nodeurl/reference_test.go::TestResolveURLMatchesNodeURL, internal/nodeurl/resolve_request_oracle_test.go::TestResolveURLRequestTargetsMatchNode, internal/nodeurl/url_oracle_test.go::TestParseMatchesNodeURL
  • Release note: The OAuth callback server reads an absolute-form request target with a scheme other than http or https, a relative reference against a base that has a path and query, and a \ path the way Node's new URL(request.url, base) does, instead of falling back to Go's net/url.

BF-33dc8a0

fix(ai): keep a custom client identity's slash in Bedrock's user agent, as the JavaScript SDK does

  • Source: PiG's own defect
  • Symptom: fix(ai): keep a custom client identity's slash in Bedrock's user agent, as the JavaScript SDK does
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 33dc8a0a22 (2026-10-21)
  • Tests: ai/identity_bedrock_oracle_test.go::TestBedrockUserAgentMatchesPi, ai/identity_bedrock_test.go::TestBedrockUserAgentCarriesTheClientIdentity

BF-33ea9e4

fix(sdk): name the Go ModelRegistry.Find change in the upgrade rules; Node adoption survives an unbound leaf…

  • Source: PiG's own defect
  • Symptom: fix(sdk): name the Go ModelRegistry.Find change in the upgrade rules; Node adoption survives an unbound leaf read
  • Root cause: The Go Find signature change is an incompatible API change: add its upgrade rule, classify the compiler's arity diagnostic for it and list the Go, Rust and Python changes under Upgrading.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 33ea9e4573 (2026-10-20)
  • Tests: extensions/sdk/upgrade/rules_test.go::TestModelRegistryFindChangeIsNamed

BF-33fe10d

ModelsError appends the cause detail the way Pi does: an Error cause with an empty message contributes its…

  • Source: PiG's own defect
  • Symptom: ModelsError appends the cause detail the way Pi does: an Error cause with an empty message contributes its name ("load failed: TypeError") instead of nothing (ai/auth_resolve.go NewModelsError, models-error.ts withCauseDetail; live-Pi oracle TestModelsErrorMessageMatchesPi, 3,000 probes, 147 red before; three mutations red).
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 33fe10d58e (2026-10-11), c4ad739eb8 (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/models_error_oracle_test.go::TestModelsErrorMessageMatchesPi, coding/extension/host/inproc/registries_test.go::TestShortcuts_DiagnosticMessageMatchesUpstream, coding/extension/host/inproc/shortcuts_oracle_test.go::TestRunnerShortcutsMatchPi, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries and 3 more in docs/bug-ledger.json
  • Release note: ModelsError appends the cause detail the way Pi does: an Error cause with an empty message contributes its name ("load failed: TypeError") instead of nothing (ai/auth_resolve.go NewModelsError, models-error.ts withCauseDetail; live-Pi oracle TestModelsErrorMessageMatchesPi, 3,000 probes, 147 red before; three mutations red).

BF-33fe10d-2

Live-Pi oracles TestExtensionSelectorMatchesPi (4,320 probes) and TestExtensionInputMatchesPi (1,144…

  • Source: PiG's own defect
  • Symptom: Live-Pi oracles TestExtensionSelectorMatchesPi (4,320 probes) and TestExtensionInputMatchesPi (1,144 probes) (tui/extension_selector_oracle_test.go, tui/extension_input_oracle_test.go): every frame and callback after each key against the pinned Pi component, default and rebound select keys, descriptions, wide/empty content, focus; no bug found; six and seven mutations red (the lone-LF confirm needed its own probe).
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 33fe10d58e (2026-10-11), c4ad739eb8 (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/models_error_oracle_test.go::TestModelsErrorMessageMatchesPi, coding/discover_extensions_oracle_test.go::TestDiscoverExtensionPathsMatchPi, coding/extension/host/inproc/registries_test.go::TestShortcuts_DiagnosticMessageMatchesUpstream, coding/extension/host/inproc/shortcuts_oracle_test.go::TestRunnerShortcutsMatchPi and 10 more in docs/bug-ledger.json
  • Release note: Live-Pi oracles TestExtensionSelectorMatchesPi (4,320 probes) and TestExtensionInputMatchesPi (1,144 probes) (tui/extension_selector_oracle_test.go, tui/extension_input_oracle_test.go): every frame and callback after each key against the pinned Pi component, default and rebound select keys, descriptions, wide/empty content, focus; no bug found; six and seven mutations red (the lone-LF confirm needed its own probe).

BF-3452432

Escape cancels a running ! or !! command again, as in Pi: the command stops and its block ends with…

  • Source: PiG's own defect
  • Symptom: Escape cancels a running ! or !! command again, as in Pi: the command stops and its block ends with (cancelled).
  • Root cause: TestTaskAbort's abort-again case let the caller join before the run phase started, so under load the caller's cancel raced the task settling and the waiter returned nil instead of the cancel cause. Pi's scheduler joins only a run invocation already on the line (scheduler.ts:274-296); the test now waits for the run phase first.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a4753ec61e (2026-10-06, #170), ba9c37b247 (2026-10-06, #171), 3094ee00ca (2026-10-07, #185), 3499c2bea7 (2026-10-07, #181), 9b63b2bc4e (2026-10-07, #176), 3452432f8b (2026-10-08, #190), 4610c9f4a1 (2026-10-08, #189)
  • Tests: coding/cli/rpc_ctx_signal_test.go::TestExtensionContextSignalComparedWithPi, coding/cli/rpc_initial_tools_test.go::TestRPCFreshPromptDeclaresStructuredSystemOnce, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleBusyEntriesAreSkippedNotErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledBeforeStartTouchesNothing, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledDuringTheLastEntryReportsTheCancellation, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleEntryLeasedAfterClassificationIsSkippedNotAnError, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleOtherErrorsStayErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleStopsWhenItsContextIsCancelled and 41 more in docs/bug-ledger.json
  • Release note: Escape cancels a running ! or !! command again, as in Pi: the command stops and its block ends with (cancelled). Escape had left the command running until it finished on its own. While an agent run streams, Escape still aborts the run and leaves a command started during it running, as Pi does.

BF-34aa421

fix(agent): hand the stream function Pi's messages; repair the tool flow in the provider path

  • Source: PiG's own defect
  • Symptom: fix(agent): hand the stream function Pi's messages; repair the tool flow in the provider path
  • Root cause: The agent loop's default conversion ran NormalizeMessages, so a custom stream function received a request Pi never builds: errored and aborted turns dropped, a synthetic 'No result provided' result for each orphaned call, orphaned/repeated results stripped, thinking rewritten (agent-loop.ts streamAssistantResponse calls convertToLlm only; the transformMessages pass lives in each provider, transform-messages.ts:36-190).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 34aa4214e8 (2026-10-10)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, agent/transform_provider_path_test.go::TestPoisonedHistoryReachesProviderLikePiWithoutRepair, ai/tool_flow_repair_test.go::TestRepairToolFlow, ai/tool_flow_repair_test.go::TestRepairToolFlowKeepsAWellFormedTranscript

BF-34b8e92

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 34b8e92a21 (2026-10-21)
  • Tests: coding/extension/host/subprocess/tool_window_settled_test.go::TestNodeToolWithoutStartWindowTakesTheFileMutationQueueAfterAwaiting

BF-34bcd58

OpenAI Responses prices a response that reports no service_tier at the requested tier, as Pi's…

  • Source: PiG's own defect
  • Symptom: OpenAI Responses prices a response that reports no service_tier at the requested tier, as Pi's processResponsesStream does (response.service_tier ?? options.serviceTier); only Codex did before, so a flex or priority request to OpenAI Responses was billed at the default rate.
  • Root cause: Pi openai-responses-shared.ts:110-122 OpenAIResponsesStreamOptions and :433 processResponsesStream, :578-583 tier pricing, :600 onProviderStreamEvent. - ai/openai_responses.go: the body of parseResponses is now the free function processResponsesStream(ctx, decoder, builder, *OpenAIResponsesStreamOptions); parseResponses builds the options (Codex gets resolveCodexServiceTier from openai-codex-responses.ts:631-639, Azure passes no pricing as azure-openai-responses.ts:97). Codex-only stream handling that Pi keeps outside the shared function stays in an unexported transport field. - Bug: non-Codex Responses priced a response without service_tier at the default rate; Pi falls back to…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 97051b184f (2026-10-05), 34bcd58204 (2026-10-07), 77bfe68d1f (2026-10-07), d5d010a275 (2026-10-07), db7007dad8 (2026-10-07)
  • Tests: ai/constrained_sampling_upstream_test.go::TestConstrainedSamplingConversionUpstream, ai/openai_responses_foreign_toolcall_test.go::TestResponsesConvertMessages_ForeignCopilotToolCallIDHashedToFcShape, ai/openai_responses_foreign_toolcall_test.go::TestResponsesToolCallID_UnattributedItemIsHashed, ai/openai_responses_shared_oracle_test.go::TestConvertResponsesMessagesMatchesPi, ai/openai_responses_stream_options_test.go::TestProcessResponsesStreamCallsTheInjectedOptions, ai/openai_responses_stream_options_test.go::TestResolveCodexServiceTier, ai/openai_responses_stream_options_test.go::TestResponsesPricesAResponseWithoutServiceTierAtTheRequestedTier, ai/openai_responses_test.go::TestNormalizeResponsesToolCallIDProviderSets and 11 more in docs/bug-ledger.json
  • Release note: OpenAI Responses prices a response that reports no service_tier at the requested tier, as Pi's processResponsesStream does (response.service_tier ?? options.serviceTier); only Codex did before, so a flex or priority request to OpenAI Responses was billed at the default rate.

BF-34c4d8e

MCP tools declare the CallToolResult scripts receive as Pi 1.2.0 does: content blocks with their type…

  • Source: PiG's own defect
  • Symptom: describeTool() and the Codemode: line of a directly exposed MCP tool listed _meta?, left out structuredContent without an output schema, and lost the types behind such references (Pi 1.2.0 mcp/tools.ts createMcpResultSchema, #10416).
  • Root cause: Activation and the tool_call wait use ToolInfo.ReachesTools instead of the IsCodemodeTool/IsToolSearchTool source-path checks, which are removed. The fake host's codemode and tool_search stand-ins carry the built-in tools' ReachesTools.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34c4d8e058 (2026-10-12), 8217a5923f (2026-10-12)
  • Tests: coding/mcpext/regression_test.go::TestCreateMcpToolDefinitionMatchesUpstreamProbes, coding/mcpext/tools_test.go::TestMCPToolsDeclaresTheCallToolResultScriptsReceiveKeepingReferencesInTheServersOutputSchema
  • Release note: MCP tools declare the CallToolResult scripts receive as Pi 1.2.0 does: content blocks with their type, text, data and mimeType fields, structuredContent always (typed unknown when the server declares no output schema), no _meta, and room for fields newer protocol versions add. Local $refs in a server's output schema are rewritten to point below structuredContent, so they resolve to the same definitions. Before, describeTool() and the Codemode: line of a directly exposed MCP tool listed _meta?, left out structuredContent without an output schema, and lost the types behind such references (Pi 1.2.0 mcp/tools.ts createMcpResultSchema, #10416).

BF-34c4d8e-2

The MCP extension decides which tools reach MCP tools by their ReachesTools, as Pi 1.2.0 does: another…

  • Source: PiG's own defect
  • Symptom: Only the built-in tools of those names were recognized, by their source path (Pi 1.2.0 mcp/index.ts).
  • Root cause: Activation and the tool_call wait use ToolInfo.ReachesTools instead of the IsCodemodeTool/IsToolSearchTool source-path checks, which are removed. The fake host's codemode and tool_search stand-ins carry the built-in tools' ReachesTools.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34c4d8e058 (2026-10-12), 8217a5923f (2026-10-12)
  • Tests: coding/mcpext/regression_test.go::TestCreateMcpToolDefinitionMatchesUpstreamProbes, coding/mcpext/tools_test.go::TestMCPToolsDeclaresTheCallToolResultScriptsReceiveKeepingReferencesInTheServersOutputSchema
  • Release note: The MCP extension decides which tools reach MCP tools by their ReachesTools, as Pi 1.2.0 does: another extension's codemode or tool_search that declares reachesTools is activated for MCP servers and waits for the servers its call may reach. Before, only the built-in tools of those names were recognized, by their source path (Pi 1.2.0 mcp/index.ts).

BF-34f7594

An extension that calls ctx.shutdown() from a session_start handler, or from a session_before_switch or…

  • Source: PiG's own defect
  • Symptom: An extension that calls ctx.shutdown() from a session_start handler, or from a session_before_switch or session_before_fork handler during /new, /resume or /fork, ends the session as Pi does: extensions get session_shutdown, pig prints the resume command when the session has a file, and exits 0 without waiting for the handler that asked for the shutdown.
  • Root cause: Pi's shutdown() stops the TUI, awaits runtimeHost.dispose() (session_shutdown), prints the resume command when the session file exists and calls process.exit(0); it never awaits the session_start, session_before_switch or session_before_fork handler that asked for the shutdown (interactive-mode.ts:2020-2024, 4251-4290).
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11)
  • Tests: internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-switch.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-start.toml
  • Release note: An extension that calls ctx.shutdown() from a session_start handler, or from a session_before_switch or session_before_fork handler during /new, /resume or /fork, ends the session as Pi does: extensions get session_shutdown, pig prints the resume command when the session has a file, and exits 0 without waiting for the handler that asked for the shutdown. pig had restored the terminal and then waited forever for that handler, and /new exited 1 as if creating the session had failed.

BF-34f7594-2

Fixed the interactive bash block stripping escape sequences with the wrong rule…

  • Source: PiG's own defect
  • Symptom: Fixed the interactive bash block stripping escape sequences with the wrong rule: BashExecutionComponent.AppendOutput now removes ANSI like Pi's stripAnsi (ansi-regex), so an unfinished ESC[38;5; loses only ESC[38;5 and a digit ends a sequence, instead of using the terminal-width scanner's rule.
  • Root cause: Pi's shutdown() stops the TUI, awaits runtimeHost.dispose() (session_shutdown), prints the resume command when the session file exists and calls process.exit(0); it never awaits the session_start, session_before_switch or session_before_fork handler that asked for the shutdown (interactive-mode.ts:2020-2024, 4251-4290).
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11)
  • Tests: internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-switch.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-start.toml
  • Release note: Fixed the interactive bash block stripping escape sequences with the wrong rule: BashExecutionComponent.AppendOutput now removes ANSI like Pi's stripAnsi (ansi-regex), so an unfinished ESC[38;5; loses only ESC[38;5 and a digit ends a sequence, instead of using the terminal-width scanner's rule. The regular expression moved to internal/text.StripANSI, which the tool output path shares.

BF-34f7594-3

Fixed the re: mode of the session selector search to read its pattern as JavaScript does (`new…

  • Source: PiG's own defect
  • Symptom: Fixed the re: mode of the session selector search to read its pattern as JavaScript does (new RegExp(pattern, "i")): assertions that cannot be repeated (\b{2}, ^*, a quantified lookbehind) and class ranges with a class escape ([a-\d]) now give Pi's results, a pattern is matched as UTF-16 code units so a quantifier binds to the second half of an astral character, \b, \w and \s use JavaScript's ASCII and WhiteSpace sets, case-insensitive matching uses JavaScript's case mapping (İ no longer matches i), (?s: and (?m: modifier groups are accepted, and an invalid pattern reports V8's message (Invalid regular expression: /…/i: Nothing to repeat).
  • Root cause: Pi's shutdown() stops the TUI, awaits runtimeHost.dispose() (session_shutdown), prints the resume command when the session file exists and calls process.exit(0); it never awaits the session_start, session_before_switch or session_before_fork handler that asked for the shutdown (interactive-mode.ts:2020-2024, 4251-4290).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11)
  • Tests: internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-switch.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-start.toml
  • Release note: Fixed the re: mode of the session selector search to read its pattern as JavaScript does (new RegExp(pattern, "i")): assertions that cannot be repeated (\b{2}, ^*, a quantified lookbehind) and class ranges with a class escape ([a-\d]) now give Pi's results, a pattern is matched as UTF-16 code units so a quantifier binds to the second half of an astral character, \b, \w and \s use JavaScript's ASCII and WhiteSpace sets, case-insensitive matching uses JavaScript's case mapping (İ no longer matches i), (?s: and (?m: modifier groups are accepted, and an invalid pattern reports V8's message (Invalid regular expression: /…/i: Nothing to repeat).

BF-34f7594-4

Repeated codemode timeouts or aborts can no longer pile up running VMs: at most two codemode VMs are alive…

  • Source: PiG's own defect
  • Symptom: Repeated codemode timeouts or aborts can no longer pile up running VMs: at most two codemode VMs are alive across sessions in one process, and a cancelled VM holds its slot until it exits (#10748).
  • Root cause: Pi's shutdown() stops the TUI, awaits runtimeHost.dispose() (session_shutdown), prints the resume command when the session file exists and calls process.exit(0); it never awaits the session_start, session_before_switch or session_before_fork handler that asked for the shutdown (interactive-mode.ts:2020-2024, 4251-4290).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11)
  • Tests: internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-switch.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-start.toml
  • Release note: Repeated codemode timeouts or aborts can no longer pile up running VMs: at most two codemode VMs are alive across sessions in one process, and a cancelled VM holds its slot until it exits (#10748).

BF-3548f29

fix(tui): bash block strips ANSI with Pi's utils/ansi.ts rule

  • Source: PiG's own defect
  • Symptom: fix(tui): bash block strips ANSI with Pi's utils/ansi.ts rule
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 3548f29c38 (2026-10-10)
  • Tests: internal/codingagent/bash_execution_oracle_test.go::TestBashExecutionComponentMatchesPi

BF-35e46b1

Codemode output and returned values keep a nested Error's name and message, with its own enumerable fields…

  • Source: A Pi defect that PiG fixed first (divergence D145)
  • Symptom: Codemode output and returned values keep a nested Error's name and message, with its own enumerable fields, instead of {} (D145, Pi #10650).
  • Root cause: D142 (coding/session_run_state.go): runningDeferred was a bool that the nested runDeferredSettledActions of the first deferred run cleared, so a waiter registered during the run resolved while a second deferred prompt was in its preflight (order request:1 queued:a queued:b request:2 idle-resolved request:3). It is now a batch count. Pi: agent-session.ts:1077-1099 _emitAgentSettled, :2434-2451 abort/waitForIdle, :2764-2765 compact awaits abort.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10650
  • Removed when Pi fixes it: Pi's codemode serializes nested Errors with their name and message.
  • Fix commits: 0225a43aa5 (2026-10-10), 35e46b114e (2026-10-10), bd4c4d7f15 (2026-10-10), 39fc7f1aea (2026-10-11)
  • Tests: codemode/nested_error_console_test.go::TestNestedErrorsKeepTheirMessageInConsoleOutputAndMixedResults, codemode/nested_error_output_test.go::TestNestedErrorsKeepTheirEnumerableFields, codemode/nested_error_output_test.go::TestNestedToolErrorsKeepTheirMessageInSerializedOutput, codemode/nested_error_unreadable_test.go::TestNestedErrorsWithUnreadableNamesSerializeAsPiDoes, codemode/sandbox_test.go::TestEmbeddedSourcesMatchTheirRecordedHashes, coding/session_settled_idle_test.go::TestAbortDuringADeferredContinuationDiscardsTheRest, coding/session_settled_idle_test.go::TestCustomMessageRunSettledHandlerAwaitsCompaction, coding/session_settled_idle_test.go::TestWaitForIdleStaysPendingThroughEveryDeferredPrompt and 1 more in docs/bug-ledger.json
  • Release note: Codemode output and returned values keep a nested Error's name and message, with its own enumerable fields, instead of {} (D145, Pi #10650).

BF-364cc57

RPC model objects of built-in models match Pi's: each keeps its catalog entry's own member order and spelling…

  • Source: PiG's own defect
  • Symptom: RPC model objects of built-in models match Pi's: each keeps its catalog entry's own member order and spelling ("type" last for most models, first for others; inputLimits.images as the entry spells it), and a models.json override keeps the changed members in place, with merged objects listing the catalog's members first.
  • Root cause: Pi's RPC model objects are its catalog's model objects, so each is JSON.stringify of its MODELS entry: "type" is last in 1164 chat entries and first in 399, baseUrl precedes provider in some, and inputLimits.images lists maxPerRequest first. PiG wrote every model in one fixed RPCModel order.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 364cc57510 (2026-10-10)
  • Tests: ai/catalog_model_json_oracle_test.go::TestCatalogModelJSONIsAbsentWithoutARecord, ai/catalog_model_json_oracle_test.go::TestCatalogModelJSONMatchesPiForEveryBuiltinModel, coding/cli/rpc_model_type_binary_test.go::TestRPCModelObjectsCarryTheRegistryModelType, coding/cli/rpc_model_type_binary_test.go::TestRPCOverriddenBuiltinModelKeepsTheCatalogOrder
  • Release note: RPC model objects of built-in models match Pi's: each keeps its catalog entry's own member order and spelling ("type" last for most models, first for others; inputLimits.images as the entry spells it), and a models.json override keeps the changed members in place, with merged objects listing the catalog's members first.

BF-3651a90

A provider stream that keeps sending keepalive comments or pings but no event now ends with an error after…

  • Source: A Pi defect that PiG fixed first (divergence D131)
  • Symptom: A provider stream that keeps sending keepalive comments or pings but no event now ends with an error after httpIdleTimeoutMs instead of hanging for ever; the timeout restarts on each event, and on keepalives while the model is reasoning (D131, pi-mono #10761, #10762).
  • Root cause: sseIdleBody.scan ended a line at CR and again at the LF after it, so the empty line between them reset the event's ping flag: a CRLF stream's ping event counted as progress and kept a stalled stream alive for ever, the hang D131 fixes. A CR followed by LF is now one line end, and the SSE field value drops only the single space the grammar allows. New rows: ping events with CRLF and with CR line ends (the CRLF row is red without the fix).
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10761
  • Removed when Pi fixes it: Pi ends a stream that sends no event for httpIdleTimeoutMs.
  • Fix commits: 00c8373052 (2026-10-10), 3651a9096a (2026-10-10), 4b6ef3e320 (2026-10-10), 64461c2028 (2026-10-10), b9e2b3fb9b (2026-10-10)
  • Tests: ai/sse_event_idle_test.go::TestSSEIdleTrackerRecognizesReasoningEvents, ai/sse_event_idle_test.go::TestSSEKeepalivesAfterANonReasoningEventStillEndTheStream, ai/sse_event_idle_test.go::TestSSEKeepalivesAfterAReasoningEventDoNotEndTheStream, ai/sse_event_idle_test.go::TestSSEStreamWithOnlyKeepalivesEndsAfterTheIdleTimeout, ai/sse_event_idle_test.go::TestSSEStreamWithSlowEventsIsNotCutOff
  • Release note: A provider stream that keeps sending keepalive comments or pings but no event now ends with an error after httpIdleTimeoutMs instead of hanging for ever; the timeout restarts on each event, and on keepalives while the model is reasoning (D131, pi-mono #10761, #10762).

BF-369b1e1

grep with context no longer loads each matched file into memory

  • Source: PiG's own defect
  • Symptom: A search over a directory of very large logs used to exhaust the heap; the output is unchanged (Pi #9276).
  • Root cause: grep.ts getFileLines reads and splits every matched file; PiG ported it. Output unchanged; heap growth for a 50 MB file with context 2 drops from 150 MB to under 12 MB.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 369b1e1ce3 (2026-10-09)
  • Tests: internal/codingagent/tools/grep_context_memory_test.go::TestGrepContextDoesNotLoadTheMatchedFile, internal/codingagent/tools/grep_context_memory_test.go::TestGrepContextOfUnreadableFile, internal/codingagent/tools/grep_context_memory_test.go::TestGrepLineWindowMatchesTheWholeFileSplit
  • Release note: grep with context no longer loads each matched file into memory. A search over a directory of very large logs used to exhaust the heap; the output is unchanged (Pi #9276).

BF-36e192b

fix(lint): port Kitty chunking in UTF-16 units; clear the codemode, terminal image and exit-restore lint hits

  • Source: PiG's own defect
  • Symptom: fix(lint): port Kitty chunking in UTF-16 units; clear the codemode, terminal image and exit-restore lint hits
  • Root cause: EncodeKitty measured and sliced the payload in bytes where Pi's encodeKitty uses .length and .slice (UTF-16 units). EncodeITerm2's size is Node's (units * 3) >>> 2. The codemode worker wait is computed in integers, and the doubleclose, numbers, env-stat and bare-recover sites name their Pi counterpart or invariant.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 36e192bef8 (2026-10-21)
  • Tests: tui/terminal_image_iterm2_options_test.go::TestEncodeKittyChunksByUTF16Units

BF-36e82ad

fix(durable): a non-finite number is a strict JSON error whichever JSON package a value's own encoder used

  • Source: PiG's own defect
  • Symptom: fix(durable): a non-finite number is a strict JSON error whichever JSON package a value's own encoder used
  • Root cause: Fix-Commit:
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 36e82ade57 (2026-10-24)
  • Tests: None recorded.

BF-37c6048

fix(tui): a streamed Markdown never resumes past a paragraph clipped before a LaTeX start

  • Source: PiG's own defect
  • Symptom: fix(tui): a streamed Markdown never resumes past a paragraph clipped before a LaTeX start
  • Root cause: renderBlocks clips a top-level paragraph where "$$" or "[" follows its first character (Lexer.ts:241-270 with pi-tui's latexBlock start()) and lexes the rest of the line again. resumeBarrier only knew a LaTeX start at the beginning of a line, so (a) a later "$$" that closes the pending block changed blocks before a recorded point, and (b) the line visited again added a second point at the same line whose kept output already held the clipped character, which a later resume rendered twice ("x" twice for "\nx$$$$$\n\n1. c- a$$* b> \n "). latexClipStart makes such a line a barrier like a line-start LaTeX block.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 37c60480c3 (2026-10-10)
  • Tests: tui/markdown_resume_soup_test.go::TestResumedAssistantBlockEqualsAFreshBlock, tui/markdown_resume_soup_test.go::TestResumedMarkdownSoupEqualsAFreshRender, tui/markdown_resume_test.go::TestResumedMarkdownEqualsAFreshRenderForDependentBlocks

BF-37d2daa

An extension's ctx.model and model lists keep a model's cost.tiers when the model's record has it as an…

  • Source: PiG's own defect
  • Symptom: An extension's ctx.model and model lists keep a model's cost.tiers when the model's record has it as an empty array (a provider object's or a models store's model), as Pi keeps the authored cost, instead of dropping the member.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 37d2daad81 (2026-10-13)
  • Tests: None recorded.
  • Release note: An extension's ctx.model and model lists keep a model's cost.tiers when the model's record has it as an empty array (a provider object's or a models store's model), as Pi keeps the authored cost, instead of dropping the member.

BF-3804a5a

The pi-messages and classifier (System One, decisions) requests are sent to the URL as written and a URL with…

  • Source: PiG's own defect
  • Symptom: The pi-messages and classifier (System One, decisions) requests are sent to the URL as written and a URL with credentials is refused as fetch refuses it, where Pig sent the credentials as Basic auth.
  • Root cause: pi-messages.ts:370,392 hands new URL(...) to options.fetch ?? globalThis.fetch, classifier-shared.ts:73 the target to the same; fetch sends the serialized URL as is and throws a TypeError naming it for a URL with a username or password. Pig re-parsed the href with net/url and sent credentials as Basic auth. Both now use ai.newFetchRequest.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 3804a5a2ab (2026-10-10)
  • Tests: ai/sdk_request_url_oracle_test.go::TestPostClassifierRequestRefusesCredentialsLikeFetch, ai/sdk_request_url_oracle_test.go::TestSDKProvidersRequestURLMatchesSDK
  • Release note: The pi-messages and classifier (System One, decisions) requests are sent to the URL as written and a URL with credentials is refused as fetch refuses it, where Pig sent the credentials as Basic auth.

BF-3840820

MCP tool results whose content blocks carry members of another type than the protocol's (a numeric text, a…

  • Source: PiG's own defect
  • Symptom: MCP tool results whose content blocks carry members of another type than the protocol's (a numeric text, a size string, a missing uri, a null block, a resource that is not an object) now convert as Pi converts them: the model sees what Pi's would, and a block Pi cannot read ends the tool call with Pi's error instead of a placeholder.
  • Root cause: package_updates.go, package_update_operations.go, package_npm_metadata.go and the update source matching move from cmd/pig into internal/packagemanager. PackageManager.Update(source) and PackageManager.CheckForAvailableUpdates() are Pi's update and checkForAvailableUpdates; cmd/pig update and the startup update check call them. Pure update tests (operations, semver/metadata, capture) move with the code; the CLI-fixture tests stay in cmd/pig and call the exported members. Behaviour is unchanged (cmd/pig package/update/npm/git tests pass).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a79de03e7f (2026-10-07), d0ad06fc18 (2026-10-07), 3840820cd6 (2026-10-08), ab8ac8ecfc (2026-10-08)
  • Tests: coding/cli/package_update_cases_upstream_test.go::TestPackageUpdateLookupPrefixesOriginal, coding/mcpext/tools_test.go::TestMCPToolsKeepAnExplicitEmptyTitle, coding/mcpext/tools_test.go::TestMCPToolsShowsBlobsOfTextTypesWithBOMPaddedMimeTypesAsText, internal/packagemanager/package_capture_test.go::TestPackageCaptureChild, internal/packagemanager/package_capture_test.go::TestPackageCaptureOutputAndFailure, internal/packagemanager/package_capture_test.go::TestPackageCaptureStartFailure, internal/packagemanager/package_capture_test.go::TestPackageCaptureTimeoutWaitsForChild, internal/packagemanager/package_manager_test.go::TestPackageManagerPersistsInstallAndRemoval and 6 more in docs/bug-ledger.json
  • Release note: MCP tool results whose content blocks carry members of another type than the protocol's (a numeric text, a size string, a missing uri, a null block, a resource that is not an object) now convert as Pi converts them: the model sees what Pi's would, and a block Pi cannot read ends the tool call with Pi's error instead of a placeholder.

BF-38b0f0c

Syntax highlighting keeps its colors after the first line of multiline strings and comments in fenced code…

  • Source: PiG's own defect
  • Symptom: Syntax highlighting keeps its colors after the first line of multiline strings and comments in fenced code blocks.
  • Root cause: renderHighlightedHtml formats each non-empty line of a scoped run on its own, and the CLI highlight theme colors string substitutions with the text color (utils/syntax-highlight.ts, theme.ts, #10143).
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 38b0f0c036 (2026-10-05)
  • Tests: tui/highlight_test.go::TestCliHighlightThemeColorsSubstitutionsWithTheTextColor, tui/highlight_test.go::TestHighlightCode_ColorsEachLineOfPythonDocstringsIndependently, tui/syntax_highlight_upstream_test.go::TestSyntaxHighlightInterpolationEndsWithTheString, tui/syntax_highlight_upstream_test.go::TestSyntaxHighlightInterpolationTakesTheTextColor
  • Release note: Syntax highlighting keeps its colors after the first line of multiline strings and comments in fenced code blocks.

BF-397866f

A Go MCP client that declares an empty capability, such as sampling or elicitation, now sends it as {}

  • Source: PiG's own defect
  • Symptom: The capability used to be left out of initialize, so a server could not see that the client supports it.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 397866f7f2 (2026-10-06)
  • Tests: mcp/client_options_test.go::TestClientInitializeCarriesTheConfiguredClientOptions
  • Release note: A Go MCP client that declares an empty capability, such as sampling or elicitation, now sends it as {}. The capability used to be left out of initialize, so a server could not see that the client supports it.

BF-3a0d0e1

A Provider object that an extension registers while it loads is now applied after every extension has…

  • Source: PiG's own defect
  • Symptom: A Provider object that an extension registers while it loads is now applied after every extension has loaded, like a provider configuration, instead of at once.
  • Root cause: Pi's loader queues a Provider object as it queues a configuration, and its model runtime stores nothing of a registration it refuses. The host now queues a Provider object declared while an extension loads, whatever registry is bound, and a registration the registry refuses when the queue drains gives back what the host recorded at load: the replica record, the claim on the provider's callbacks, the bridged OAuth login, a Provider object's proxy. A superseded owner is told only when the registry accepts. A scenario reloads an extension whose registration is rejected and compares with Pi.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 3a0d0e1711 (2026-10-25)
  • Tests: coding/cli/extension_host_wiring_guard_test.go::TestBuildFlushesTheExtensionHostProviderRegistrations, coding/extension/host/subprocess/native_provider_callback_context_test.go::TestNodeNativeProviderReceivesTheCallersOnResponseAndOnPayloadByName, coding/extension/host/subprocess/native_provider_oauth_test.go::TestNativeProviderOAuthRefreshAndLogin, coding/extension/host/subprocess/provider_load_rejection_rollback_test.go::TestRejectedLoadRegistrationLeavesAnEarlierRegistrationIntact, coding/extension/host/subprocess/provider_object_load_queue_test.go::TestProviderObjectRegisteredWhileLoadingIsQueuedForABoundRegistry, test/parity/scenarios/extensions-runtime/89-reload-provider-registration-failure.toml
  • Release note: A Provider object that an extension registers while it loads is now applied after every extension has loaded, like a provider configuration, instead of at once. When the model registry rejects a registration, PiG now leaves nothing of it behind: the login it declared, its entry in the model list that extensions read, and its claim on the provider are given back, so a registration of the same provider by another extension stays as it was. Pi's registry validates a registration before it stores anything of it.

BF-3ad9ec8

fix(tui): tree label prompt gives its input width-2 cells (Pi LabelInput)

  • Source: PiG's own defect
  • Symptom: fix(tui): tree label prompt gives its input width-2 cells (Pi LabelInput)
  • Root cause: tree-selector.ts LabelInput.render passes availableWidth = width - 2 straight to Input.render; Pig clamped it to 1, so a two-column terminal drew one cell more.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 3ad9ec876a (2026-10-11)
  • Tests: internal/codingagent/scoped_models_oracle_test.go::TestScopedModelsSelectorMatchesPi, internal/codingagent/session_selector_oracle_test.go::TestSessionSelectorInputMatchesPi, internal/codingagent/tree_selector_oracle_test.go::TestTreeSelectorInputMatchesPi, tui/model_selector_oracle_test.go::TestModelSelectorInputMatchesPi

BF-3b009a3

chord/host: a child host forked by ForkDriver no longer inherits its parent's CHORD2_LOCK

  • Source: PiG's own defect
  • Symptom: A child host that forked its own children passed its lock on, and children without a lock of their own failed to start with locked (Pi a4d040d3).
  • Root cause: ForkDriver passed os.Environ() to the child, so a child host that forks its own children (or a test run inside a worker) passed its lock on, and children without a lock of their own failed with locked. The child's environment drops an inherited CHORD2_LOCK before the driver's env and lock apply, as Pi's forkDriver now does. TestHostManagerAChildWithoutALockDoesNotInheritTheParentsLock ports the new manager.test.ts case.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 3b009a35c1 (2026-10-13), 488a47a47d (2026-10-13), b02876b1d5 (2026-10-13)
  • Tests: chord/host/link_upstream_test.go::TestLinksARouteThatReturnsAnAlreadyClosedLinkDoesNotPoisonTheHostName, chord/host/link_upstream_test.go::TestLinksAnAlreadyClosedLinkIsNotKeptAndStartsNoPinger, chord/host/manager_upstream_test.go::TestHostManagerAChildWithoutALockDoesNotInheritTheParentsLock, chord/host/manager_upstream_test.go::TestProcessLock, chord/host/manager_upstream_test.go::TestProcessLockAdmitsOneHolderWhenTheTemporaryDirectoryIsTooDeepForASocketPath, chord/host/manager_worker_unix_test.go::TestLockSocketPathKeepsTheSocketPathWithinSunPath
  • Release note: chord/host: a child host forked by ForkDriver no longer inherits its parent's CHORD2_LOCK. Before, a child host that forked its own children passed its lock on, and children without a lock of their own failed to start with locked (Pi a4d040d3).

BF-3b3dba6

A streamed tool argument of -0 is held as 0, so the serialized arguments write 0 as Pi's…

  • Source: PiG's own defect
  • Symptom: A streamed tool argument of -0 is held as 0, so the serialized arguments write 0 as Pi's JSON.stringify does instead of -0.
  • Root cause: Comparing ToolCall.ArgumentsJSON token by token with Pi's JSON.stringify(parseStreamingJson(text)) shows member order matches on all 1504 object results, but -0 differs (48 results): Pi writes 0 (JSON.stringify, as String(-0) is "0"), Go's encoding/json writes -0. nonFiniteToNull now maps -0 to 0, on the strict path as well.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 3b3dba64c8 (2026-10-09)
  • Tests: ai/streaming_json_oracle_test.go::TestStreamingJSONMatchesPiOnSeededInput
  • Release note: A streamed tool argument of -0 is held as 0, so the serialized arguments write 0 as Pi's JSON.stringify does instead of -0.

BF-3b8aa66

A Piglet Binary that strips bedrock-converse-stream links the AWS SDK again: the Bedrock retry backoff…

  • Source: PiG's own defect
  • Symptom: A Piglet Binary that strips bedrock-converse-stream links the AWS SDK again: the Bedrock retry backoff (ai/bedrock_retry_backoff.go) had no pig_strip_bedrock_converse_stream build tag and imported aws-sdk-go-v2 (27 packages).
  • Root cause: An all-strip Piglet Binary still linked go/types, go/importer and friends (965 KB of symbols) because coding/cli's extension upgrade analysis calls sdk/upgrade.Open, and that analysis needs the Go SDK the Piglet stripped. analyzeExtension moves to extension_upgrade_analyze.go (stock) with a pig_strip_extension_sdk_go variant that reports the strip (D92); the linker then drops the type checker: go/* symbols 965 KB -> 272 KB, Binary 56.10 -> 53.69 MB before -s -w.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 3b8aa66c74 (2026-10-11)
  • Tests: coding/cli/extension_upgrade_analyze_off_test.go::TestUpgradeInAGoSDKStrippedBinaryNamesTheStrip, coding/cli/strip_providers_binary_test.go::TestStripBedrockConverseStreamBuildOmitsAWSSDK, coding/cli/strip_upgrade_link_test.go::TestStripExtensionSDKGoBuildOmitsTheUpgradeAnalysis
  • Release note: A Piglet Binary that strips bedrock-converse-stream links the AWS SDK again: the Bedrock retry backoff (ai/bedrock_retry_backoff.go) had no pig_strip_bedrock_converse_stream build tag and imported aws-sdk-go-v2 (27 packages). It carries the tag now, like the rest of the Bedrock provider.

BF-3ba74f2

fix(codingagent): wait for a read's rest on the route snapshot that found no modal

  • Source: PiG's own defect
  • Symptom: fix(codingagent): wait for a read's rest on the route snapshot that found no modal
  • Root cause: dispatchTerminalRead took its route-change signal from a second snapshot after dispatchRoutedInput's modal check, and before any modal had armed the signal did not exist, so a modal arming off the loop left the main loop waiting for input that went to the modal. The wait now uses the signal of the snapshot that found no modal, and the signal exists before the first modal arms.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 3ba74f2827 (2026-10-13)
  • Tests: None recorded.

BF-3cb7131

fix(sdk-py): stage pig_sdk.tui with the package, which pig_sdk imports at load; keep the pi-ai patterns…

  • Source: PiG's own defect
  • Symptom: fix(sdk-py): stage pig_sdk.tui with the package, which pig_sdk imports at load; keep the pi-ai patterns gofmt-clean
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 3cb7131f3a (2026-10-21)
  • Tests: extensions/sdk-py/bundle_test.go::TestEveryPackageSourceIsBundled

BF-3ce6bcb

fix(changelog,source): /i scheme tests fold as JavaScript does, not as RE2's (?i)

  • Source: PiG's own defect
  • Symptom: fix(changelog,source): /i scheme tests fold as JavaScript does, not as RE2's (?i)
  • Root cause: changelog.ts:14 URL_SCHEME_RE and git.ts:177 test a scheme with a non-unicode /i regex, which folds through toUpperCase and never maps U+017F (ſ) or U+212A (K) to s or k; RE2's (?i) does. A changelog link such as t was kept as an absolute URL where Pi rewrites it to the repository blob URL.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 3ce6bcb129 (2026-10-13)
  • Tests: coding/source/parse_git_oracle_test.go::TestParseGitMatchesPi, internal/codingagent/changelog_links_test.go::TestNormalizeChangelogLinksMatchesPi

BF-3cf03c0

A faux provider error message that carries a response id writes responseId after errorMessage in session…

  • Source: PiG's own defect
  • Symptom: responseId followed model.
  • Root cause: fauxAssistantMessage (faux.ts:97-99) writes errorMessage, then responseId, then timestamp after usage and stopReason. assistantErrorFirst put responseId in the head of the struct, before usage, so a faux error message that records a responseId persisted its keys in a different order than Pi's JSON.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 3cf03c047a (2026-10-10)
  • Tests: ai/assistant_error_response_id_order_test.go::TestAssistantErrorFirstWritesResponseIDAfterErrorMessage
  • Release note: A faux provider error message that carries a response id writes responseId after errorMessage in session JSON, in the key order Pi's fauxAssistantMessage uses. Before, responseId followed model.

BF-3d12e48

fix(tui): tree selector cursor follows Pi's selectedIndex over an empty list

  • Source: PiG's own defect
  • Symptom: fix(tui): tree selector cursor follows Pi's selectedIndex over an empty list
  • Root cause: Pi's up/down/page keys move selectedIndex without clamping, so over an empty filtered list it becomes -1 or 1 and applyFilter keeps it while lastSelectedId is null (tree-selector.ts handleInput, applyFilter). Go clamped to 0.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 3d12e48493 (2026-10-10)
  • Tests: internal/codingagent/tree_branching_oracle_test.go::TestTreeListBranchingRenderMatchesPiOracle

BF-3d475c4

Image resize strategy against live Pi: new TestResizeImageDimensionStrategyMatchesPi…

  • Source: PiG's own defect
  • Symptom: Image resize strategy against live Pi: new TestResizeImageDimensionStrategyMatchesPi (internal/imageprocessing/resize_strategy_oracle_test.go; resizeImageInProcess through Photon vs ResizeImage; 200 seeded PNGs 1-2,600 px, maxWidth/maxHeight 1-3,000, default/huge/1-byte maxBytes, unset/explicit mime).
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), 6a6fffa638 (2026-10-10), 3d475c443d (2026-10-11)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult and 20 more in docs/bug-ledger.json
  • Release note: Image resize strategy against live Pi: new TestResizeImageDimensionStrategyMatchesPi (internal/imageprocessing/resize_strategy_oracle_test.go; resizeImageInProcess through Photon vs ResizeImage; 200 seeded PNGs 1-2,600 px, maxWidth/maxHeight 1-3,000, default/huge/1-byte maxBytes, unset/explicit mime). One real fix, red first (15 of 300): a scaled side that rounds to zero (8000x1 under the 2000 limit gives a height of round(0.25) = 0) made PiG clamp it to one pixel and return an image, where Pi's Photon resize throws and resizeImageInProcess returns null, so the image is dropped; ResizeImage now returns ErrImageTooLarge (internal/imageprocessing/images.go). Regression TestResizeImageReturnsNoImageWhenAScaledSideRoundsToZero. 5 mutations: 4 red; the 0.75 shrink factor survives because the shrink loop is reached only when every candidate misses maxBytes, which depends on the encoders (named as not compared). Not compared: the encoded bytes, which candidate wins a tight maxBytes, BMP input (PiG-only branch), the shrink steps.

BF-3def2a7

MCP servers whose names are numbers ("9", "42") load and report their errors in the order Pi does: a JSON…

  • Source: PiG's own defect
  • Symptom: MCP servers whose names are numbers ("9", "42") load and report their errors in the order Pi does: a JSON object lists its array-index keys first, in ascending order, then the others in file order.
  • Root cause: TestLoadMcpConfigMatchesPiOnRandomFiles loads 1500 random global and project mcp.json files through Pi's loadMcpConfig and PiG's LoadMcpConfig: 187 differed, among them every file with a numeric server name, because Object.entries lists array-index keys first and orderedjson.Object kept file order. Object.Set now inserts an array-index key at its ascending place before the other keys (Keys, MarshalJSON, Clone follow), with TestObjectEnumeratesArrayIndexKeysFirst. Dependent packages (codingagent, extension, mcpext, mcp, packagecontent) pass; the failures left in tools and the node-backed subprocess tests are the fd/rg and node-shim environment ones.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 10e9b5c109 (2026-10-09), 1f84136411 (2026-10-09), 5ceebfdae9 (2026-10-09), b41d7817cc (2026-10-09), 3def2a7bd8 (2026-10-10)
  • Tests: ai/anthropic_messages_response_oracle_test.go::TestAnthropicMessagesResponseMatchesPi, ai/models_store_bytes_oracle_test.go::TestModelsStoreFileBytesMatchPi, ai/models_store_lossless_test.go::TestModelsStoreKeepsUnknownFieldsThroughCloneAndFile, ai/openai_completions_response_oracle_test.go::TestOpenAICompletionsResponseMatchesPi, ai/openai_responses_response_oracle_test.go::TestOpenAIResponsesResponseMatchesPi, ai/transcript_replay_oracle_test.go::TestTranscriptReplayMatchesPi, coding/mcpext/config_load_oracle_test.go::TestLoadMcpConfigMatchesPiOnRandomFiles, coding/nested_recorder_oracle_test.go::TestNestedCallRecorderMatchesPi and 2 more in docs/bug-ledger.json
  • Release note: MCP servers whose names are numbers ("9", "42") load and report their errors in the order Pi does: a JSON object lists its array-index keys first, in ascending order, then the others in file order. The ordered JSON object the MCP configuration, tool arguments and other raw objects use now follows that rule.

BF-3e755ef

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: ai: SystemOneWireRequest keeps an explicit empty images list (system-one-shared.ts images?: string[]) instead of dropping it with omitempty, and the OpenRouter detection cites typesafe-system-one.ts:20. tools: WaitContext's hand-off goroutine moves out of its loop. gen-models: hasChatCompat compares with an encoder that does not escape <, > and &. Signed-off-by: Michael Kinsy 54590707+MichaelKinsy@users.noreply.github.com
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 3e755ef501 (2026-10-22)
  • Tests: ai/system_one_wire_test.go::TestSystemOneWireRequestImagesKeepAnExplicitEmptyList, ai/system_one_wire_test.go::TestTypesafeSystemOneDetectCompatShapes, internal/codingagent/tools/mutation_queue_test.go::TestFileMutationQueueWithPanickingCallbackStillDrains, internal/codingagent/tools/mutation_queue_test.go::TestMutationTicketWaitContextGaveUpTicketPassesOnAfterEveryPredecessor

BF-3ee123c

CI: the Linux / portmap job (make portmap-check) no longer runs out its 60-minute limit

  • Source: PiG's own defect
  • Symptom: CI: the Linux / portmap job (make portmap-check) no longer runs out its 60-minute limit.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: Linux
  • Fixed in: 0.5.2
  • Fix commits: 3ee123c6d5 (2026-10-20, #255)
  • Tests: agent/stream_fn_descriptor_test.go::TestAgentEffectiveStreamFunction, chord/host/manager_fork_test.go::TestForkInstanceReplaysControlMessagesReceivedBeforeTheFirstListener, chord/host/manager_ipc_listener_test.go::TestNodeChannelGivesTheFirstMessageListenerTheMessagesThatCameBeforeIt, chord/host/manager_ipc_listener_test.go::TestNodeChannelQueuesAMessageThatArrivesDuringTheReplayBehindTheBacklog, coding/cli/extension_stale_copy_test.go::TestStaleCopyDiagnosticFindsAnEditedCopyByItsModulePath, coding/cli/extension_upgrade_completeness_startup_test.go::TestStartupOffersEveryDriftedExtensionAndOneYesUpgradesThemAll, coding/cli/extension_upgrade_completeness_test.go::TestDryRunReportsWhatTheRewrittenTreeDoes, coding/cli/extension_upgrade_completeness_test.go::TestExtensionUpgradeCommandAppliesWhatItCanAndListsTheRest and 40 more in docs/bug-ledger.json
  • Release note: CI: the Linux / portmap job (make portmap-check) no longer runs out its 60-minute limit. portmapcheck builds each evidence package's test binary once instead of once per PORT_MAP row, runs rows that name the same tests of a package once, and keeps a marker row's killed-mutant verdict in its cached record. CI runs the check as three shards (make portmap-check-N, portmapcheck -shard N/3); every shard computes the same plan and judges its own rows, and the Linux verification job still requires all of them. Each row's requirement is unchanged: its named tests pass, they execute its mapped cited files, and a marker row's mutant makes them fail.

BF-3fa6e63

MCP servers with resources registered no tools until every resource was listed, so a slow listing (a large…

  • Source: PiG's own defect
  • Symptom: MCP servers with resources registered no tools until every resource was listed, so a slow listing (a large drive) kept their tools out of the first prompt.
  • Root cause: Ports Pi packages/coding-agent/src/extensions/mcp/{runtime,cli,index}.ts: a connection no longer calls resources/list and resources/templates/list (fetchResources, refreshResources and the resources/list_changed handler are gone), so tools register without waiting for a slow listing. Connection keeps HasResources; /mcp shows "connected · N tools · resources" (index.ts describeState), pig mcp list prints " offers resources" and --json reports resources: true instead of the two counts (cli.ts list). The resource tools still list and read on demand.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 3fa6e63e8e (2026-10-12)
  • Tests: coding/mcpext/cli_upstream_test.go::TestMcpCommandListReportsThatAServerOffersResourcesWithoutCountingThem, coding/mcpext/connection_test.go::TestMCPConnectionsConnectsToServersWithResourcesWithoutListingThem, coding/mcpext/responsive_test.go::TestMcpDescribeStateShowsThatAServerOffersResources
  • Release note: MCP servers with resources registered no tools until every resource was listed, so a slow listing (a large drive) kept their tools out of the first prompt. PiG no longer lists resources when connecting; the resource tools still list and read them on demand.

BF-3fbdfd2

A configured number in settings text prints as JavaScript's String(number): -0 is 0, and a magnitude of…

  • Source: PiG's own defect
  • Symptom: A configured number in settings text prints as JavaScript's String(number): -0 is 0, and a magnitude of 1e21 or more, or below 1e-6, gets an exponent (the fullscreen wheel scroll lines label and choices, and the invalid compaction token setting message).
  • Root cause: wheelScrollLinesLabel, wheelScrollLinesValues and compactionNumberText used strconv.FormatFloat(v, 'f', -1, 64): -0 printed -0 (JS: 0), 1e21 printed 22 digits (JS: 1e+21) and 1e-7 printed 0.0000001 (JS: 1e-7). Pi interpolates the configured number with String(), so they use jsnumber.String.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 3fbdfd2140 (2026-10-10), 6a6fffa638 (2026-10-10), bc4cd62404 (2026-10-10)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain, internal/codingagent/settings_number_text_test.go::TestSettingsNumberTextIsString, internal/jsstring/toprecision_oracle_test.go::TestToPrecisionAgainstNodeCorpus and 3 more in docs/bug-ledger.json
  • Release note: A configured number in settings text prints as JavaScript's String(number): -0 is 0, and a magnitude of 1e21 or more, or below 1e-6, gets an exponent (the fullscreen wheel scroll lines label and choices, and the invalid compaction token setting message).

BF-4027b34

A finished code-mode execution no longer keeps its closed VM reachable through its stopped halt-grace timer…

  • Source: PiG's own defect
  • Symptom: A finished code-mode execution no longer keeps its closed VM reachable through its stopped halt-grace timer, which stays in an idle scheduler's timer heap until that scheduler next runs.
  • Root cause: The end RSS moved in 64 MiB steps (the bytes each script touches), which looked like the runtime returning pages late. It is not: wazero runs here without a custom allocator, so a VM's linear memory is one Go heap slice reserved at the heap limit plus 64 MiB of headroom (320 MiB), and the live Go heap after runtime.GC() was 0 to 6 of those above the start. A closed VM was still reachable.
  • User impact: Behavior differed from Pi
  • Platform: Linux
  • Fixed in: 0.5.0
  • Fix commits: 4027b34bc8 (2026-10-13)
  • Tests: codemode/resources_linux_test.go::TestConcurrentExecutionsReturnMemoryAndDescriptors
  • Release note: A finished code-mode execution no longer keeps its closed VM reachable through its stopped halt-grace timer, which stays in an idle scheduler's timer heap until that scheduler next runs. Each such VM held its whole linear memory (the heap limit plus 64 MiB, 320 MiB at a 256 MiB limit) until then. After 16 concurrent executions on a 196-CPU Linux host, the live Go heap after a collection was 0 to 6 of those memories above the start before the fix (up to 1.9 GiB) and under 0.1 MiB after it.

BF-4058f34

The /fork message picker checks its keys in Pi's order (up, down, confirm, cancel) when bindings overlap

  • Source: PiG's own defect
  • Symptom: The /fork message picker checks its keys in Pi's order (up, down, confirm, cancel) when bindings overlap.
  • Root cause: The picker now uses NewInput with OnSubmit = selectHighlighted, shared with the confirm case.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 082355f166 (2026-10-07), 4058f34548 (2026-10-07), 56a0400b9c (2026-10-07), a7be906e71 (2026-10-07), f2a2b3409b (2026-10-07)
  • Tests: internal/codingagent/extension_dialog_remapping_test.go::TestExtensionDialogRemappingThroughOwnerLoop, internal/codingagent/remote_overlay_production_review_test.go::TestRemoteOverlayProductionRejectsCachedTerminalWidth, internal/codingagent/session_selector_oracle_test.go::TestSessionSelectorInputMatchesPi, internal/codingagent/tree_row_format_test.go::TestFormatTreeRow_AllEntryTypes, internal/codingagent/tree_selector_oracle_test.go::TestTreeSelectorInputMatchesPi, internal/codingagent/trust_selector_oracle_test.go::TestTrustSelectorInputMatchesPi, internal/codingagent/w3_pi_test.go::TestW3SessionPiComparison, tui/editor_bindings_oracle_test.go::TestEditorRebindingsMatchPi and 7 more in docs/bug-ledger.json
  • Release note: The /fork message picker checks its keys in Pi's order (up, down, confirm, cancel) when bindings overlap.

BF-40b5a89

Assistant messages in session files and RPC output list responseId before responseModel, Codex's…

  • Source: PiG's own defect
  • Symptom: Assistant messages in session files and RPC output list responseId before responseModel, Codex's endTurn before rawStopReason, and rawStopReason before errorMessage, the order in which Pi's providers create them.
  • Root cause: An earlier change says it writes "the static creation order every Pi provider produces", but it puts endTurn after rawStopReason and errorMessage. Codex is the one Pi provider that sets endTurn. It sets it on the terminal event inside mapCodexEvents (openai-codex-responses.ts:774-778), before it yields that event to processResponsesStream, whose finalizeResponse then creates rawStopReason and errorMessage (openai-responses-shared.ts:589-593). PiG wrote responseId,rawStopReason,endTurn.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 40b5a892b8 (2026-10-10), c10cde302d (2026-10-10), f29e906cb3 (2026-10-10)
  • Tests: agent/assistant_member_order_codex_oracle_test.go::TestCodexAssistantEndTurnPrecedesRawStopReason, agent/assistant_member_order_oracle_test.go::TestAssistantLateMembersFollowPiCreationOrder, ai/openai_request_key_order_oracle_test.go::TestOpenAICompletionsRequestKeysFollowPiAssignmentOrder, ai/openai_responses_request_key_order_oracle_test.go::TestOpenAIResponsesRequestKeysFollowPiAssignmentOrder, coding/cli/rpc_assistant_member_order_test.go::TestRPCAssistantMessageMembersMatchTheSessionLineOrder, coding/cli/rpc_model_type_binary_test.go::TestRPCModelObjectsCarryTheRegistryModelType
  • Release note: Assistant messages in session files and RPC output list responseId before responseModel, Codex's endTurn before rawStopReason, and rawStopReason before errorMessage, the order in which Pi's providers create them.

BF-40bc651

The Anthropic event reader decides message_start and message_stop from the parsed event type as Pi does (not…

  • Source: PiG's own defect
  • Symptom: The Anthropic event reader decides message_start and message_stop from the parsed event type as Pi does (not the SSE event name), and a JSON null event fails with the TypeError Pi reports (a CRLF split across chunks still waits for the LF, D121).
  • Root cause: Differential: 6000 seeded response bodies through Pi's iterateAnthropicEvents (the installed pi-ai dist, private generator exported by a load hook) and Pig's event reader, split at arbitrary bytes (never inside a CRLF: D121 keeps Pig's wait for the LF); events and the way each stream ends compared. 222 of 6000 differed.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 40bc651bae (2026-10-10)
  • Tests: ai/anthropic_sse_oracle_test.go::TestAnthropicEventReaderMatchesPi
  • Release note: The Anthropic event reader decides message_start and message_stop from the parsed event type as Pi does (not the SSE event name), and a JSON null event fails with the TypeError Pi reports (a CRLF split across chunks still waits for the LF, D121).

BF-410d733

settings.json, trust.json and auth.json are written as Pi's JSON.stringify writes them: <, > and…

  • Source: PiG's own defect
  • Symptom: settings.json, trust.json and auth.json are written as Pi's JSON.stringify writes them: <, > and & stay literal instead of \u0026-style escapes, so a command-backed key or a shellCommandPrefix such as source x && keeps its text.
  • Root cause: Pi writes JSON.stringify(value, null, 2) for all three (settings-manager.ts:731, trust-manager.ts:126-135, auth-storage.ts:358,467,479). encoding/json and its SDK fork escape <, > and &, so source x && and !cmd && x were persisted as \u0026, and settings.json kept the file's raw number and string forms. trust.json sorted keys by UTF-8 bytes where Array.prototype.sort compares UTF-16 units. Each file now goes through jsonstringify.Canonicalize and json.Indent.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 410d7332a8 (2026-10-06), 8d23cfcee3 (2026-10-06), a223fe3ff9 (2026-10-06), fbd3640da9 (2026-10-06)
  • Tests: ai/auth_credential_payload_test.go::TestCredentialCustomJSONRetainsUTF16Payload, ai/auth_stringify_test.go::TestAuthStorageWritesJSONStringifyText, automation/ci/portlint/checks/checks_test.go::TestAllowMarkerNeedsAReason, automation/ci/portlint/checks/checks_test.go::TestAnalyzers, automation/ci/portlint/checks/checks_test.go::TestEveryCheckIsDocumented, automation/ci/portlint/checks/testdata/src/rawsymlink/a_test.go::TestBad, automation/ci/portlint/checks/testdata/src/rawsymlink/b_unix_test.go::TestUnixOnly, automation/ci/portlint/checks/testdata/src/testisolation/a_test.go::TestBad and 14 more in docs/bug-ledger.json
  • Release note: settings.json, trust.json and auth.json are written as Pi's JSON.stringify writes them: <, > and & stay literal instead of \u0026-style escapes, so a command-backed key or a shellCommandPrefix such as source x && keeps its text. Saving a setting also rewrites numbers and strings in Pi's form, and trust.json sorts its paths by UTF-16 code units as Pi does.

BF-411a8cb

Background runs of pi-subagents (and other helpers an extension starts as a detached Node process) complete…

  • Source: PiG's own defect
  • Symptom: Background runs of pi-subagents (and other helpers an extension starts as a detached Node process) complete under PiG.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 411a8cb698 (2026-10-12)
  • Tests: test/docs-drift/commands_test.go::TestDocumentedPigCommandsAreValid
  • Release note: Background runs of pi-subagents (and other helpers an extension starts as a detached Node process) complete under PiG. The harness package now carries the host peer packages Pi's pi-coding-agent depends on, and a process with no extension runtime streams model requests through a bridge process it owns (the internal provider-bridge command), D74.

BF-4163646

AuthStorage file semantics: new live-Pi oracle TestAuthStorageFileMatchesPi…

  • Source: PiG's own defect
  • Symptom: AuthStorage file semantics: new live-Pi oracle TestAuthStorageFileMatchesPi (ai/auth_storage_file_oracle_test.go; 600 auth.json files x 1-5 operations over read, list, modify and delete: absent, empty, corrupt, byte-order-marked and non-object files, $VAR/${VAR}/!command/credential-env key resolution, OAuth and unknown types, integer-index provider order).
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), e8cc072521 (2026-10-08), 433d232815 (2026-10-09), 41636465fe (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/auth_storage_file_oracle_test.go::TestAuthStorageFileMatchesPi and 11 more in docs/bug-ledger.json
  • Release note: AuthStorage file semantics: new live-Pi oracle TestAuthStorageFileMatchesPi (ai/auth_storage_file_oracle_test.go; 600 auth.json files x 1-5 operations over read, list, modify and delete: absent, empty, corrupt, byte-order-marked and non-object files, $VAR/${VAR}/!command/credential-env key resolution, OAuth and unknown types, integer-index provider order). Real fixes for a file whose JSON is not an object (Pi keeps whatever JSON.parse returns): null made PiG treat the file as empty where Pi's read, list, modify and delete all throw (Cannot read properties of null (reading 'x') / Cannot convert undefined or null to object); an array was rejected where Pi reads and lists it as an index-keyed object, turns it into an object on modify and rewrites it as an array (deleted element null) on delete; a number or boolean was rejected where Pi reads it as empty and a delete rewrites it as itself; a null credential entry made list return an entry where Pi's credential.type throws. 10 mutations red; one equivalent (the root shape of the post-write read cache is never rewritten). Not compared: V8's JSON parse error text, string-valued files (JavaScript indexes their characters), file modes, cross-process locking.

BF-41b305d

Codemode rejects a // @options: line that is not the first line of the script with an error before the…

  • Source: PiG's own defect
  • Symptom: The line used to be ignored, so its timeout_ms never applied and the script ran without a deadline (Pi #10631).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 41b305dc69 (2026-10-12)
  • Tests: codemode/source_test.go::TestParseCodemodeSourceOnlyTreatsTheFirstLineAsAnOptionsLine, codemode/source_test.go::TestParseCodemodeSourceRejectsAnOptionsLineThatIsNotTheFirstLine, coding/codemode_session_upstream_test.go::TestUpstreamCodemodeOptionsAndStore, durable/codemode/call_test.go::TestACallLaunchesOnlyOnceItsIssuedCountIsCommitted, durable/codemode/call_test.go::TestAScriptsTimeoutLimitsItsRun, durable/codemode/call_test.go::TestCutCountsUTF16Units, durable/codemode/call_test.go::TestSameJSONIgnoresKeyOrder, durable/codemode/call_test.go::TestTheFirstRunRecordsTheVMsFingerprint and 1 more in docs/bug-ledger.json
  • Release note: Codemode rejects a // @options: line that is not the first line of the script with an error before the script runs. The line used to be ignored, so its timeout_ms never applied and the script ran without a deadline (Pi #10631).

BF-42116f8

Durable: the system baseline written after a compaction now leads the model context when the kept history…

  • Source: PiG's own defect
  • Symptom: Durable: the system baseline written after a compaction now leads the model context when the kept history includes an assistant message, as does a first baseline written after earlier answers, instead of following the new input; Anthropic and OpenAI Codex requests no longer lose the initial prompt and tool set.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 42116f895b (2026-10-14)
  • Tests: durable/harness/context_pi_oracle_test.go::TestContextDerivationMatchesPi, durable/harness/harness_compaction_test.go::TestManualCompaction, durable/harness/harness_compaction_test.go::TestOverflowCompaction, durable/harness/harness_context_test.go::TestConversationContext
  • Release note: Durable: the system baseline written after a compaction now leads the model context when the kept history includes an assistant message, as does a first baseline written after earlier answers, instead of following the new input; Anthropic and OpenAI Codex requests no longer lose the initial prompt and tool set. Other system messages after an assistant message stay in place. Carried from Pi 5713c950 (earendil-works/pi#10904)

BF-4259631

An extension's direct call into a pi-ai API module (openAICompletionsApi() and the other bridged APIs)…

  • Source: PiG's own defect
  • Symptom: An extension's direct call into a pi-ai API module (openAICompletionsApi() and the other bridged APIs) sends the model's base URL as given, as Pi's API modules do.
  • Root cause:
    • ai.WrapBuiltinProvider built a fresh provider (new http.Client and Transport) for every request of a Cloudflare or OpenCode per-model provider, so keep-alive connections were never reused and the provider's configured client was dropped. withEndpoint now keeps the provider's client and returns the provider itself when the endpoint is unchanged. Pi's cloudflareStreams (cloudflare-stream.ts:21-28) swaps only the model. TestWrapBuiltinProviderSendsThroughTheProvidersClient fails on the old code (requests left the injected client). - An extension's call into a bridged pi-ai API module (apiLeaf) was wrapped too, so the host materialized Cloudflare placeholders that Pi's API modules send as…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 24c157ae1d (2026-10-09), 42596315cb (2026-10-09), 7a0908ebbf (2026-10-09)
  • Tests: ai/cloudflare_test.go::TestCloudflareStreamsResolveTheModelForBothEntryPoints, ai/cloudflare_test.go::TestOnlyTheCloudflareProvidersWrapTheirStreams, ai/provider_env_test.go::TestResolveCloudflareModel_UsesProviderEnv, ai/provider_wrap_test.go::TestWrapBuiltinProviderSendsThroughTheProvidersClient, coding/model_api_request_test.go::TestIndependentAPIRequestSendsTheModelEndpointAsGiven, durable/document_semantics_test.go::TestConversationSemanticsFixScopeAndHistory, test/parity/interface-closure/autobind/error_rationale_test.go::TestErrorRuntimeRationaleCitesC5OnlyForModelsError
  • Release note: An extension's direct call into a pi-ai API module (openAICompletionsApi() and the other bridged APIs) sends the model's base URL as given, as Pi's API modules do. Only the Cloudflare providers' stream wrapper materializes {CLOUDFLARE_ACCOUNT_ID} and {CLOUDFLARE_GATEWAY_ID}.

BF-42c059a

fix(tui): bash block strips ANSI with Pi's utils/ansi.ts rule

  • Source: PiG's own defect
  • Symptom: fix(tui): bash block strips ANSI with Pi's utils/ansi.ts rule
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 42c059a149 (2026-10-10)
  • Tests: internal/codingagent/bash_execution_oracle_test.go::TestBashExecutionComponentMatchesPi

BF-42e3d0e

fix(hljs): a class that negates only \r still reaches across lines; gate the streamed code block Linear

  • Source: PiG's own defect
  • Symptom: fix(hljs): a class that negates only \r still reaches across lines; gate the streamed code block Linear
  • Root cause: The previous commit carries only the changelog fragment rename (a failed git add left these files out). - reach.class: a negated class counted as excluding a line break when it named \r, U+2028 or U+2029, but \r* matches "\n" without bound (JS RegExp). Only "\n" (literal, escape or \s) makes a negated class line-free now. No bundled grammar has such a class today, so resume points are unchanged. - TestScalingStreamedCodeBlockHighlight: python block streamed 24 bytes per frame, 2k -> 20k bytes: allocs slope 1.09, bytes 1.45 (bound Linear, bytes linearWithFrameCopy).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 42e3d0eb5b (2026-10-10)
  • Tests: tui/highlight_resume_scaling_norace_test.go::TestScalingStreamedCodeBlockHighlight, tui/internal/hljs/resume_test.go::TestPatternReach

BF-4312595

fix(ai,durable): keep a value that is not JSON an error when a message or a nested result is encoded

  • Source: PiG's own defect
  • Symptom: fix(ai,durable): keep a value that is not JSON an error when a message or a nested result is encoded
  • Root cause: Fix-Commit:
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 43125951f9 (2026-10-24)
  • Tests: None recorded.

BF-432ab7f

An agent assistant message that holds an empty diagnostics array writes it as [], as Pi does, instead of…

  • Source: PiG's own defect
  • Symptom: An agent assistant message that holds an empty diagnostics array writes it as [], as Pi does, instead of leaving the member out.
  • Root cause: JSON.stringify writes a present diagnostics member even when it is empty (ai types.ts:577 diagnostics?: AssistantMessageDiagnostic[]); omitempty dropped it from both assistant wire shapes. omitzero leaves out only an absent (nil) array.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 432ab7fd80 (2026-10-13)
  • Tests: agent/message_json_member_order_test.go::TestAssistantMessageJSONKeepsAnEmptyDiagnosticsArray
  • Release note: An agent assistant message that holds an empty diagnostics array writes it as [], as Pi does, instead of leaving the member out.

BF-433d232

A request sent after /tree navigation to a point between parallel tool calls and their results no longer…

  • Source: PiG's own defect
  • Symptom: A request sent after /tree navigation to a point between parallel tool calls and their results no longer fails with "unexpected tool_use_id in tool_result blocks".
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/streaming_json_resume_oracle_test.go::TestStreamingArgumentsParserMatchesPiAtEveryChunk and 1 more in docs/bug-ledger.json
  • Release note: A request sent after /tree navigation to a point between parallel tool calls and their results no longer fails with "unexpected tool_use_id in tool_result blocks". A branch summary, compaction summary, bash execution or custom message now ends the pending tool calls like any user turn, so the placeholder results come before it. A system message between a tool call and its results is now held until after the results, as in Pi.

BF-433d232-2

An extension custom message whose content is a block array (text or images), or an empty string, now reaches…

  • Source: PiG's own defect
  • Symptom: An extension custom message whose content is a block array (text or images), or an empty string, now reaches the model as a user message, as in Pi; it was dropped before.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/streaming_json_resume_oracle_test.go::TestStreamingArgumentsParserMatchesPiAtEveryChunk and 1 more in docs/bug-ledger.json
  • Release note: An extension custom message whose content is a block array (text or images), or an empty string, now reaches the model as a user message, as in Pi; it was dropped before. Branch summaries, compaction summaries, bash executions and custom messages are now sent as text-block arrays, so Anthropic Messages and OpenAI Completions requests carry the same content shape as Pi. Any of these messages ends pending tool calls like a user turn.

BF-433d232-3

DefaultResourceLoader.reload: new live-Pi oracle TestDefaultResourceLoaderReloadMatchesPi…

  • Source: PiG's own defect
  • Symptom: DefaultResourceLoader.reload: new live-Pi oracle TestDefaultResourceLoaderReloadMatchesPi (coding/resource_loader_reload_oracle_test.go; 400 random trees of user, project, ancestor .agents, home and settings-listed prompt templates, skills, themes, SYSTEM.md, APPEND_SYSTEM.md and AGENTS.md; trusted and untrusted; no-skills/prompts/themes/context switches; systemPrompt, appendSystemPrompt and additional-path options incl. missing and home-relative paths).
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e8cc072521 (2026-10-08), 433d232815 (2026-10-09), da99854d1b (2026-10-09)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/anthropic_messages_payload_oracle_test.go::TestAnthropicMessagesPayloadMatchesPi and 6 more in docs/bug-ledger.json
  • Release note: DefaultResourceLoader.reload: new live-Pi oracle TestDefaultResourceLoaderReloadMatchesPi (coding/resource_loader_reload_oracle_test.go; 400 random trees of user, project, ancestor .agents, home and settings-listed prompt templates, skills, themes, SYSTEM.md, APPEND_SYSTEM.md and AGENTS.md; trusted and untrusted; no-skills/prompts/themes/context switches; systemPrompt, appendSystemPrompt and additional-path options incl. missing and home-relative paths). Compares prompts, skills and themes (names, descriptions, contents, paths, source info), collision/validation diagnostics (including Pi's installed theme JSON validator messages), system and appended prompts with sources, and context files. No bug found. 12 mutations red (discovery trust, dedupe winner/loser, message, append/source handling, no-prompts/skills/themes/context switches, mergePaths dedupe, global discovery, missing-path message). Equivalent mutants: the || len(paths) > 0 prompt/theme gates (loading zero paths yields nothing), CanonicalizePath in mergePaths (corpus has no symlinks or case-folded duplicates), the missing-path diagnostic skip and the DefaultSourceInfoForPath fallback (every corpus path carries resolved metadata). Not compared: extensions, Packages (npm/git), symlinked duplicates.

BF-433d232-4

ReadOnlyAuthStorage: new live-Pi oracle TestReadOnlyAuthStorageMatchesPi…

  • Source: PiG's own defect
  • Symptom: ReadOnlyAuthStorage: new live-Pi oracle TestReadOnlyAuthStorageMatchesPi (ai/auth_storage_readonly_oracle_test.go; 800 auth.json files x 1-5 operations over read, list, refused modify and delete, and a file rewrite between operations that a loaded store must not see; valid and invalid credential shapes: non-string key, non-string env value, env null/array, OAuth missing access/refresh/expires or with a string expiry, null/array/scalar entries and roots).
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), e8cc072521 (2026-10-08), 433d232815 (2026-10-09), da99854d1b (2026-10-09)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/anthropic_messages_payload_oracle_test.go::TestAnthropicMessagesPayloadMatchesPi and 8 more in docs/bug-ledger.json
  • Release note: ReadOnlyAuthStorage: new live-Pi oracle TestReadOnlyAuthStorageMatchesPi (ai/auth_storage_readonly_oracle_test.go; 800 auth.json files x 1-5 operations over read, list, refused modify and delete, and a file rewrite between operations that a loaded store must not see; valid and invalid credential shapes: non-string key, non-string env value, env null/array, OAuth missing access/refresh/expires or with a string expiry, null/array/scalar entries and roots). Results and thrown messages match; no bug found. 9 mutations red (key/env/OAuth validation, command-key bypass, refused modify, null root, error text). Equivalent mutants: the finite-expiry check (Go's JSON decoder already rejects 1e999), the empty-key and API-key type guards in Read (resolveStoredCredential repeats both). Not compared: V8 text after "Failed to read auth.json: ".

BF-43a2293

Key order of non-index keys stays byte order (a Go map has no insertion order): question Q-state-order filed

  • Source: PiG's own defect
  • Symptom: Key order of non-index keys stays byte order (a Go map has no insertion order): question Q-state-order filed
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 43a22934f1 (2026-10-11)
  • Tests: ai/llama_cpp_render_oracle_test.go::TestLlamaCppRenderDocsDump, ai/llama_cpp_render_oracle_test.go::TestLlamaCppRenderDocsParity, ai/llama_cpp_render_oracle_test.go::TestLlamaCppRenderQuestionMatchesPi, internal/jsstring/stringify_test.go::TestMarshalJSONOrdersArrayIndexKeysLikeJSONStringify, test/parity/scenarios/providers-registry/07-llama-classify-render-question.toml

BF-43c46f5

Chord host timers (reload vote, gate and retire deadlines, link pings, service linger, host manager grace…

  • Source: PiG's own defect
  • Symptom: A deadline above the limit used to never fire and a zero one fired at once.
  • Root cause: Pi's chord timers are Node timers: lib/internal/timers.js Timeout runs a delay outside [1, TIMEOUT_MAX = 2^31-1] ms after 1 ms, and insert truncates the rest to whole milliseconds. The Go port handed the raw Duration to time.AfterFunc/NewTimer/NewTicker, so a deadline above TIMEOUT_MAX never fired, a zero or negative one fired at once, and fractions were kept.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 43c46f5394 (2026-10-13)
  • Tests: chord/host/node_delay_test.go::TestAwaitVoteDeadlineAboveNodeTimeoutMaxRejectsAfterOneMillisecond, chord/host/node_delay_test.go::TestNodeDelayMatchesNodeTimers
  • Release note: Chord host timers (reload vote, gate and retire deadlines, link pings, service linger, host manager grace, ready timeout and keepAlive interval) wait as Node's setTimeout does: a delay above 2147483647 ms, below 1 ms or zero runs after 1 ms, and fractional milliseconds are truncated. A deadline above the limit used to never fire and a zero one fired at once.

BF-43d2597

@ file autocomplete finds paths for queries that hold regex metacharacters such as [ or (, for a query…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: @ file autocomplete finds paths for queries that hold regex metacharacters such as [ or (, for a query with or without a / (Pi 612a172e, #10972).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 43d259770e (2026-10-21)
  • Tests: agent/tool_replay_error_test.go::TestToolReplayErrorMatchesPi, coding/cli/rpc_replacement_order_test.go::TestRPCSwitchSessionFailsWithTheSystemReplayTypeError, coding/runtime_switch_wrong_typed_system_test.go::TestSwitchSessionRejectsAWrongTypedSystemReplayLikePi, coding/session_resume_default_tools_port_test.go::TestResumeRestoresTheRecordedLoadoutPort, internal/codingagent/compaction/context_edit_test.go::TestCompactAppliesCustomInstructionsToTheTurnPrefixSummaryOfASplitTurn, test/extension-conformance/resume_tools_test.go::TestResumeActivatesExtensionToolsTheBranchNeverMentionedAcrossSDKs, tui/autocomplete_upstream_test.go::TestUpstreamAutocompleteFD, tui/file_autocomplete_test.go::TestBuildFdPathQuery
  • Release note: @ file autocomplete finds paths for queries that hold regex metacharacters such as [ or (, for a query with or without a / (Pi 612a172e, #10972).

BF-43d2597-2

/compact <instructions> applies the instructions to the summary of the start of a turn that compaction…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: /compact <instructions> applies the instructions to the summary of the start of a turn that compaction splits, and no longer ignores them when the split turn is the only new content since the last compaction (Pi adfc1446, #11058).
  • Root cause: /compact <instructions> applies the instructions to the summary of the start of a turn that compaction splits, and no longer ignores them when the split turn is the only new content since the last compaction (Pi adfc1446, #11058).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 43d259770e (2026-10-21)
  • Tests: agent/tool_replay_error_test.go::TestToolReplayErrorMatchesPi, coding/cli/rpc_replacement_order_test.go::TestRPCSwitchSessionFailsWithTheSystemReplayTypeError, coding/runtime_switch_wrong_typed_system_test.go::TestSwitchSessionRejectsAWrongTypedSystemReplayLikePi, coding/session_resume_default_tools_port_test.go::TestResumeRestoresTheRecordedLoadoutPort, internal/codingagent/compaction/context_edit_test.go::TestCompactAppliesCustomInstructionsToTheTurnPrefixSummaryOfASplitTurn, test/extension-conformance/resume_tools_test.go::TestResumeActivatesExtensionToolsTheBranchNeverMentionedAcrossSDKs, tui/autocomplete_upstream_test.go::TestUpstreamAutocompleteFD, tui/file_autocomplete_test.go::TestBuildFdPathQuery
  • Release note: /compact <instructions> applies the instructions to the summary of the start of a turn that compaction splits, and no longer ignores them when the split turn is the only new content since the last compaction (Pi adfc1446, #11058).

BF-43d2597-3

A resumed session and /tree navigation activate the extension tools the branch never added or removed, such…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: A resumed session and /tree navigation activate the extension tools the branch never added or removed, such as tools of an extension installed after the session started, unless they are registered with defaultActive: false; a tool the branch removed, also before a compaction, stays inactive.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 43d259770e (2026-10-21)
  • Tests: agent/tool_replay_error_test.go::TestToolReplayErrorMatchesPi, coding/cli/rpc_replacement_order_test.go::TestRPCSwitchSessionFailsWithTheSystemReplayTypeError, coding/runtime_switch_wrong_typed_system_test.go::TestSwitchSessionRejectsAWrongTypedSystemReplayLikePi, coding/session_resume_default_tools_port_test.go::TestResumeRestoresTheRecordedLoadoutPort, internal/codingagent/compaction/context_edit_test.go::TestCompactAppliesCustomInstructionsToTheTurnPrefixSummaryOfASplitTurn, test/extension-conformance/resume_tools_test.go::TestResumeActivatesExtensionToolsTheBranchNeverMentionedAcrossSDKs, tui/autocomplete_upstream_test.go::TestUpstreamAutocompleteFD, tui/file_autocomplete_test.go::TestBuildFdPathQuery
  • Release note: A resumed session and /tree navigation activate the extension tools the branch never added or removed, such as tools of an extension installed after the session started, unless they are registered with defaultActive: false; a tool the branch removed, also before a compaction, stays inactive. Built-in tools keep following defaultTools (Pi bd3c9b82, #11062, #11075).

BF-43d2597-4

Switching to a session whose system message holds a toolsAdded or toolsRemoved member the loadout restore…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: Switching to a session whose system message holds a toolsAdded or toolsRemoved member the loadout restore cannot read fails with the TypeError of Pi's tool-list replay; a system message with an unreadable content no longer fails the switch (Pi bd3c9b82).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 43d259770e (2026-10-21)
  • Tests: agent/tool_replay_error_test.go::TestToolReplayErrorMatchesPi, coding/cli/rpc_replacement_order_test.go::TestRPCSwitchSessionFailsWithTheSystemReplayTypeError, coding/runtime_switch_wrong_typed_system_test.go::TestSwitchSessionRejectsAWrongTypedSystemReplayLikePi, coding/session_resume_default_tools_port_test.go::TestResumeRestoresTheRecordedLoadoutPort, internal/codingagent/compaction/context_edit_test.go::TestCompactAppliesCustomInstructionsToTheTurnPrefixSummaryOfASplitTurn, test/extension-conformance/resume_tools_test.go::TestResumeActivatesExtensionToolsTheBranchNeverMentionedAcrossSDKs, tui/autocomplete_upstream_test.go::TestUpstreamAutocompleteFD, tui/file_autocomplete_test.go::TestBuildFdPathQuery
  • Release note: Switching to a session whose system message holds a toolsAdded or toolsRemoved member the loadout restore cannot read fails with the TypeError of Pi's tool-list replay; a system message with an unreadable content no longer fails the switch (Pi bd3c9b82).

BF-448d2a9

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: SendMessage returns at once and the Session is busy from the call (Pi PR #10739 _startRun); pending nextTurn messages ride with a custom run; plan-mode example skips non-user runs; conformance row TestBeforeAgentStartTriggerAcrossSDKs and TS augmentation; D140 reverted (models.md:66 intends replacement; test pins it); UTF-16 tail test for D141.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 448d2a9568 (2026-10-09)
  • Tests: coding/session_pi_bugs_test.go::TestIdleCustomMessageSendReturnsAtOnceAndTheStartingRunIsBusy, examples/extensions/plan-mode/before_agent_start_test.go::TestPlanContextIsInjectedOnlyBeforeUserRuns, internal/codingagent/models_json_replace_test.go::TestModelsJSONEntryForAnExposedModelReplacesItAndModelOverridesKeepItsFields, test/extension-conformance/before_agent_start_trigger_test.go::TestBeforeAgentStartTriggerAcrossSDKs

BF-448ff71

A panic in an extension error listener is now named on stderr instead of vanishing, and extension SDK…

  • Source: PiG's own defect
  • Symptom: A panic in an extension error listener is now named on stderr instead of vanishing, and extension SDK terminal-input handlers and overlay invalidators that panic are reported in the extension log.
  • Root cause: QUALITY-AUDIT-050 5.3: 23 production recover() sites discarded the panic value, only one of them inside the packages bare-recover scanned.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 448ff71f1f (2026-10-09)
  • Tests: automation/ci/divguard/divguard_test.go::TestBareRecoverScansEveryProductionPackage, coding/mcpext/notify_panic_internal_test.go::TestNotifyPanicPropagatesExceptOnTheLoadFailurePath, coding/session_event_panic_test.go::TestReportExtensionErrorNamesAPanickingListener, extensions/sdk/recovered_panic_reporting_test.go::TestInvalidatorPanicOnStopIsReported, extensions/sdk/recovered_panic_reporting_test.go::TestSameOverlayComponentNeverPanicsOnUncomparableValues, extensions/sdk/recovered_panic_reporting_test.go::TestTerminalInputHandlerPanicIsReportedAndYieldsNoVerdict
  • Release note: A panic in an extension error listener is now named on stderr instead of vanishing, and extension SDK terminal-input handlers and overlay invalidators that panic are reported in the extension log.

BF-448ff71-2

MCP notifications no longer swallow every panic from the notify callback; only the "MCP failed to load"…

  • Source: PiG's own defect
  • Symptom: MCP notifications no longer swallow every panic from the notify callback; only the "MCP failed to load" message ignores a throwing notify, as Pi does.
  • Root cause: QUALITY-AUDIT-050 5.3: 23 production recover() sites discarded the panic value, only one of them inside the packages bare-recover scanned.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 448ff71f1f (2026-10-09)
  • Tests: automation/ci/divguard/divguard_test.go::TestBareRecoverScansEveryProductionPackage, coding/mcpext/notify_panic_internal_test.go::TestNotifyPanicPropagatesExceptOnTheLoadFailurePath, coding/session_event_panic_test.go::TestReportExtensionErrorNamesAPanickingListener, extensions/sdk/recovered_panic_reporting_test.go::TestInvalidatorPanicOnStopIsReported, extensions/sdk/recovered_panic_reporting_test.go::TestSameOverlayComponentNeverPanicsOnUncomparableValues, extensions/sdk/recovered_panic_reporting_test.go::TestTerminalInputHandlerPanicIsReportedAndYieldsNoVerdict
  • Release note: MCP notifications no longer swallow every panic from the notify callback; only the "MCP failed to load" message ignores a throwing notify, as Pi does.

BF-44f8d86

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: The Node adoption change edits runtime.mjs, so the embedded archive and its digest are regenerated (go generate ./coding/extension/host/subprocess).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 44f8d86c29 (2026-10-20)
  • Tests: None recorded.

BF-4599fab

fix(ai): an iteration's shared cancellation does not compare contexts

  • Source: PiG's own defect
  • Symptom: fix(ai): an iteration's shared cancellation does not compare contexts
  • Root cause: An earlier change's one-registration-per-iteration path compared the iterator's recorded context with the context of each later next() using . Go panics when it compares two interface values whose identical dynamic type is not comparable, so Events(ctx) with a context struct carrying a slice or map (passed by value) panicked at its second wait: "comparing uncomparable type". Both iteration loops (events() turn path, observeEvents) pass the same captured context to every next(), so the comparison never decided anything. The registration is now armed once, keyed on stop nil, and contexts are not compared.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 4599fab32c (2026-10-12)
  • Tests: ai/event_stream_cancellation_test.go::TestIterationOnAnUncomparableContextWaitsForEachEvent, ai/event_stream_waiter_disarm_test.go::TestContinuationIteratorDisarmsDeliveredWaits

BF-45ec149

Read-only credential storage reports an unreadable or malformed auth.json as Pi does: `Failed to read…

  • Source: PiG's own defect
  • Symptom: Read-only credential storage reports an unreadable or malformed auth.json as Pi does: Failed to read auth.json: followed by Node's errno text (for example EISDIR: illegal operation on a directory, read) or the JSON.parse SyntaxError text (for example Unexpected end of JSON input), instead of Go's error text.
  • Root cause: The writable store already validates with jsonparse and maps read errors with nodeerrno; the read-only load now does the same.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 45ec14925a (2026-10-10)
  • Tests: ai/auth_storage_readonly_oracle_test.go::TestReadOnlyAuthStorageMatchesPi, ai/auth_storage_readonly_read_error_test.go::TestReadOnlyAuthStorageReportsReadAndParseErrorsWithNodeMessages
  • Release note: Read-only credential storage reports an unreadable or malformed auth.json as Pi does: Failed to read auth.json: followed by Node's errno text (for example EISDIR: illegal operation on a directory, read) or the JSON.parse SyntaxError text (for example Unexpected end of JSON input), instead of Go's error text.

BF-4610c9f

A ! or !! command's block draws the bytes Pi's BashExecutionComponent draws: its rows are padded to the…

  • Source: PiG's own defect
  • Symptom: A ! or !! command's block draws the bytes Pi's BashExecutionComponent draws: its rows are padded to the terminal width, the exit status, cancelled and truncation lines use the theme's error and warning colors instead of fixed red and yellow, the collapse hint uses the theme's key hint with the configured expand key, the spinner names the configured cancel keys (escape/ctrl+c by default) instead of Esc, no blank row follows the bottom border, the output Pi would cut for the model's context (the last 2000 lines or 50 KB) is cut the same way, and ANSI escapes and carriage returns in the output are cleaned as Pi cleans them.
  • Root cause: TestTaskAbort's abort-again case let the caller join before the run phase started, so under load the caller's cancel raced the task settling and the waiter returned nil instead of the cancel cause. Pi's scheduler joins only a run invocation already on the line (scheduler.ts:274-296); the test now waits for the run phase first.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a4753ec61e (2026-10-06, #170), ba9c37b247 (2026-10-06, #171), 165cf5884d (2026-10-07, #180), 3499c2bea7 (2026-10-07, #181), 9b63b2bc4e (2026-10-07, #176), 4610c9f4a1 (2026-10-08, #189), 50e81255de (2026-10-08, #174)
  • Tests: coding/cli/rpc_initial_tools_test.go::TestRPCFreshPromptDeclaresStructuredSystemOnce, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleBusyEntriesAreSkippedNotErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledBeforeStartTouchesNothing, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledDuringTheLastEntryReportsTheCancellation, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleEntryLeasedAfterClassificationIsSkippedNotAnError, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleOtherErrorsStayErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleStopsWhenItsContextIsCancelled, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestIsBusyErrorLooksThroughWrappedJoins and 57 more in docs/bug-ledger.json
  • Release note: A ! or !! command's block draws the bytes Pi's BashExecutionComponent draws: its rows are padded to the terminal width, the exit status, cancelled and truncation lines use the theme's error and warning colors instead of fixed red and yellow, the collapse hint uses the theme's key hint with the configured expand key, the spinner names the configured cancel keys (escape/ctrl+c by default) instead of Esc, no blank row follows the bottom border, the output Pi would cut for the model's context (the last 2000 lines or 50 KB) is cut the same way, and ANSI escapes and carriage returns in the output are cleaned as Pi cleans them.

BF-4649166

fix(sdk): place the arrival-order start in the request scope so a tool_call round trip keeps its 68…

  • Source: PiG's own defect
  • Symptom: fix(sdk): place the arrival-order start in the request scope so a tool_call round trip keeps its 68 allocations; test the emptydrop claims; keep an empty operations list
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 46491664b6 (2026-10-21)
  • Tests: coding/create_agent_session_test.go::TestResolveSDKToolSelectionEmptyExcludeToolsIsAbsent, coding/extension/host/subprocess/host_builtin_tool_test.go::TestBuiltinToolDefinitionsHaveNoEmptyPromptGuidelines, coding/extension/host/subprocess/host_builtin_tool_test.go::TestBuiltinToolOptionsKeepAnEmptyOperationsList

BF-46abed3

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 46abed3453 (2026-10-21)
  • Tests: None recorded.

BF-471c46e

An MCP tool or resource link with an explicit empty title keeps it, as Pi does, instead of falling back to…

  • Source: PiG's own defect
  • Symptom: An MCP tool or resource link with an explicit empty title keeps it, as Pi does, instead of falling back to the annotation title or the name.
  • Root cause: tools.ts: block.title ?? block.name and tool.title ?? tool.annotations?.title replace only a missing title; mcp.Tool and mcp.ContentBlock track presence (HasTitle). index.ts: hideTools and the re-registration of dropped tools iterate a Set in insertion order; serverTools is an insertion-ordered set (the dropped-tool order was a map iteration).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 471c46e063 (2026-10-05)
  • Tests: coding/mcpext/extension_test.go::TestAgentSessionMCPHidesToolsInTheOrderTheServerListedThem, coding/mcpext/tools_test.go::TestMCPToolsKeepAnExplicitEmptyTitle, mcp/client_cursor_test.go::TestToolsAndContentBlocksTrackWhetherTheServerSentATitle
  • Release note: An MCP tool or resource link with an explicit empty title keeps it, as Pi does, instead of falling back to the annotation title or the name.

BF-4789fa5

thinking_level_select and turn_start handlers are covered through the production Session: a handler sees…

  • Source: PiG's own defect
  • Symptom: thinking_level_select and turn_start handlers are covered through the production Session: a handler sees the clamped effective level only when it changes, and turn_start carries the per-run turn index and the time of the turn.
  • Root cause: Pi's handler returns an object whose exec runs the user's command (an SSH extension's remote shell). Functions cannot cross the socket, so the reply names the object by handle and the host's BashOperations.Exec sends user_bash_exec to the owner connection: onData chunks stream as tool_update notifications before the exit code, the request's cancellation is the exec's signal, a rejection fails the command, and the extension drops the object when the host's is collected (bash_operations_release). Node, Go, Python and Rust SDKs; the embedded Node runtime is regenerated.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 4789fa51f0 (2026-10-08)
  • Tests: coding/extension/host/subprocess/runtime_node_user_bash_operations_test.go::TestNodeRuntimeUserBashOperations, coding/extension/host/subprocess/user_bash_operations_test.go::TestUserBashOperationsExecAbortsWithTheContext, coding/extension/host/subprocess/user_bash_operations_test.go::TestUserBashOperationsReleaseTheExtensionHandleWhenTheHostDropsThem, coding/extension/host/subprocess/user_bash_operations_test.go::TestUserBashOperationsReplyWithResultOrWithoutHandleIsLeftRaw, coding/session_extension_events_upstream_test.go::TestSessionEmitsThinkingLevelSelectOnlyForAChangeWithTheEffectiveLevel, coding/session_extension_events_upstream_test.go::TestSessionEmitsTurnStartWithThePerRunTurnIndexAndATimestamp, extensions/sdk-py/tests/test_user_bash_operations.py, extensions/sdk/user_bash_operations_test.go::TestUserBashOperationsStayInTheExtensionUntilTheHostReleasesThem and 4 more in docs/bug-ledger.json
  • Release note: thinking_level_select and turn_start handlers are covered through the production Session: a handler sees the clamped effective level only when it changes, and turn_start carries the per-run turn index and the time of the turn.

BF-47c3007

/mcp now opens at once while servers are still connecting, and connection actions (reconnect, enable…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: /mcp now opens at once while servers are still connecting, and connection actions (reconnect, enable, disable) run in the background so the manager stays usable.
  • Root cause:
    • mcp/oauth/flow.go: an aborted refresh no longer falls back to a new authorization (flow.ts runFlow). - coding/mcpext: one session context replaces the generation counter; sign-ins stop with Esc and with the session, are tracked so shutdown waits for them, report aborted requests as McpSignInCancelledError, and each authorization-server request times out after 15 s (oauth.ts OAUTH_REQUEST_TIMEOUT_MS/timedFetch); pig mcp login --timeout bounds the whole sign-in (cli.ts). - /mcp opens before startup connections finish; reconnect, enable and disable run in the background with an attempt token; reconnects and replacements are serialized; direct tool calls wait for their own server; prepared…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 47c300724d (2026-10-07)
  • Tests: coding/mcp_oauth_session_upstream_test.go::TestAgentSessionMCPOAuthCancelsASignInWaitingOnTheAuthorizationServerWhenTheSessionShutsDown, coding/mcp_oauth_session_upstream_test.go::TestAgentSessionMCPOAuthClosesTheSessionWithoutRefreshingAnExpiringToken, coding/mcp_oauth_session_upstream_test.go::TestAgentSessionMCPOAuthGivesUpOnPigMcpLoginAfterTimeoutAlsoWhileTheAuthorizationServerHangs, coding/mcpext/extension_test.go::TestAgentSessionMCPHoldsTheFirstPromptForServersWithDirectToolsOnlyUpToTheStartupWait, coding/mcpext/manager_test.go::TestMcpManagerReconnectRunsInTheBackgroundWithoutAStatusScreen, coding/mcpext/manager_test.go::TestMcpManagerServerMenuAndDisable, coding/mcpext/manager_test.go::TestMcpManagerStatusScreenCancelsTheRunningOperationWithTheCancelKey, coding/mcpext/manager_test.go::TestMcpManagerStatusScreenIgnoresTheCancelKeyForOperationsThatCannotBeCancelled and 15 more in docs/bug-ledger.json
  • Release note: /mcp now opens at once while servers are still connecting, and connection actions (reconnect, enable, disable) run in the background so the manager stays usable. Reconnects and replacements are serialized, direct MCP tool calls wait for their own server's connection, and a call prepared earlier uses the server's current connection when it runs.

BF-47c3007-2

MCP OAuth sign-ins can now be cancelled with Esc at every step, session shutdown stops a running sign-in, and…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: MCP OAuth sign-ins can now be cancelled with Esc at every step, session shutdown stops a running sign-in, and each request to the authorization server times out after 15 seconds.
  • Root cause:
    • mcp/oauth/flow.go: an aborted refresh no longer falls back to a new authorization (flow.ts runFlow). - coding/mcpext: one session context replaces the generation counter; sign-ins stop with Esc and with the session, are tracked so shutdown waits for them, report aborted requests as McpSignInCancelledError, and each authorization-server request times out after 15 s (oauth.ts OAUTH_REQUEST_TIMEOUT_MS/timedFetch); pig mcp login --timeout bounds the whole sign-in (cli.ts). - /mcp opens before startup connections finish; reconnect, enable and disable run in the background with an attempt token; reconnects and replacements are serialized; direct tool calls wait for their own server; prepared…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 47c300724d (2026-10-07)
  • Tests: coding/mcp_oauth_session_upstream_test.go::TestAgentSessionMCPOAuthCancelsASignInWaitingOnTheAuthorizationServerWhenTheSessionShutsDown, coding/mcp_oauth_session_upstream_test.go::TestAgentSessionMCPOAuthClosesTheSessionWithoutRefreshingAnExpiringToken, coding/mcp_oauth_session_upstream_test.go::TestAgentSessionMCPOAuthGivesUpOnPigMcpLoginAfterTimeoutAlsoWhileTheAuthorizationServerHangs, coding/mcpext/manager_test.go::TestMcpManagerReconnectRunsInTheBackgroundWithoutAStatusScreen, coding/mcpext/manager_test.go::TestMcpManagerServerMenuAndDisable, coding/mcpext/manager_test.go::TestMcpManagerStatusScreenCancelsTheRunningOperationWithTheCancelKey, coding/mcpext/manager_test.go::TestMcpManagerStatusScreenIgnoresTheCancelKeyForOperationsThatCannotBeCancelled, coding/mcpext/manager_test.go::TestMcpManagerViewCutsRowsToTheWidth and 14 more in docs/bug-ledger.json
  • Release note: MCP OAuth sign-ins can now be cancelled with Esc at every step, session shutdown stops a running sign-in, and each request to the authorization server times out after 15 seconds. An aborted token refresh no longer falls back to a new authorization.

BF-47c3007-3

Shutdown no longer waits up to 15 seconds to refresh an MCP OAuth token that was about to expire, only to…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: Shutdown no longer waits up to 15 seconds to refresh an MCP OAuth token that was about to expire, only to close the server's session.
  • Root cause:
    • mcp/oauth/flow.go: an aborted refresh no longer falls back to a new authorization (flow.ts runFlow). - coding/mcpext: one session context replaces the generation counter; sign-ins stop with Esc and with the session, are tracked so shutdown waits for them, report aborted requests as McpSignInCancelledError, and each authorization-server request times out after 15 s (oauth.ts OAUTH_REQUEST_TIMEOUT_MS/timedFetch); pig mcp login --timeout bounds the whole sign-in (cli.ts). - /mcp opens before startup connections finish; reconnect, enable and disable run in the background with an attempt token; reconnects and replacements are serialized; direct tool calls wait for their own server; prepared…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 47c300724d (2026-10-07)
  • Tests: coding/mcp_oauth_session_upstream_test.go::TestAgentSessionMCPOAuthCancelsASignInWaitingOnTheAuthorizationServerWhenTheSessionShutsDown, coding/mcp_oauth_session_upstream_test.go::TestAgentSessionMCPOAuthClosesTheSessionWithoutRefreshingAnExpiringToken, coding/mcp_oauth_session_upstream_test.go::TestAgentSessionMCPOAuthGivesUpOnPigMcpLoginAfterTimeoutAlsoWhileTheAuthorizationServerHangs, coding/mcpext/manager_test.go::TestMcpManagerReconnectRunsInTheBackgroundWithoutAStatusScreen, coding/mcpext/manager_test.go::TestMcpManagerServerMenuAndDisable, coding/mcpext/manager_test.go::TestMcpManagerStatusScreenCancelsTheRunningOperationWithTheCancelKey, coding/mcpext/manager_test.go::TestMcpManagerStatusScreenIgnoresTheCancelKeyForOperationsThatCannotBeCancelled, coding/mcpext/manager_test.go::TestMcpManagerViewCutsRowsToTheWidth and 14 more in docs/bug-ledger.json
  • Release note: Shutdown no longer waits up to 15 seconds to refresh an MCP OAuth token that was about to expire, only to close the server's session. Closing a streamable HTTP session reuses the token of the last request.

BF-48318eb

fix(hljs): a resumed run counts the scan iterations before its checkpoint

  • Source: PiG's own defect
  • Symptom: fix(hljs): a resumed run counts the scan iterations before its checkpoint
  • Root cause: A resumed run started counting at 0, so a whole run could throw (Pi prints the escaped code) where the resumed run did not (PiG printed highlighted code); maxCheckpointIterations only narrowed that window. A checkpoint now records the iterations before it, ResumePoint carries them, tui's resume state keeps them, and the resumed run continues the count, so both runs see the same counter at every match. maxCheckpointIterations goes: points past it are now exact.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 48318ebda2 (2026-10-11)
  • Tests: tui/highlight_resume_test.go::TestStreamedHighlightEqualsAWholeRun, tui/internal/hljs/resume_test.go::TestHighlightResumableEqualsAFreshRun, tui/internal/hljs/resume_test.go::TestHighlightResumableFollowsLaterText, tui/internal/hljs/resume_test.go::TestResumedRunCountsTheIterationsOfAWholeRun

BF-48435cd

A parsed http, https, ws, wss or ftp URL reports its fragment percent-encoded, as Node's url.hash does

  • Source: PiG's own defect
  • Symptom: A parsed http, https, ws, wss or ftp URL reports its fragment percent-encoded, as Node's url.hash does.
  • Root cause: compareParseWithNode checked only protocol, hostname and search.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 48435cd213 (2026-10-10)
  • Tests: internal/nodeurl/url_hash_test.go::TestParseURLHashIsTheEncodedFragment
  • Release note: A parsed http, https, ws, wss or ftp URL reports its fragment percent-encoded, as Node's url.hash does.

BF-486aee5

fix(sdk): the Go SDK ends the connection on a frame it cannot decode

  • Source: PiG's own defect
  • Symptom: fix(sdk): the Go SDK ends the connection on a frame it cannot decode
  • Root cause: readLoop skipped a malformed frame (continue), so a request that frame carried never got an answer and the host waited on a lease. The Node, Python and Rust SDKs end the connection on such a frame (FrameBuffer destroy, json.loads and serde_json::from_slice errors), as does the host's reader. TestMalformedFrameEndsTheConnection was red (connection still open after 10 s). The other three SDKs' behavior is from reading their code, not a cross-SDK row.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 486aee5da1 (2026-10-10)
  • Tests: extensions/sdk/malformed_frame_test.go::TestMalformedFrameEndsTheConnection

BF-488a47a

chord/host: ProcessLock no longer fails with invalid argument on macOS and the BSDs when the temporary…

  • Source: PiG's own defect
  • Symptom: chord/host: ProcessLock no longer fails with invalid argument on macOS and the BSDs when the temporary directory is too deep for a socket path (sun_path holds 104 bytes); the lock socket then lives in a private per-user directory, /tmp/chord2-<uid> (Pi cd6b2a55).
  • Root cause: A deep TMPDIR made the lock socket path exceed 104 bytes and listen failed with EINVAL. lockSocketPath falls back to the private per-user directory /tmp/chord2-<uid> when the path does not fit, and refuses a directory that is not ours or is open to others with unavailable, as Pi's manager.ts lockSocketPath does. The new processLock case of manager.test.ts is TestProcessLockAdmitsOneHolderWhenTheTemporaryDirectoryIsTooDeepForASocketPath.
  • User impact: An operation failed or was rejected
  • Platform: macOS
  • Fixed in: 0.5.0
  • Fix commits: 488a47a47d (2026-10-13)
  • Tests: chord/host/manager_upstream_test.go::TestProcessLock, chord/host/manager_upstream_test.go::TestProcessLockAdmitsOneHolderWhenTheTemporaryDirectoryIsTooDeepForASocketPath, chord/host/manager_worker_unix_test.go::TestLockSocketPathKeepsTheSocketPathWithinSunPath
  • Release note: chord/host: ProcessLock no longer fails with invalid argument on macOS and the BSDs when the temporary directory is too deep for a socket path (sun_path holds 104 bytes); the lock socket then lives in a private per-user directory, /tmp/chord2-<uid> (Pi cd6b2a55).

BF-48a28fe

Models an extension's provider object returns keep the member order the extension wrote them in, as Pi keeps…

  • Source: PiG's own defect
  • Symptom: Models an extension's provider object returns keep the member order the extension wrote them in, as Pi keeps the returned objects, instead of being sorted by member name.
  • Root cause: decodeModels re-encoded every record through a map, which sorted its members. The stored record keeps the bytes it is given, so models an extension's provider object returned reached the catalog (and later filterModels calls) in sorted order. Keep the record as written and append the provider id only when the record has none.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 48a28fe06a (2026-10-13), 7b0f5d7da8 (2026-10-13)
  • Tests: coding/extension/host/subprocess/native_provider_models_test.go::TestNativeProviderModelsKeepTheirMemberOrder, internal/codingagent/native_provider_filter_order_test.go::TestNativeProviderFilterAllModelsSeesTheProvidersOwnModels
  • Release note: Models an extension's provider object returns keep the member order the extension wrote them in, as Pi keeps the returned objects, instead of being sorted by member name. A model without a provider gets the provider's id after its own members.

BF-495f017

A bashExecution entry records excludeFromContext: false when the caller passed false, as Pi does; an…

  • Source: PiG's own defect
  • Symptom: A bashExecution entry records excludeFromContext: false when the caller passed false, as Pi does; an RPC bash command without the member records none.
  • Root cause: messages.ts:39 declares the member optional and agent-session.ts:3889 assigns options?.excludeFromContext, so the member is present exactly when a boolean was passed: interactive ! commands write false (interactive-mode.ts:7029,7058), an RPC bash command without the member writes none (rpc-mode.ts:571,577). PiG's bool+omitempty dropped false. The field is now *bool through the entry, the Session recording API and the RPC command. Found by the bash journey (session file diff).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 495f01780d (2026-10-09), 4a0bc3d84d (2026-10-09), eb6ae90e7d (2026-10-09)
  • Tests: internal/codingagent/interactive_user_bash_footer_test.go::TestInteractiveBashRefreshesTheFooterContextUsage, internal/codingagent/output_pad_blocks_test.go::TestResumedBranchSummaryHasPiSpacingAboveItsLabel, internal/sessionentry/entry_test.go::TestBashExecutionEntryKeepsExcludeFromContextFalseAndOmitsUnset, internal/sessionentry/entry_test.go::TestBranchSummaryEntryWritesFromHookFalseInPiMemberOrder
  • Release note: A bashExecution entry records excludeFromContext: false when the caller passed false, as Pi does; an RPC bash command without the member records none. The Go fields are *bool.

BF-495f017-2

A branch-summary entry writes fromHook: false in Pi's member order, and the summary has its Spacer

  • Source: PiG's own defect
  • Symptom: A branch-summary entry writes fromHook: false in Pi's member order, and the summary has its Spacer.
  • Root cause: messages.ts:39 declares the member optional and agent-session.ts:3889 assigns options?.excludeFromContext, so the member is present exactly when a boolean was passed: interactive ! commands write false (interactive-mode.ts:7029,7058), an RPC bash command without the member writes none (rpc-mode.ts:571,577). PiG's bool+omitempty dropped false. The field is now *bool through the entry, the Session recording API and the RPC command. Found by the bash journey (session file diff).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 495f01780d (2026-10-09), 6935d5ecea (2026-10-09), eb6ae90e7d (2026-10-09)
  • Tests: internal/codingagent/interactive_compaction_notice_test.go::TestSessionCompactedStatusIsShownByInitialRenderOnly, internal/codingagent/output_pad_blocks_test.go::TestResumedBranchSummaryHasPiSpacingAboveItsLabel, internal/sessionentry/entry_test.go::TestBashExecutionEntryKeepsExcludeFromContextFalseAndOmitsUnset, internal/sessionentry/entry_test.go::TestBranchSummaryEntryWritesFromHookFalseInPiMemberOrder
  • Release note: A branch-summary entry writes fromHook: false in Pi's member order, and the summary has its Spacer. "Session compacted N time(s)" is shown only when a session is opened or its transcript is rebuilt.

BF-4a0bc3d

The footer context percentage is refreshed after a ! command is recorded, as Pi's footer asks the session…

  • Source: PiG's own defect
  • Symptom: The footer context percentage is refreshed after a ! command is recorded, as Pi's footer asks the session on every render.
  • Root cause: Pi's footer asks session.getContextUsage() on every render (footer.ts:143) and the estimate counts a bashExecution message (compaction.ts:337-340), so a recorded ! command moves the percentage at once. PiG keeps a stored estimate that only agent events and transcript rebuilds refreshed.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 4a0bc3d84d (2026-10-09)
  • Tests: internal/codingagent/interactive_user_bash_footer_test.go::TestInteractiveBashRefreshesTheFooterContextUsage
  • Release note: The footer context percentage is refreshed after a ! command is recorded, as Pi's footer asks the session on every render.

BF-4ac21c8

fix(jsloop): run a tick whose tool call already ended at once, though no wait is open

  • Source: PiG's own defect
  • Symptom: fix(jsloop): run a tick whose tool call already ended at once, though no wait is open
  • Root cause: The host sees no wait while an extension tool awaits its own timer, so a tick of an ended tool call that reached the host late was held for the next turn and written behind agent_settled. The runtime marks a call tick only while a sibling tool still runs, so the loop turns: the call runs at once, behind the callbacks held before it.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 4ac21c86c4 (2026-10-21)
  • Tests: coding/extension/host/subprocess/timer_turns_test.go::TestNodeTickCallsOfAToolRunAfterItsEndWhenTheyArriveLate, internal/jsloop/turns_test.go::TestTurnsRunALateTickAtOnceWhileNoWaitIsOpen

BF-4afdbac

ProjectTrustStore reads entries in JavaScript order: readTrustFile named whichever invalid value Go's map…

  • Source: PiG's own defect
  • Symptom: ProjectTrustStore reads entries in JavaScript order: readTrustFile named whichever invalid value Go's map iteration reached first, so a trust.json with several wrong-typed values reported a random key; Pi's Object.entries order (array-index keys, then insertion order) decides, and read now walks orderedjson keys.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 4afdbac644 (2026-10-10)
  • Tests: internal/codingagent/trust_store_multi_oracle_test.go::TestProjectTrustStoreMultiEntryFilesMatchPi
  • Release note: ProjectTrustStore reads entries in JavaScript order: readTrustFile named whichever invalid value Go's map iteration reached first, so a trust.json with several wrong-typed values reported a random key; Pi's Object.entries order (array-index keys, then insertion order) decides, and read now walks orderedjson keys. New live-Pi oracle TestProjectTrustStoreMultiEntryFilesMatchPi (internal/codingagent/trust_store_multi_oracle_test.go; 1,200 trust.json files x 1-5 get/getEntry/set/setMany operations; symlinked cwd, missing paths, astral and BMP-high keys); red before the fix (4-5 of 1,200, varying with map order); 8 mutations red (key order, null rejected, false-as-true, non-object check, UTF-16 key sort, null entry stops the ancestor walk, null delete, BOM). Extends TestProjectTrustStoreMatchesPi, which passed before the fix. Not compared: the V8 text after Failed to read trust store <path>:, file modes, cross-process locking.

BF-4bcc7da

StreamProxy merges the final tool call and reports abort and timeout as Pi does, and it no longer changes a…

  • Source: PiG's own defect
  • Symptom: StreamProxy merges the final tool call and reports abort and timeout as Pi does, and it no longer changes a delivered message when the server sends more after the terminal event.
  • Root cause: PiG compared the text after each input and notified once. - A chunk that carries the paste start marker discards any earlier paste buffer, removes the first marker and joins surrounding text into the pasted content (editor.ts:720-724); PiG processed the text before the marker as typed input and kept the old buffer. - Jump mode takes decodePrintableKey(data) before raw data (editor.ts:707); PiG inserted CSI-u letters as jump targets. After: all 700 sequences match; a 4000-sequence run with another seed also matches.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 53dccd580f (2026-10-05), e2e8d50d71 (2026-10-05), f38ab7ac87 (2026-10-05), 18fb7dd7d4 (2026-10-06), 4bcc7da691 (2026-10-06), 9c8d8439aa (2026-10-06), e2cde42910 (2026-10-06)
  • Tests: agent/new_agent_stream_error_test.go::TestNewAgentAcceptsEachStreamFunctionSource, agent/new_agent_stream_error_test.go::TestNewAgentFailsWithoutAStreamFunction, agent/new_agent_stream_error_test.go::TestSendWithoutAStreamFunctionReportsTheFailure, agent/proxy_oracle_test.go::TestStreamProxyEventAfterDoneIsNotDelivered, agent/proxy_oracle_test.go::TestStreamProxyMalformedLineAfterDoneLeavesTheResult, internal/nativeplatform/clipboard_native_roundtrip_test.go::TestNativeClipboardTextRoundTrip, tui/editor_oracle_test.go::TestEditorMatchesPiOracle, tui/main_screen_image_row_test.go::TestMainScreenEditBelowImageRowDoesNotClear and 19 more in docs/bug-ledger.json
  • Release note: StreamProxy merges the final tool call and reports abort and timeout as Pi does, and it no longer changes a delivered message when the server sends more after the terminal event. The stdin buffer drops an incomplete sequence before a bracketed paste, the editor calls onChange as Pi does, and overlays hide the cursor and request a frame as Pi does.

BF-4c45afa

fix(sdk-py): stage pig_sdk.tui with the package, which pig_sdk imports at load; keep the pi-ai patterns…

  • Source: PiG's own defect
  • Symptom: fix(sdk-py): stage pig_sdk.tui with the package, which pig_sdk imports at load; keep the pi-ai patterns gofmt-clean
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 4c45afaf73 (2026-10-21)
  • Tests: extensions/sdk-py/bundle_test.go::TestEveryPackageSourceIsBundled

BF-4c7ccf2

A classifier response that reports more than 2,147,483,647 input or output tokens keeps the reported count…

  • Source: PiG's own defect
  • Symptom: A classifier response that reports more than 2,147,483,647 input or output tokens keeps the reported count and its cost, as Pi does, instead of capping it.
  • Root cause: classifier-shared.ts tokenCount keeps the response's number, so a count past 2^31-1 (3000000000 input tokens) is that count; classifierTokenCount capped it at MaxInt32. It now caps at Number.MAX_SAFE_INTEGER, so every whole count a JS number holds exactly is kept.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 4c7ccf231e (2026-10-13)
  • Tests: ai/classifier_shared_exports_test.go::TestParseClassifierUsagePricesTheModelCatalog
  • Release note: A classifier response that reports more than 2,147,483,647 input or output tokens keeps the reported count and its cost, as Pi does, instead of capping it.

BF-4d04134

The cancel hint of extension loaders (/share, /bug) shows the key bound to tui.select.cancel instead of…

  • Source: PiG's own defect
  • Symptom: The cancel hint of extension loaders (/share, /bug) shows the key bound to tui.select.cancel instead of always escape/ctrl+c.
  • Root cause: (1) tui.input.submit (ctrl+s here) must select through the search Input's onSubmit -> selectList.handleInput("\r"); Pig's host-owned TextInput only marked itself done. (2) Pig re-fired onSelect/onCancel on every later navigation key because it read the list's sticky Done state; Pi fires once per confirm/cancel key (new FilterableList.Reset, called by the selector after it dispatches). Scenario settings/06 (/thinking, m Down Left d -> '> dm') is red on the old selector ('> md') and byte-equal now.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d1f258f335 (2026-10-06), 4d041349a7 (2026-10-07), ccaade27af (2026-10-07)
  • Tests: internal/codingagent/thinking_selector_oracle_test.go::TestThinkingSelectorInputMatchesPi, test/parity/scenarios/settings/06-thinking-selector-search-editing.toml, test/parity/scenarios/settings/08-settings-search-editing.toml, tui/bordered_loader_oracle_test.go::TestBorderedLoaderMatchesPi, tui/cancellable_loader_input_test.go::TestCancellableLoaderCancelFollowsTheBindingAndCallsOnAbortEachPress, tui/cancellable_loader_input_test.go::TestCancellableLoaderCancelWithoutOnAbortOnlyAborts, tui/settings_list_input_test.go::TestSettingsListAnyDelegatedKeyResetsTheSelectionToTheFirstMatch, tui/settings_list_input_test.go::TestSettingsListBoundActionsRunInUpstreamOrder and 5 more in docs/bug-ledger.json
  • Release note: The cancel hint of extension loaders (/share, /bug) shows the key bound to tui.select.cancel instead of always escape/ctrl+c.

BF-4d33f78

fix(tui): a tool error result collapses like any other (Pi updateResult)

  • Source: PiG's own defect
  • Symptom: fix(tui): a tool error result collapses like any other (Pi updateResult)
  • Root cause: tool-execution.ts updateResult never changes the expansion; bash/read/write renderers show a collapsed preview for an error as for a success. Found by e2e runs of scripted replies through real Pi and Pig (TUI panes and session files compared). TestErrorResultStaysCollapsedUntilExpanded is red without the change. NOTE: TestRefreshModelRuntimeHonoursNetworkForceErrorsAndRegistrationCover fails ('TempDir RemoveAll cleanup: directory not empty') at -count>1 and in full-package runs on the integrate base, unrelated.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 4d33f78449 (2026-10-11)
  • Tests: internal/codingagent/model_registry_availability_refresh_gaps_test.go::TestRefreshModelRuntimeHonoursNetworkForceErrorsAndRegistrationCover, tui/tool_execution_test.go::TestErrorResultStaysCollapsedUntilExpanded

BF-4d965e3

The /tree label of a custom tool call shows its arguments in the order the model sent them, with <, >…

  • Source: PiG's own defect
  • Symptom: It used to sort the members and could cut a multi-byte character.
  • Root cause: tree-selector.ts:996-999 writes JSON.stringify(args).slice(0, 40) and "..." when the whole text is longer. Pig encoded the decoded map, which sorted the members, and cut at 40 bytes, which could split a multi-byte character into invalid UTF-8. The label now uses ToolCall.ArgumentsJSON (the model's member order), jsonstringify.Canonicalize and UTF-16 length and slicing. The truncation case asserts the exact label instead of only its byte length. Regression: TestFormatToolCall_CustomToolMatchesJSONStringify (red before).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 4d965e37c0 (2026-10-06)
  • Tests: internal/codingagent/tree_row_format_test.go::TestFormatToolCall_CustomToolMatchesJSONStringify, internal/codingagent/tree_row_format_test.go::TestFormatToolCall_PerToolFormatters, internal/codingagent/tree_row_format_test.go::TestFormatTreeRow_AllEntryTypes
  • Release note: The /tree label of a custom tool call shows its arguments in the order the model sent them, with <, > and & literal, and truncates at 40 UTF-16 units as Pi does. It used to sort the members and could cut a multi-byte character.

BF-4e204f4

fix(sdk-py): clear bandit's findings in the 0.5.2 Python SDK code

  • Source: PiG's own defect
  • Symptom: fix(sdk-py): clear bandit's findings in the 0.5.2 Python SDK code
  • Root cause: BuiltinTool.execute checked its spec with assert, which python -O strips; it now raises RuntimeError instead.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 4e204f4f23 (2026-10-22)
  • Tests: None recorded.

BF-4e47260

A lazy line joins a list item as written (indentation kept), four spaces after the marker make the first line…

  • Source: PiG's own defect
  • Symptom: A lazy line joins a list item as written (indentation kept), four spaces after the marker make the first line indented code that no lazy line continues, the break test uses the content indent to limit rules/headings/fences, a blank-after-slice line no longer ends the item, a lazy line inside an item's fence stays in the fence, and a LaTeX block that takes the end of the text leaves no trailing space row.
  • Root cause: TestParseMatchesTheLibraryOnMutatedCorpus and TestLexerMatchesTheLibraryOnMutatedCorpus compare eemeli/yaml 2.9.0 with Parse/lex on 6000 mutated corpus texts (40 differed before, 15-17 per 20000 after the first fixes). Fixed: an unterminated quote (buffer.substring(0, -1) is empty: the error is at the end of input, not after the first line); a line starting with a non-ASCII character reduced the continuation indent because the lexer looked at the second byte, not the second UTF-16 unit (charAt(1)); Invalid escape sequence raw text and the 1024-unit implicit key limit counted bytes; anchors reject JavaScript whitespace (RE2 \s is narrower); -0 is 0 as an omap/set key; Buffer.from(.., base64)…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 125d9e37a8 (2026-10-09), 2b3403045b (2026-10-09), 2a919a4c3c (2026-10-10), 4e472601e7 (2026-10-10), 4e5b684ecd (2026-10-10), 5b4a3d393e (2026-10-10), ac0dc3bee4 (2026-10-10), b5b7b43f82 (2026-10-10), cdf0be76e4 (2026-10-10), ffcd800f73 (2026-10-10)
  • Tests: ai/anthropic_setup_oracle_test.go::TestAnthropicMessagesAPISetupMatchesPi, internal/codingagent/session_file_bytes_oracle_test.go::TestSessionFileBytesMatchPi, internal/yaml12/parse_mutation_test.go::TestLexerMatchesTheLibraryOnMutatedCorpus, internal/yaml12/parse_mutation_test.go::TestParseMatchesTheLibraryOnMutatedCorpus, internal/yaml12/parse_mutation_test.go::TestParseMatchesTheLibraryOnUTF16Offsets, internal/yaml12/parse_test.go::TestParseMatchesTheLibrary, tui/markdown_render_oracle_test.go::TestMarkdownRenderMatchesPi
  • Release note: A lazy line joins a list item as written (indentation kept), four spaces after the marker make the first line indented code that no lazy line continues, the break test uses the content indent to limit rules/headings/fences, a blank-after-slice line no longer ends the item, a lazy line inside an item's fence stays in the fence, and a LaTeX block that takes the end of the text leaves no trailing space row.

BF-4e5b684

A list item whose first line is another list item is rendered as a nested list with no bullet of its own, and…

  • Source: PiG's own defect
  • Symptom: A list item whose first line is another list item is rendered as a nested list with no bullet of its own, and an indented line that lazily continues a quoted paragraph loses up to four leading spaces, as in Pi.
  • Root cause: marked lexes a list item's text as blocks, so '- + a' is a list in a list item and Pi's renderList prints no bullet for an item whose first token is a list (markdown.ts renderList); the blockquote tokenizer removes up to four spaces (codeRemoveIndent) from an indented lazy line it joins to the paragraph. 9 new rows in TestMarkdownRenderMatchesPi (9 red before).
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 4e5b684ecd (2026-10-10), cdf0be76e4 (2026-10-10)
  • Tests: tui/markdown_render_oracle_test.go::TestMarkdownRenderMatchesPi
  • Release note: A list item whose first line is another list item is rendered as a nested list with no bullet of its own, and an indented line that lazily continues a quoted paragraph loses up to four leading spaces, as in Pi.

BF-4e8c517

fix(runtimecell): keep the Python runner's _run callable without the built event

  • Source: PiG's own defect
  • Symptom: fix(runtimecell): keep the Python runner's _run callable without the built event
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 4e8c517e40 (2026-10-21)
  • Tests: None recorded.

BF-4f079a7

An MCP request over Streamable HTTP that is cancelled or times out while the server's authorization is being…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: An MCP request over Streamable HTTP that is cancelled or times out while the server's authorization is being refreshed is no longer sent again after the refresh; other requests waiting on the same refresh still complete.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 4f079a786c (2026-10-12)
  • Tests: mcp/streamable_http_send_signal_test.go::TestStreamableHTTPTransportDoesNotRetryRequestsThatWereCancelledWhileAuthorizationWasRecovering
  • Release note: An MCP request over Streamable HTTP that is cancelled or times out while the server's authorization is being refreshed is no longer sent again after the refresh; other requests waiting on the same refresh still complete.

BF-4f6992d

Durable: a tool's PrepareArguments result of a typed object (a struct, or a map of another element type)…

  • Source: PiG's own defect
  • Symptom: Durable: a tool's PrepareArguments result of a typed object (a struct, or a map of another element type) reaches validation and execution as that object; it reached them as no arguments.
  • Root cause: Pi harness/tool.ts:138-145 prepare returns { args: tool.prepareArguments(args) as JsonObject } without checking the value, and validate (tool.ts:148-154) hands it to validateToolArguments, whose error text ends with Received arguments:\n${JSON.stringify(toolCall.arguments, null, 2)} (ai/src/utils/validation.ts:347).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 19e834a869 (2026-10-11), 4f6992d663 (2026-10-11)
  • Tests: durable/core/driver/tool_intent_pi_test.go::TestCoreToolRecordsItsPreparedArgumentsAndDefaultReplayBeforeExecuting, durable/core/driver/tool_intent_pi_test.go::TestCoreToolRefusesANonObjectPreparedResultAsInvalidArguments, durable/core/turn/generation_test.go::TestToolBlockedAndInvalid
  • Release note: Durable: a tool's PrepareArguments result of a typed object (a struct, or a map of another element type) reaches validation and execution as that object; it reached them as no arguments.

BF-4f6992d-2

Durable: a PrepareArguments result that is not an object fails validation as an invalid_arguments tool…

  • Source: PiG's own defect
  • Symptom: Durable: a PrepareArguments result that is not an object fails validation as an invalid_arguments tool error whose Received arguments shows the returned value, as Pi does; it panicked the core.
  • Root cause: Pi harness/tool.ts:138-145 prepare returns { args: tool.prepareArguments(args) as JsonObject } without checking the value, and validate (tool.ts:148-154) hands it to validateToolArguments, whose error text ends with Received arguments:\n${JSON.stringify(toolCall.arguments, null, 2)} (ai/src/utils/validation.ts:347).
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 19e834a869 (2026-10-11), 4f6992d663 (2026-10-11)
  • Tests: durable/core/driver/tool_intent_pi_test.go::TestCoreToolRecordsItsPreparedArgumentsAndDefaultReplayBeforeExecuting, durable/core/driver/tool_intent_pi_test.go::TestCoreToolRefusesANonObjectPreparedResultAsInvalidArguments, durable/core/turn/generation_test.go::TestToolBlockedAndInvalid
  • Release note: Durable: a PrepareArguments result that is not an object fails validation as an invalid_arguments tool error whose Received arguments shows the returned value, as Pi does; it panicked the core.

BF-4fab2ef

Durable error values (FileError, ExecutionError, JsonlCorruptionError, ReadAfterWrite, StorageRejected…

  • Source: PiG's own defect
  • Symptom: Durable error values (FileError, ExecutionError, JsonlCorruptionError, ReadAfterWrite, StorageRejected, ConversationBusy) report Pi's name and a V8-style stack when built by their constructors; the JSONL corruption constructor is now exported as NewJsonlCorruptionError.
  • Root cause: FileError, ExecutionError (env), JsonlCorruptionError (jsonl), ReadAfterWrite, StorageRejected and ConversationBusy now have Name() and a Stack captured by their constructors, headed '<name>: <message>' as the JSONL poisoned error already does (precedent). newCorruption is exported as NewJsonlCorruptionError (Pi's exported class constructor; 27 internal call sites renamed), closing its no-go-symbol row. Tests: TestJsonlCorruptionErrorKeepsItsMessageCauseNameAndStack, TestDurableErrorsHaveNameAndStack, TestFileAndExecutionErrorsHaveNameAndStack (names, messages, unwrap, stack header and call site; removing a Stack assignment or Name fails them).
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 4fab2efaa0 (2026-10-07)
  • Tests: None recorded.
  • Release note: Durable error values (FileError, ExecutionError, JsonlCorruptionError, ReadAfterWrite, StorageRejected, ConversationBusy) report Pi's name and a V8-style stack when built by their constructors; the JSONL corruption constructor is now exported as NewJsonlCorruptionError.

BF-4fc8c2e

With an active Piglet, an extension that denies every tool with SetActiveTools([]) is no longer widened…

  • Source: PiG's own defect
  • Symptom: With an active Piglet, an extension that denies every tool with SetActiveTools([]) is no longer widened back to the Piglet's full tool scope.
  • Root cause: …ts scope
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 4fc8c2e65f (2026-10-06, #169)
  • Tests: coding/extension/host/subprocess/state_presence_test.go::TestNodeActiveToolsNarrowedToNoneSurvivesTheWire, coding/extension/host/subprocess/state_test.go::TestActiveToolsNarrowedToNoneIsAnEmptyListOnTheWire, coding/extension/host/subprocess/state_test.go::TestSetActiveToolsEmptyListReachesTheHostAsEmpty, coding/piglet/empty_active_scope_test.go::TestPigletScopeNeverWidensWhatAnExtensionNarrowed, coding/session_prompt_defaults_test.go::TestActiveToolNamesAfterDeactivatingAllToolsIsNonNilEmpty, extensions/sdk/getter_state_test.go::TestContextGettersDistinguishAbsentEmptyAndFailure
  • Release note: With an active Piglet, an extension that denies every tool with SetActiveTools([]) is no longer widened back to the Piglet's full tool scope. The Piglet scope only narrows what an earlier extension selected. getActiveTools replies and the replicated state carry [] for a session narrowed to no tools, never null.

BF-4fe779e

A /reload that arrived while the supervisor was restarting a crashed isolated extension could end the…

  • Source: PiG's own defect
  • Symptom: A /reload that arrived while the supervisor was restarting a crashed isolated extension could end the process the reloaded extension runs in, so its next call failed with a reset or closed connection.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 4fe779e53e (2026-10-20, #236)
  • Tests: coding/extension/host/subprocess/retained_factory_test.go::TestNodeIsolatedCrashAfterRetainedReloadRestarts, coding/extension/host/subprocess/retained_factory_test.go::TestNodeIsolatedReloadWaitsForCrashRestart
  • Release note: A /reload that arrived while the supervisor was restarting a crashed isolated extension could end the process the reloaded extension runs in, so its next call failed with a reset or closed connection. The restart now runs under the same lock as load and reload: a reload waits for the restart to register, then retains the restarted process; a restart that a reload or load already replaced does nothing.

BF-50233fd

An env SSH command that runs past its timeout now receives SIGTERM, as in Pi, instead of SIGKILL, and the…

  • Source: PiG's own defect
  • Symptom: An env SSH command that runs past its timeout now receives SIGTERM, as in Pi, instead of SIGKILL, and the operation fails as soon as the timeout expires, even when a process started by ssh keeps its output open.
  • Root cause: Pi's runSsh timer (ssh.ts:136-146) calls child.kill(), Node's SIGTERM, and rejects at once with the stderr read so far. PiG's runSsh killed the child with SIGKILL through exec.CommandContext and returned only after cmd.Run finished, up to WaitDelay (5 s) later when a descendant kept stdout or stderr open. runSsh now sets Cmd.Cancel to send SIGTERM (a plain kill on Windows, as Node does), waits on a goroutine and returns when the timer fires; the goroutine reaps the child, still bounded by WaitDelay, and stderr is collected in a locked buffer.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d4e4a82ed9 (2026-10-08), 50233fd0d4 (2026-10-10)
  • Tests: coding/cli/session_replacement_interactive_test.go::TestInteractiveExtensionNewSessionKeepsTheCallerAliveUntilItReturns, coding/cli/session_resume_ui_test.go::TestInteractiveResumeIntoAnUntrustedProjectPromptsForTrust, coding/cli/session_resume_ui_test.go::TestInteractiveResumeWithAMissingCWDShowsPisStatuses, env/ssh_known_hosts_semantics_test.go::TestConcurrentHostKeyChangesToOneFileAreAppliedOneAtATime, env/ssh_timeout_test.go::TestRunSshTimeoutTerminatesTheChildAndFailsWithoutWaitingForItsOutputToClose, internal/codingagent/interactive_runtime_host_test.go::TestNewInteractiveModeRoutesSessionWorkThroughItsRuntimeHost, internal/codingagent/interactive_runtime_host_test.go::TestNewInteractiveModeWithoutRuntimeHostCannotImport
  • Release note: An env SSH command that runs past its timeout now receives SIGTERM, as in Pi, instead of SIGKILL, and the operation fails as soon as the timeout expires, even when a process started by ssh keeps its output open.

BF-504869b

The MCP client uses JavaScript's trim() and \s where Pi does: in stdio and Streamable HTTP blank-line and…

  • Source: PiG's own defect
  • Symptom: The MCP client uses JavaScript's trim() and \s where Pi does: in stdio and Streamable HTTP blank-line and blank-event checks, HTTP error bodies, the insufficient_scope challenge check and the offline_access consent check.
  • Root cause: streamable-http.ts:163 matches /(?:^|[\s,])error="?insufficient_scope"?/i and flow.ts:197 asks for consent when scope.split(/\s+/) includes offline_access. JavaScript's \s includes U+00A0, U+FEFF and the Unicode spaces and excludes U+0085; RE2's \s is ASCII only and strings.Fields splits at U+0085 and not at U+FEFF. A 403 whose challenge separated the field with a no-break space was not treated as step-up authorization, and a scope joined with a BOM or ending in U+0085 got the wrong prompt. The regex now uses the JS class (as mcp/oauth/discovery.go already does) and StartAuthorization uses scopeFields (as flow.go:553 already does).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 504869b911 (2026-10-09), 97493667e3 (2026-10-09), abf52e2a35 (2026-10-09), c0020ece56 (2026-10-09), ceadbf354e (2026-10-09)
  • Tests: coding/mcpext/oauth_challenge_latin1_test.go::TestMcpAuthProviderParsesTheChallengeHeaderAsLatin1, coding/mcpext/oauth_expire_at_test.go::TestMCPOAuthCredentialStoreSavesATokenExpiryThatOverflowsAsNull, durable/env/node/find_bash_trim_test.go::TestFindBashOnPathTrimsTheAnswerLikeJavaScript, durable/harness/js_trim_test.go::TestSummaryTextTrimsLikeJavaScript, env/js_trim_test.go::TestAcceptHostKeyStoresTheLineTrimmedLikeJavaScript, env/js_trim_test.go::TestCheckDaemonTrimsTheAnswerLikeJavaScript, env/js_trim_test.go::TestNonBlankLinesTrimsLikeJavaScript, env/js_trim_test.go::TestParseProbeReportsTheAnswerTrimmedLikeJavaScript and 16 more in docs/bug-ledger.json
  • Release note: The MCP client uses JavaScript's trim() and \s where Pi does: in stdio and Streamable HTTP blank-line and blank-event checks, HTTP error bodies, the insufficient_scope challenge check and the offline_access consent check. The env SSH helpers and the durable compaction summary also trim as Pi does. A byte-order mark counts as whitespace and U+0085 does not.

BF-504869b-2

MCP response headers are read as Node's fetch returns them, one Latin-1 character per byte

  • Source: PiG's own defect
  • Symptom: MCP response headers are read as Node's fetch returns them, one Latin-1 character per byte.
  • Root cause:
    • mcp/oauth: an expires_in that overflows Date.now() + expires_in * 1000 to Infinity made json.Marshal fail after TokensExpireAt became *float64, so SaveTokens on the credential store failed the sign-in. Pi's JSON.stringify writes null and reads it back as expired; structuredClone keeps Infinity in memory. McpOAuthState now writes null, reads null as 0 and Clone keeps the float. The insufficient_scope test expected a UTF-8 BOM to separate the field, which Pi does not do. jsstring.FromLatin1 now decodes Content-Type and WWW-Authenticate at every reader (needsAuthorization, responseContentType, McpAuthRequiredError, both OnUnauthorized). - mcp: a number id keys Maps by SameValueZero and…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 504869b911 (2026-10-09), abf52e2a35 (2026-10-09)
  • Tests: coding/mcpext/oauth_challenge_latin1_test.go::TestMcpAuthProviderParsesTheChallengeHeaderAsLatin1, coding/mcpext/oauth_expire_at_test.go::TestMCPOAuthCredentialStoreSavesATokenExpiryThatOverflowsAsNull, durable/env/node/find_bash_trim_test.go::TestFindBashOnPathTrimsTheAnswerLikeJavaScript, env/js_trim_test.go::TestAcceptHostKeyStoresTheLineTrimmedLikeJavaScript, env/js_trim_test.go::TestCheckDaemonTrimsTheAnswerLikeJavaScript, env/js_trim_test.go::TestParseProbeReportsTheAnswerTrimmedLikeJavaScript, env/js_trim_test.go::TestSshErrorMessageTrimsTheDiagnosticsLikeJavaScript, internal/jsstring/latin1_test.go::TestFromLatin1DecodesEachByteAsOneCodePoint and 9 more in docs/bug-ledger.json
  • Release note: MCP response headers are read as Node's fetch returns them, one Latin-1 character per byte. This applies to Content-Type, WWW-Authenticate and McpAuthRequiredError.WWWAuthenticate.

BF-504869b-3

A JSON-RPC number id is a JavaScript number

  • Source: PiG's own defect
  • Symptom: A JSON-RPC number id is a JavaScript number.
  • Root cause:
    • mcp/oauth: an expires_in that overflows Date.now() + expires_in * 1000 to Infinity made json.Marshal fail after TokensExpireAt became *float64, so SaveTokens on the credential store failed the sign-in. Pi's JSON.stringify writes null and reads it back as expired; structuredClone keeps Infinity in memory. McpOAuthState now writes null, reads null as 0 and Clone keeps the float. The insufficient_scope test expected a UTF-8 BOM to separate the field, which Pi does not do. jsstring.FromLatin1 now decodes Content-Type and WWW-Authenticate at every reader (needsAuthorization, responseContentType, McpAuthRequiredError, both OnUnauthorized). - mcp: a number id keys Maps by SameValueZero and…
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 504869b911 (2026-10-09), c0020ece56 (2026-10-09)
  • Tests: coding/mcpext/oauth_challenge_latin1_test.go::TestMcpAuthProviderParsesTheChallengeHeaderAsLatin1, coding/mcpext/oauth_expire_at_test.go::TestMCPOAuthCredentialStoreSavesATokenExpiryThatOverflowsAsNull, durable/env/node/find_bash_trim_test.go::TestFindBashOnPathTrimsTheAnswerLikeJavaScript, env/js_trim_test.go::TestAcceptHostKeyStoresTheLineTrimmedLikeJavaScript, env/js_trim_test.go::TestCheckDaemonTrimsTheAnswerLikeJavaScript, env/js_trim_test.go::TestParseProbeReportsTheAnswerTrimmedLikeJavaScript, env/js_trim_test.go::TestSshErrorMessageTrimsTheDiagnosticsLikeJavaScript, internal/jsstring/latin1_test.go::TestFromLatin1DecodesEachByteAsOneCodePoint and 9 more in docs/bug-ledger.json
  • Release note: A JSON-RPC number id is a JavaScript number. The unknown-response message renders the id as String(id) does, and the id is written as JSON.stringify writes it. A request with id -0 is cancelled by requestId: 0.

BF-50f526d

The Go, Rust and Python SDKs export Pi's CURRENT_SESSION_VERSION, VIRTUAL_MODEL_STATE_ENTRY…

  • Source: PiG's own defect
  • Symptom: The Go, Rust and Python SDKs export Pi's CURRENT_SESSION_VERSION, VIRTUAL_MODEL_STATE_ENTRY, getLatestCompactionEntry, parseSkillBlock and formatSkillsForPrompt.
  • Root cause: Pi's index.ts exports CURRENT_SESSION_VERSION, VIRTUAL_MODEL_STATE_ENTRY, getLatestCompactionEntry, parseSkillBlock and formatSkillsForPrompt; only the Node SDK, which runs Pi's bundle, had them. Each SDK now has them: parseSkillBlock follows Pi's lazy pattern without a regex dependency (the content ends at the first closing tag that leaves nothing or two newlines and one more character) and trims the user message as JavaScript's trim does (U+FEFF is white space, U+0085 is not); formatSkillsForPrompt escapes the five XML characters and names the loading tool as Pi does.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 50f526d69e (2026-10-11)
  • Tests: test/extension-conformance/session_skill_helpers_test.go::TestConformance_SessionAndSkillHelpersAgreeAcrossSDKs
  • Release note: The Go, Rust and Python SDKs export Pi's CURRENT_SESSION_VERSION, VIRTUAL_MODEL_STATE_ENTRY, getLatestCompactionEntry, parseSkillBlock and formatSkillsForPrompt.

BF-5144418

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 5144418192 (2026-10-21)
  • Tests: None recorded.

BF-5260e5c

The Rust SDK's get_model_info() reads Pi's maxTokens and cost (it reported 0 for the output limit and…

  • Source: PiG's own defect
  • Symptom: The Rust SDK's get_model_info() reads Pi's maxTokens and cost (it reported 0 for the output limit and every price) and returns an error for a field of the wrong type, as the Go SDK's ModelInfo does.
  • Root cause: The host answers getModelInfo with Pi's Model: maxTokens is the output limit and cost holds the per-million-token prices (packages/ai/src/types.ts). The Go SDK's ModelInfo reads those, and the older flat keys, modelId, displayName and an object provider. The Rust get_model_info read only the flat keys with unwrap_or(0), so against a Pi model it reported 0 for the output limit and for all four prices: a plausible constant, not an error.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5260e5c15f (2026-10-11)
  • Tests: extensions/sdk/model_info_decode_test.go::TestModelInfoDecodeMatchesTheRustSDK, test/extension-conformance/typed_model_info_test.go::TestConformance_TypedModelInfoAgreesBetweenGoAndRust
  • Release note: The Rust SDK's get_model_info() reads Pi's maxTokens and cost (it reported 0 for the output limit and every price) and returns an error for a field of the wrong type, as the Go SDK's ModelInfo does.

BF-52858aa

setWorkingMessage with no message restores the default working label and an empty string shows an empty…

  • Source: PiG's own defect
  • Symptom: Both used to show the default.
  • Root cause: Pi's setHiddenThinkingLabel(label?: string) shows label ?? defaultHiddenThinkingLabel (interactive-mode.ts:2383), as setWorkingMessage does. extension.HiddenThinkingLabelClearer {ClearHiddenThinkingLabel()} is the optional absent case (the bridge falls back to SetHiddenThinkingLabel("") for a UIContext without it; ExtUIContext and the in-process runner wrapper implement it); the interactive mode keeps *string (an empty label is drawn empty); Go ClearHiddenThinkingLabel, Rust clear_hidden_thinking_label, Python set_hidden_thinking_label(None); Node already omits undefined. Two existing tests asserted the old 'an empty label restores the default' and now assert the empty label and the clear.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e8cc072521 (2026-10-08), 682f5d7578 (2026-10-09), a5d58095b0 (2026-10-09), d2b0ada190 (2026-10-09), da99854d1b (2026-10-09), 45254d4a39 (2026-10-10), 52858aa12e (2026-10-10), 5f93ef7f8c (2026-10-10)
  • Tests: chord/delta/own_key_order_test.go::TestDiffRevisionsVisitsArrayIndexKeysFirstInNumericOrder, coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, coding/extension/host/inproc/ui_working_message_test.go::TestUIPrompt_HiddenThinkingLabelClearerReachesTheBoundUI, coding/extension/host/subprocess/ui_bridge_test.go::TestUIBridge_HandleCall_SetHiddenThinkingLabelDistinguishesAbsentFromEmpty, coding/extension/host/subprocess/ui_bridge_test.go::TestUIBridge_HandleCall_SetWorkingMessageDistinguishesAbsentFromEmpty, coding/extension/host/subprocess/ui_bridge_test.go::TestUIBridge_HandleCall_SetWorkingMessageWithoutAClearerFallsBackToEmpty, coding/extension/ui_test.go::TestNoopUIContext_AllMethodsReturnUpstreamDefaults, extensions/sdk/truncate_test.go::TestFormatSizeRoundsHalfUpLikeToFixed and 15 more in docs/bug-ledger.json
  • Release note: setWorkingMessage with no message restores the default working label and an empty string shows an empty label, as in Pi (message ?? defaultWorkingMessage). Both used to show the default. The Go, Rust and Python extension SDKs gain ClearWorkingMessage(), clear_working_message() and set_working_message(None) for the no-argument form; SetWorkingMessage(message) still takes a string. A UIContext can implement the new extension.WorkingMessageClearer interface to tell the two apart; one that does not keeps the old behavior, so existing implementations still compile. setHiddenThinkingLabel follows the same rule (ClearHiddenThinkingLabel(), clear_hidden_thinking_label(), set_hidden_thinking_label(None); extension.HiddenThinkingLabelClearer). A Session replacement now also resets the working message, visibility and indicator an extension set, as Pi does.

BF-53385a3

Registering a large MCP catalog, building a CodeMode sandbox from one, and CodeMode tool discovery were…

  • Source: PiG's own defect
  • Symptom: Registering a large MCP catalog, building a CodeMode sandbox from one, and CodeMode tool discovery were quadratic in the number of tools, so a 100,000-tool server took tens of seconds to register after its last tools/list page, NewSandbox took 27 seconds, and every searchTools() or describeNamespace() call rescanned the whole host catalog once per tool.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 53385a3d93 (2026-10-21, #268)
  • Tests: codemode/sandbox_scaling_test.go::TestSandboxConstructionKeepsOrderAndRejectsDuplicates, coding/extension/builtin/codemode/run_test.go::TestDiscoveryDoesNotKeepReadingTheCatalogAfterCancellation, coding/extension/builtin/codemode/run_test.go::TestDiscoveryReadsTheCatalogOnceAndKeepsTheFirstEntryOfAName, coding/extension/builtin/codemode/run_test.go::TestDiscoveryStopsWhenItsContextIsCancelled, coding/mcpext/extension_test.go::TestAgentSessionMCPNamesCollidingToolsAcrossTheWholeCatalog
  • Release note: Registering a large MCP catalog, building a CodeMode sandbox from one, and CodeMode tool discovery were quadratic in the number of tools, so a 100,000-tool server took tens of seconds to register after its last tools/list page, NewSandbox took 27 seconds, and every searchTools() or describeNamespace() call rescanned the whole host catalog once per tool. Tool names are now assigned from a per-server count of the sanitized names, the sandbox keeps a name index beside its tool list, and a discovery call reads the host catalog once and resolves each tool's namespace by lookup. Names, collision suffixes, registration order, duplicate-tool errors and the first-entry-wins namespace of a repeated name are unchanged. Discovery also now stops when its context is cancelled and returns the context's error; before, it kept scanning. Pi resolves the same way for the sandbox (a Map by name) but scans per tool in the MCP extension and in codemode discovery; PiG's results match Pi's.

BF-5380e0a

fix(extension): load failure shows the extension's own cause; registration edge-case rows

  • Source: PiG's own defect
  • Symptom: fix(extension): load failure shows the extension's own cause; registration edge-case rows
  • Root cause: A failed Go/Rust/Python registration lost its cause line when the host read the stderr log before the copy of the dying process's last lines drained, and the isolated register path never read it. The host now waits (bounded) for the process to exit before reading the cause, in both paths. The Rust panic cause pattern did not match toolchains that print a thread id ('thread 'x' (123) panicked at'), and took the location line instead of the message; it now matches and joins the message. Scenario rows (all four SDKs, loader.ts:289-345): duplicate registrations replace in place and the first flag default wins, a non-object tool schema, a flag default of the wrong type and a nameless command…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 5380e0acc2 (2026-10-09)
  • Tests: coding/extension/host/subprocess/load_error_cause_test.go::TestStderrCausePicksTheRuntimeErrorLine

BF-54283ce

fix(extension): Shutdown closes live child sessions and releases the objects they hold

  • Source: PiG's own defect
  • Symptom: fix(extension): Shutdown closes live child sessions and releases the objects they hold
  • Root cause: Not recorded.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 54283ceb0f (2026-10-21)
  • Tests: None recorded.

BF-542b536

fix(cli): PIG_VERSION_FORMAT=pi prints the pinned Pi package version

  • Source: PiG's own defect
  • Symptom: fix(cli): PIG_VERSION_FORMAT=pi prints the pinned Pi package version
  • Root cause: Pi's --version prints the package version, which differs from the display version when the pin is a pi-mono commit. Use coding.UpstreamPackageVersion so the opt-in always matches pi --version.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 542b5369f6 (2026-10-11)
  • Tests: None recorded.

BF-5443b6c

durable/images is the image processor of pi-durable: images.CreatePhotonImages() (also images/node and…

  • Source: PiG's own defect
  • Symptom: durable/images is the image processor of pi-durable: images.CreatePhotonImages() (also images/node and images/cloudflare) is a tools.ImageProcessor that turns an image upright by its EXIF orientation, scales it to the limits with a Lanczos3 filter, converts BMP to PNG, and re-encodes PNG or JPEG at falling qualities until it fits.
  • Root cause: .upstream/pin names the pinned Pi build: a release tag or a pi-mono commit (version 1.1.0+). pigversion.go is generated from it; coding gains UpstreamRef and UpstreamPackageVersion beside the display version. PIUPSTREAM_REF=<tag|commit> overrides the pin for one run (make variables, a pigversion.go build overlay, PIG_UPSTREAM* in the environment). make pin-bump moves the pin and regenerates what derives from it: SDK, CI-image and Durable package pins, README and ledger version fields, the source mirror, the inventories.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), 86fe3dc28b (2026-10-09), f1d3eec6de (2026-10-09), 014c014e74 (2026-10-10), 2de6fe21b8 (2026-10-10), 30ca7fcf7f (2026-10-10), 5443b6c4d5 (2026-10-10), 691e3e1536 (2026-10-10), 76d1a85e45 (2026-10-10), 90b48897e1 (2026-10-10), 98c1299067 (2026-10-10), af1289d2d7 (2026-10-10), b7199a2ba1 (2026-10-10), c87dea7287 (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/builtin_catalog_upstream_test.go::TestBuiltinCatalogsMatchPinnedProviderDataModules and 72 more in docs/bug-ledger.json
  • Release note: durable/images is the image processor of pi-durable: images.CreatePhotonImages() (also images/node and images/cloudflare) is a tools.ImageProcessor that turns an image upright by its EXIF orientation, scales it to the limits with a Lanczos3 filter, converts BMP to PNG, and re-encodes PNG or JPEG at falling qualities until it fits. tools.CreateReadTool(&tools.ReadToolOptions{Images: p}) and tools.CreateCodingTools(...) pass it to read; tools.CreateReadTool now takes its options (nil for none), as CreateBashTool does.

BF-54a5bf6

The host answers a parsed frontmatter with <, >, & and U+2028 as they are (as JSON.stringify writes…

  • Source: PiG's own defect
  • Symptom: The host answers a parsed frontmatter with <, >, & and U+2028 as they are (as JSON.stringify writes them) instead of \u003c-style escapes, and a packed cell's helper without a Context keeps a live connection when the first extension to start has stopped (Go, Rust and Python SDKs).
  • Root cause: The host wrote the frontmatter result with encoding/json, which rewrites < > & and U+2028 as escapes. It now writes the result itself. The helpers' running-connection slot held only the first extension of a packed cell, so a helper with no Context failed when that one stopped: Go, Rust and Python keep every running extension's connection and use the first still running. The parity row is partial, because a value only an explicit YAML tag builds is still an error.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 058e7a0e4b (2026-10-11), 15cb4f2db8 (2026-10-11), 50f526d69e (2026-10-11), 54a5bf6140 (2026-10-11), 8894a13f34 (2026-10-11), a178939d6a (2026-10-11), a4e5b071cb (2026-10-11), e4aa58a078 (2026-10-11), f51d8b4bea (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, coding/extension/host/subprocess/host_helpers_quote_test.go::TestParseFrontmatterResultKeepsMarkupAndLineSeparatorsLiteral, extensions/sdk-py/tests/test_compaction_estimate.py, extensions/sdk-py/tests/test_helper_running.py, extensions/sdk-py/tests/test_serialize_conversation_surrogates.py and 13 more in docs/bug-ledger.json
  • Release note: The host answers a parsed frontmatter with <, >, & and U+2028 as they are (as JSON.stringify writes them) instead of \u003c-style escapes, and a packed cell's helper without a Context keeps a live connection when the first extension to start has stopped (Go, Rust and Python SDKs).

BF-5654e71

MCP OAuth discovery and resource selection now read URLs the way Node's URL does (default ports, dot…

  • Source: PiG's own defect
  • Symptom: MCP OAuth discovery and resource selection now read URLs the way Node's URL does (default ports, dot segments, backslashes, punycode hosts), and WWW-Authenticate challenges with quoted or odd values parse as Pi parses them.
  • Root cause: Pi packages/mcp/src: discovery.ts field() reads an unquoted value with a quote and tests JavaScript's \s and ASCII-only /i; PiG returned an empty value and used RE2's classes. discovery.ts builds URLs with WHATWG new URL (default port, dot segments, backslash, punycode, scheme-relative // paths, origin null for other schemes); PiG used net/url. types.ts keeps empty arrays, reads expires_in with Number() (arrays, 0x10, booleans) before access_token, treats null *_at as absent and spreads unmodeled client members; PiG dropped or rejected each. jsonrpc.ts isJsonRpcRequest/Notification ignore the error member and require a string method.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5654e71185 (2026-10-05)
  • Tests: mcp/oauth/upstream_oracle_test.go::TestBuildAuthorizationServerDiscoveryURLsMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseAuthorizationServerMetadataMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseClientInformationMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseOAuthTokensMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseProtectedResourceMetadataMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseWWWAuthenticateMatchesPi, mcp/oauth/upstream_oracle_test.go::TestSelectResourceMatchesPi, mcp/oauth/upstream_oracle_test.go::TestStepUpScopeMatchesPi and 1 more in docs/bug-ledger.json
  • Release note: MCP OAuth discovery and resource selection now read URLs the way Node's URL does (default ports, dot segments, backslashes, punycode hosts), and WWW-Authenticate challenges with quoted or odd values parse as Pi parses them.

BF-5654e71-2

A JSON-RPC request or notification with a malformed error member is no longer rejected, and a method that…

  • Source: PiG's own defect
  • Symptom: A JSON-RPC request or notification with a malformed error member is no longer rejected, and a method that is not a string is.
  • Root cause: Pi packages/mcp/src: discovery.ts field() reads an unquoted value with a quote and tests JavaScript's \s and ASCII-only /i; PiG returned an empty value and used RE2's classes. discovery.ts builds URLs with WHATWG new URL (default port, dot segments, backslash, punycode, scheme-relative // paths, origin null for other schemes); PiG used net/url. types.ts keeps empty arrays, reads expires_in with Number() (arrays, 0x10, booleans) before access_token, treats null *_at as absent and spreads unmodeled client members; PiG dropped or rejected each. jsonrpc.ts isJsonRpcRequest/Notification ignore the error member and require a string method.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5654e71185 (2026-10-05)
  • Tests: mcp/oauth/upstream_oracle_test.go::TestBuildAuthorizationServerDiscoveryURLsMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseAuthorizationServerMetadataMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseClientInformationMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseOAuthTokensMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseProtectedResourceMetadataMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseWWWAuthenticateMatchesPi, mcp/oauth/upstream_oracle_test.go::TestSelectResourceMatchesPi, mcp/oauth/upstream_oracle_test.go::TestStepUpScopeMatchesPi and 1 more in docs/bug-ledger.json
  • Release note: A JSON-RPC request or notification with a malformed error member is no longer rejected, and a method that is not a string is. A JSON-RPC error response whose error.code or error.message is null is rejected as an invalid message, and one whose code is beyond the float64 range is accepted, as in Pi.

BF-56731e9

/login runs a provider object's OAuth login with Pi's login interaction, so an info event shows in the…

  • Source: PiG's own defect
  • Symptom: PiG showed the message as a progress line and dropped the links.
  • Root cause: It now uses ShowSecretInput.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 56731e90d8 (2026-10-09)
  • Tests: ai/credential_key_order_test.go::TestAuthStorageKeepsEnvOrderAndFirstPositionOfARepeatedKey, internal/codingagent/device_code_native_test.go::TestProviderOwnedAuthDialogNotifications, internal/codingagent/login_info_event_test.go::TestOAuthInteractionSecretPromptHonorsMaskSecretInput, internal/codingagent/native_provider_login_test.go::TestNativeProviderOAuthLoginSelectPrompt, test/parity/scenarios/oauth/31-provider-object-login-info-links.toml
  • Release note: /login runs a provider object's OAuth login with Pi's login interaction, so an info event shows in the login dialog with its message and links, as Pi shows it. Before, PiG showed the message as a progress line and dropped the links. A secret prompt in that login now follows the Mask secret input setting, as it does in an API-key login.

BF-56731e9-2

An extension's API-key login no longer shows "Waiting for authentication..." after an authorization URL

  • Source: PiG's own defect
  • Symptom: An extension's API-key login no longer shows "Waiting for authentication..." after an authorization URL.
  • Root cause: It now uses ShowSecretInput.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 56731e90d8 (2026-10-09)
  • Tests: ai/credential_key_order_test.go::TestAuthStorageKeepsEnvOrderAndFirstPositionOfARepeatedKey, internal/codingagent/device_code_native_test.go::TestProviderOwnedAuthDialogNotifications, internal/codingagent/login_info_event_test.go::TestOAuthInteractionSecretPromptHonorsMaskSecretInput, internal/codingagent/native_provider_login_test.go::TestNativeProviderOAuthLoginSelectPrompt, test/parity/scenarios/oauth/31-provider-object-login-info-links.toml
  • Release note: An extension's API-key login no longer shows "Waiting for authentication..." after an authorization URL. Pi shows that line only after a device code.

BF-56a0400

Single-line inputs (login prompts, search boxes) ignore xterm modifyOtherKeys text sequences, as Pi's Input…

  • Source: PiG's own defect
  • Symptom: Single-line inputs (login prompts, search boxes) ignore xterm modifyOtherKeys text sequences, as Pi's Input does; the multi-line editor still accepts them.
  • Root cause: Label timestamps are toggled off before any save because a label saved now shows the wall clock.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 56a0400b9c (2026-10-07), f2a2b3409b (2026-10-07)
  • Tests: internal/codingagent/tree_row_format_test.go::TestFormatTreeRow_AllEntryTypes, internal/codingagent/tree_selector_oracle_test.go::TestTreeSelectorInputMatchesPi, tui/editor_bindings_oracle_test.go::TestEditorRebindingsMatchPi, tui/input_oracle_test.go::TestInputHandleInputMatchesPi, tui/key_csiu_text_test.go::TestTextInputIgnoresModifyOtherKeysText, tui/scoped_models_list_test.go::TestScopedModels_SearchAcceptsKittyAndPasteInput, tui/select_list_oracle_test.go::TestSelectListInputMatchesPi
  • Release note: Single-line inputs (login prompts, search boxes) ignore xterm modifyOtherKeys text sequences, as Pi's Input does; the multi-line editor still accepts them.

BF-56f1d3c

A family document member accepts a typed Go value as its seed, as CreateTask input and snapshots already do

  • Source: PiG's own defect
  • Symptom: A family document member accepts a typed Go value as its seed, as CreateTask input and snapshots already do.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 56f1d3c23a (2026-10-05)
  • Tests: durable/examples/pocket_dropin_test.go::TestPocketCommitSubscriberTracksBusySpendAndSubmissions, durable/examples/pocket_dropin_test.go::TestPocketForkInitCopiesAndTrimsDocuments, durable/examples/pocket_dropin_test.go::TestPocketOpenCreatesDocumentsAndSurvivesReopen, durable/examples/pocket_dropin_test.go::TestPocketToolHooksAreIntrospectableAndCallableFromATool, durable/session/watch_doc_typed_test.go::TestDeferUntilPublishedRunsAfterEveryListener, durable/session/watch_doc_typed_test.go::TestFamilyDocSeedAcceptsATypedGoValue
  • Release note: A family document member accepts a typed Go value as its seed, as CreateTask input and snapshots already do.

BF-56f1d3c-2

Submission.Wait, WaitForTask and the idle waits return after every commit subscriber has run for the…

  • Source: PiG's own defect
  • Symptom: Submission.Wait, WaitForTask and the idle waits return after every commit subscriber has run for the commit that settled them, as Pi's promise continuations do.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 56f1d3c23a (2026-10-05)
  • Tests: durable/examples/pocket_dropin_test.go::TestPocketCommitSubscriberTracksBusySpendAndSubmissions, durable/examples/pocket_dropin_test.go::TestPocketForkInitCopiesAndTrimsDocuments, durable/examples/pocket_dropin_test.go::TestPocketOpenCreatesDocumentsAndSurvivesReopen, durable/examples/pocket_dropin_test.go::TestPocketToolHooksAreIntrospectableAndCallableFromATool, durable/session/watch_doc_typed_test.go::TestDeferUntilPublishedRunsAfterEveryListener, durable/session/watch_doc_typed_test.go::TestFamilyDocSeedAcceptsATypedGoValue
  • Release note: Submission.Wait, WaitForTask and the idle waits return after every commit subscriber has run for the commit that settled them, as Pi's promise continuations do.

BF-570f857

Fixed interactive sessions leaving extension prompt sections, such as the MCP mcp_servers list, out of the…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: Fixed interactive sessions leaving extension prompt sections, such as the MCP mcp_servers list, out of the transcript's system message.
  • Root cause: Interactive mode prepares its own before_agent_start run (prepareRunPrompt) but only the Session's own prompt path stored the run in Session.runPrompt, which promptSectionUpdate reads when the request hook appends the sections patch to the transcript.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 47c300724d (2026-10-07), 570f85770f (2026-10-07), b3543f848c (2026-10-07)
  • Tests: coding/mcp_oauth_session_upstream_test.go::TestAgentSessionMCPOAuthCancelsASignInWaitingOnTheAuthorizationServerWhenTheSessionShutsDown, coding/mcp_oauth_session_upstream_test.go::TestAgentSessionMCPOAuthClosesTheSessionWithoutRefreshingAnExpiringToken, coding/mcp_oauth_session_upstream_test.go::TestAgentSessionMCPOAuthGivesUpOnPigMcpLoginAfterTimeoutAlsoWhileTheAuthorizationServerHangs, coding/mcpext/manager_test.go::TestMcpManagerReconnectRunsInTheBackgroundWithoutAStatusScreen, coding/mcpext/manager_test.go::TestMcpManagerServerMenuAndDisable, coding/mcpext/manager_test.go::TestMcpManagerStatusScreenCancelsTheRunningOperationWithTheCancelKey, coding/mcpext/manager_test.go::TestMcpManagerStatusScreenIgnoresTheCancelKeyForOperationsThatCannotBeCancelled, coding/mcpext/manager_test.go::TestMcpManagerViewCutsRowsToTheWidth and 23 more in docs/bug-ledger.json
  • Release note: Fixed interactive sessions leaving extension prompt sections, such as the MCP mcp_servers list, out of the transcript's system message. Print mode and Pi record them; interactive mode now publishes its prompt run to the session, which appends the sections before the first request.

BF-5732479

auth.json keeps each credential's properties in the order they were read or returned by an extension's…

  • Source: PiG's own defect
  • Symptom: PiG rewrote every credential in a fixed key order whenever auth.json was saved, including credentials of providers that were not changed.
  • Root cause: BenchmarkCredentialKeyOrderRoundTrip decodes and encodes an auth.json of three credentials. The remaining cost is the default encoding that decides whether an order must be kept. That cost is acceptable, because AuthStorage decodes only when auth.json's revision changes and encodes only on a login, refresh or logout.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), 24ee26a82b (2026-10-09), 26d302a2d7 (2026-10-09, #201), 5732479897 (2026-10-09), a527291444 (2026-10-09), a9956e80d8 (2026-10-09), b88044ff8a (2026-10-09), c7ebb23d87 (2026-10-09)
  • Tests: ai/auth_credential_payload_test.go::TestOAuthCredentialCustomJSONRetainsUTF16Payload, ai/credential_key_order_test.go::TestAuthStorageStoresALoginCredentialInItsOwnKeyOrder, ai/credential_key_order_test.go::TestAuthStorageWritesCredentialsInTheirDecodedKeyOrder, ai/credential_key_order_test.go::TestCredentialFromOAuthPlacesTheDiscriminatorAsPiDoes, ai/credential_key_order_test.go::TestCredentialInCodecOrderCarriesNoKeyOrder, coding/extension/host/subprocess/native_provider_login_test.go::TestDecodeNativeLoginCallback, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeAuthInteractionAnswersTheLoginDialog, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeProviderCarrierCarriesDeclaredLogins and 15 more in docs/bug-ledger.json
  • Release note: auth.json keeps each credential's properties in the order they were read or returned by an extension's login, as Pi keeps the object it stores and writes it with JSON.stringify. Before, PiG rewrote every credential in a fixed key order whenever auth.json was saved, including credentials of providers that were not changed. A config.oauth login's credential gets its type after the flow's own properties, as Pi's { ...credential, type: "oauth" } does. A credential that one of PiG's built-in logins creates still uses PiG's own key order, not the order of Pi's object literal.

BF-5774749

A Piglet found by name in <project>/.pig/piglets now follows project trust: it is used only when the…

  • Source: PiG's own defect
  • Symptom: A cloned repository could shadow the user's Piglet of that name, with its system prompt, extensions and model, with no prompt and no flag.
  • Root cause: A bare --piglet <name> is never read as a file in the working directory.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: da99854d1b (2026-10-09), 5774749dbc (2026-10-10), cb0873dbfa (2026-10-10)
  • Tests: coding/cli/main_test.go::TestResolvePigletResolvesExtends, coding/cli/main_test.go::TestResolvePiglet_FlagOverridesEnv, coding/cli/main_test.go::TestResolvePiglet_FromEnvPath, coding/cli/main_test.go::TestResolvePiglet_NoFlagNoEnv, coding/cli/mcp_builtin_binary_test.go::TestBuiltinMCPExtensionStartsProjectServersOnlyInATrustedProject, coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, coding/piglet/workspace_trust_test.go::TestResolveNamesAnIgnoredUntrustedWorkspacePiglet, coding/piglet/workspace_trust_test.go::TestResolveSkipsAnUntrustedWorkspacePigletAndPrefersTheUsers and 1 more in docs/bug-ledger.json
  • Release note: A Piglet found by name in <project>/.pig/piglets now follows project trust: it is used only when the project has a stored trust decision of yes or the run passes --approve, and an ignored workspace Piglet is named in the error. A bare --piglet <name> is never read as a file in the working directory. Before, a cloned repository could shadow the user's Piglet of that name, with its system prompt, extensions and model, with no prompt and no flag.

BF-578c1a3

The "Applied use cases" page in the manual that ships inside PiG now links the Security page on the website

  • Source: PiG's own defect
  • Symptom: It named a security.md page that the shipped manual does not have.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 578c1a3b47 (2026-10-11)
  • Tests: None recorded.
  • Release note: The "Applied use cases" page in the manual that ships inside PiG now links the Security page on the website. Before, it named a security.md page that the shipped manual does not have.

BF-579b4d2

fix(durable): a non-finite number is a strict JSON error whichever JSON package a value's own encoder used

  • Source: PiG's own defect
  • Symptom: fix(durable): a non-finite number is a strict JSON error whichever JSON package a value's own encoder used
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 579b4d240a (2026-10-24)
  • Tests: None recorded.

BF-57a1687

fix(ai,agent): return a projection's blocks apart from its scalars so the caller's buffer stays on its stack

  • Source: PiG's own defect
  • Symptom: fix(ai,agent): return a projection's blocks apart from its scalars so the caller's buffer stays on its stack
  • Root cause: An earlier change (lw-tui2, merged into int-050 by) made updateAssistantMessageBlock read ObserveProjection into a stack array instead of cloning the message. The projection came back as one struct holding the caller's buffer next to the stop reason and error message. Go's escape analysis does not tell struct fields apart: the block stores the stop reason and error text, so the array moved to the heap, 1 allocation per call.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 57a1687bc4 (2026-10-13)
  • Tests: agent/messages_projection_test.go::TestAssistantMessageProjectionEqualsObserve, ai/assistant_observation_projection_norace_test.go::TestObserveProjectionAllocatesNothing, ai/assistant_observation_projection_test.go::TestObserveProjectionEqualsObserve, ai/stream_builder_deferred_arguments_test.go::TestDeferredToolArgumentsReadAsTheEagerParse, internal/codingagent/reload_allocs_norace_test.go::TestInteractiveSessionReloadAllocatesPerRetainedMessageWithinBudget

BF-57ee33b

A model an extension registers with registerProvider reaches ctx.modelRegistry, ctx.model and RPC as…

  • Source: PiG's own defect
  • Symptom: A model an extension registers with registerProvider reaches ctx.modelRegistry, ctx.model and RPC as Pi's object: the definition's members in the order the extension wrote them, then api, provider and baseUrl, and its input as written.
  • Root cause: The four TestSubprocessModelRegistry* tests required every model an extension reads from ctx.modelRegistry to have promptCache, thinkingLevelMap, samplingParams, headers and compat members. The check was written for the old ModelInfo, which sent null for every absent member; made the objects Pi's and left it behind.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 50ca49180f (2026-10-13), 57ee33b5c7 (2026-10-13), 5cd840e477 (2026-10-13)
  • Tests: coding/cli/model_registry_refresh_test.go::TestSubprocessModelRegistryPublishesExtensionReplacementAndRestoration, coding/cli/model_registry_refresh_test.go::TestSubprocessModelRegistryPublishesProviderOnlyOverlayRefresh, coding/cli/model_registry_refresh_test.go::TestSubprocessModelRegistryPublishesProviderRegistrationAndRemoval, coding/cli/model_registry_refresh_test.go::TestSubprocessModelRegistryRefreshesConnectedNode, coding/cli/port_wave_10_package_command_paths_test.go::TestPortWave10ModelRuntimeCreation, coding/cli/shipped_piglet_inventory_test.go::TestShippedPigletInventory, coding/cli/strip_leak_gate_test.go::TestStripLeakGate, coding/model_availability_auth_view_test.go::TestAvailabilityPassAuthViewMatchesPiUpstream and 1 more in docs/bug-ledger.json
  • Release note: A model an extension registers with registerProvider reaches ctx.modelRegistry, ctx.model and RPC as Pi's object: the definition's members in the order the extension wrote them, then api, provider and baseUrl, and its input as written. PiG sent the members in its own order and filled in input: ["text"] when the definition left it out.

BF-59456ae

Pressing a key in a subprocess extension's overlay no longer freezes interactive mode when the key reaches…

  • Source: PiG's own defect
  • Symptom: Pressing a key in a subprocess extension's overlay no longer freezes interactive mode when the key reaches the overlay on the main loop (public PR #197).
  • Root cause: The subprocess bridge reads a focused overlay's state before it forwards a key. That read queued a task on the owner loop and waited for its result, so a key delivered on the owner loop waited for work only that loop could run. Pi reads the same state synchronously on its single event loop. The owner loop is now identified by goroutine: a caller already on it runs owner work inline, and a post to its own full queue no longer blocks.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 59456ae205 (2026-10-09)
  • Tests: internal/codingagent/overlay_owner_loop_deadlock_test.go::TestNestedOwnerLoopRestoresOwner, internal/codingagent/overlay_owner_loop_deadlock_test.go::TestOwnerLoopIdentity, internal/codingagent/overlay_owner_loop_deadlock_test.go::TestPostToMainWithRoomSkipsOwnerCheck, internal/codingagent/overlay_owner_loop_deadlock_test.go::TestRemoteOverlayKeyOnOwnerLoopDoesNotWaitOnOwnerQueue, internal/codingagent/overlay_owner_loop_deadlock_test.go::TestRunOnMainAndWaitOnOwnerLoopRunsInline, internal/codingagent/overlay_owner_loop_deadlock_test.go::TestRunOnMainFromOwnerLoopNeverBlocksOnItsOwnQueue, internal/codingagent/overlay_owner_loop_deadlock_test.go::TestRunOnOwnerInlineOnLoopQueuedElsewhere
  • Release note: Pressing a key in a subprocess extension's overlay no longer freezes interactive mode when the key reaches the overlay on the main loop (public PR #197).

BF-598e27f

Escape over an open autocomplete list during compaction now closes the list and lets compaction continue, as…

  • Source: PiG's own defect
  • Symptom: Previously it aborted the compaction and left the list open.
  • Root cause: CustomEditor runs onEscape only while no autocomplete is showing (custom-editor.ts:106-116); otherwise super.handleInput closes the list. Pig's CustomEditor falls through to superInput, which re-enters handleEditorAction with actionInterrupt, and the compaction-abort check (Pi's compaction_start onEscape, interactive-mode.ts:3678) ran before the autocomplete intercept: Escape aborted the compaction and left the list open. The check now follows the intercept. TestInteractiveMode_EscapeClosesAutocompleteDuringCompactionWithoutAborting is red on the old order.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 598e27f7e9 (2026-10-10)
  • Tests: internal/codingagent/custom_editor_test.go::TestCustomEditorActionLoopSkipsInterruptAndExit, internal/codingagent/custom_editor_test.go::TestCustomEditorEscapeOverAutocompleteGoesToTheEditor, internal/codingagent/interactive_test.go::TestInteractiveMode_EscapeClosesAutocompleteDuringCompactionWithoutAborting
  • Release note: Escape over an open autocomplete list during compaction now closes the list and lets compaction continue, as in Pi. Previously it aborted the compaction and left the list open.

BF-59fb623

fix(requestvalidity): one Responses assistant turn holds all its items, in any order

  • Source: PiG's own defect
  • Symptom: fix(requestvalidity): one Responses assistant turn holds all its items, in any order
  • Root cause: The Responses projection opened a new assistant turn for a message or reasoning item that followed a function_call, so an assistant message with text after its tool call ([toolCall, text], which a model may stream) was reported as between-call-and-result plus result-has-call. OpenAI documents only a missing output ("No tool output found for function call") or a missing call as errors. In a test binary the checker ends the process on that request, so any test replaying such a history through openai-responses, azure-openai-responses or openai-codex-responses died. Input items carry no assistant-message boundary, so consecutive assistant items now form one turn. A user, system or developer…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 59fb6231c2 (2026-10-09)
  • Tests: ai/request_validity_test.go::TestProviderSendSeamChecksEveryAPIsRequest, internal/requestvalidity/requestvalidity_test.go::TestCheckBodyContracts, test/pipeline-diff/diff_test.go::TestExplore, test/pipeline-diff/diff_test.go::TestShow

BF-5a093cd

In print and JSON mode, Node extensions that share one process now keep Pi's order at disposal

  • Source: PiG's own defect
  • Symptom: An earlier extension's queued callback could run before a later extension's session_shutdown handler started, or a timer could run with a live ctx after the runner was invalidated, depending on the order in which the process read its sockets.
  • Root cause: Pi's runner awaits each extension's handler in turn on one event loop (runner.ts:1089-1116). When a later extension's session_shutdown or agent_settled handler awaits a timer or I/O, that loop runs the immediate or timer an earlier extension's handler queued, with a live ctx, before print mode invalidates the runner (agent-session-runtime.ts:404-410, agent-session.ts:1393-1406). With isolated Node processes the earlier process waited for the host's next step after answering (), so its callbacks ran only after invalidate, or never, because the host stopped the process first. Packed processes share one loop and matched Pi.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 49798a80d2 (2026-10-07), 1f7fa2fe22 (2026-10-09), 5a093cd8f6 (2026-10-09)
  • Tests: coding/extension/host/subprocess/loop_turned_forward_test.go::TestLoopTurnedReachesOnlyOtherNodeProcesses, coding/extension/host/subprocess/runtime_node_loop_turn_test.go::TestNodeLoopTurnRunsQueuedImmediatesBeforeLaterFrames, coding/extension/host/subprocess/runtime_node_loop_turn_test.go::TestNodePromptEndWaitIgnoresAFinishedRunsSignal, coding/extension/host/subprocess/runtime_node_prompt_end_test.go::TestNodePackedPromptEndWaitHoldsTimersUntilEveryMemberIsInvalidated, coding/extension/host/subprocess/runtime_node_prompt_end_test.go::TestNodePromptEndWaitEndsWhenAnotherProcessTurnsTheLoop, extensions/sdk/surface_parity_test.go::TestLoopTurnedNotifyChangesNothing, test/parity/scenarios/extensions-runtime/75-print-shutdown-loop-turn-isolated.toml, test/parity/scenarios/extensions-runtime/75-print-shutdown-loop-turn.toml
  • Release note: In print and JSON mode, Node extensions that share one process now keep Pi's order at disposal. Before, an earlier extension's queued callback could run before a later extension's session_shutdown handler started, or a timer could run with a live ctx after the runner was invalidated, depending on the order in which the process read its sockets.

BF-5aab94c

The color level check reads TERM with JavaScript's case-insensitive matching, as Pi's chalk does: a name…

  • Source: PiG's own defect
  • Symptom: The color level check reads TERM with JavaScript's case-insensitive matching, as Pi's chalk does: a name such as ſcreen (long s) no longer counts as screen.
  • Root cause:
    • ai/validate_formats.go: formatDateTimeSeparator. - coding/cli/extension_upgrade.go: backupNameUnsafe. - coding/extension/source/resolve.go: pythonModuleName. - internal/packagemanager/package_updates.go: lsRemoteHash and lsRemoteHEAD. - internal/ignorerules/ignorerules.go: a shared matchEverything (a *regexp.Regexp is safe for concurrent use). Each is hoisted to a package-level lazyregexp (or a plain regexp for the empty pattern); the patterns are unchanged.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5aab94c69d (2026-10-10)
  • Tests: coding/cli/chalk_color_oracle_test.go::TestCLIChalkColorLevelMatchesChalk
  • Release note: The color level check reads TERM with JavaScript's case-insensitive matching, as Pi's chalk does: a name such as ſcreen (long s) no longer counts as screen. Ten regular expressions that were compiled on every call are now compiled once.

BF-5ab2304

Starting an extension no longer fails with fork/exec …: text file busy when the host builds or starts…

  • Source: PiG's own defect
  • Symptom: Starting an extension no longer fails with fork/exec …: text file busy when the host builds or starts other extensions at the same moment (seen as a ci-test-conformance failure in TestModelTypesVirtualModelsAcrossSDKs/rust-isolated).
  • Root cause: the host copies a compiled binary into the cache with an in-process write (copyArtifact for a cell runner when CARGO_TARGET_DIR puts the compiler output outside the build scratch; copyBuiltBinary, formerly inline in buildRust, for a standalone Rust extension; atomicWriteFile for an embedded cell of a Piglet Binary) and executes the copy as soon as it is published. A child that another goroutine forks while the copy is open holds a copy of the write descriptor until it calls exec. Close-on-exec closes it at exec, not at fork, so the host's exec of the binary fails while that child is still between fork and exec. Renaming the file into place does not help: ETXTBSY follows the open file, not…
  • User impact: An operation failed or was rejected
  • Platform: Linux
  • Fixed in: Unreleased
  • Fix commits: 5ab230421c (2026-10-24)
  • Tests: coding/extension/host/cellpack/extract_fork_test.go::TestAtomicWriteFileStartsNoChildWhileWriting, coding/extension/host/runtimecell/cellcache_exec_test.go::TestPublishArtifactStartsNoChildWhileCopyingAnExternalArtifact, coding/extension/host/subprocess/builder_rust_copy_test.go::TestCopyBuiltBinaryStartsNoChildWhileCopying, internal/execfile/execfile_test.go::TestWriteReleasesTheForkLockWhenItFails, internal/execfile/execfile_test.go::TestWriteStartsNoChildWhileTheFileIsOpen, internal/execfile/execfile_test.go::TestWriteTruncatesWhenAsked, internal/execfile/execfile_unix_test.go::TestStartAfterWriteIsNeverBusy, internal/execfile/execfile_unix_test.go::TestWriteLeavesNoDescriptorOpen and 1 more in docs/bug-ledger.json
  • Release note: Starting an extension no longer fails with fork/exec …: text file busy when the host builds or starts other extensions at the same moment (seen as a ci-test-conformance failure in TestModelTypesVirtualModelsAcrossSDKs/rust-isolated). The host copies a compiled extension binary into its cache and starts the copy as soon as it is published: a Rust cell runner when CARGO_TARGET_DIR puts the compiler output outside the build directory, a standalone Rust extension, and the embedded cells of a Piglet Binary. A process that another goroutine started while the copy was open still held the copy's write descriptor, and Linux refuses to start a file that any process holds open for writing. The host now starts no process from before the copy is opened until after it is closed, so a start waits for at most one 256 KiB chunk of a copy (about 0.2 ms), not the whole copy. A standalone Rust extension build also no longer reads the whole binary into memory to copy it.

BF-5b0e909

/scoped-models: an empty search no longer makes enable-all/clear-all act on every model, clearing every model…

  • Source: PiG's own defect
  • Symptom: /scoped-models: an empty search no longer makes enable-all/clear-all act on every model, clearing every model from an all-enabled list now leaves none enabled (not all), and the save key is checked after the navigation keys like Pi.
  • Root cause: (2) clearing every model from an all-enabled list produced a nil slice, i.e. 'all enabled', instead of an empty explicit list. (3) the save action was checked before navigation; Pi checks up, down, reorder, confirm, enableAll, clearAll, toggleProvider, save, ctrl+c, escape, so a shared key keeps the earlier action. Found, not changed: both Pi and Pig crash (TypeError / index out of range [-1]) when a reorder with an active search moves the selection above the first filtered row (reorder bounds are checked against the enabled list, not the filtered one).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 56a0400b9c (2026-10-07), 5b0e90985b (2026-10-07)
  • Tests: internal/codingagent/scoped_models_oracle_test.go::TestScopedModelsSelectorMatchesPi, tui/editor_bindings_oracle_test.go::TestEditorRebindingsMatchPi, tui/input_oracle_test.go::TestInputHandleInputMatchesPi, tui/key_csiu_text_test.go::TestTextInputIgnoresModifyOtherKeysText, tui/scoped_models_list_test.go::TestScopedModels_SearchAcceptsKittyAndPasteInput, tui/select_list_oracle_test.go::TestSelectListInputMatchesPi
  • Release note: /scoped-models: an empty search no longer makes enable-all/clear-all act on every model, clearing every model from an all-enabled list now leaves none enabled (not all), and the save key is checked after the navigation keys like Pi.

BF-5b15407

In-process extensions that call pi.getCommands() now receive the extension commands, prompt templates and…

  • Source: PiG's own defect
  • Symptom: In-process extensions that call pi.getCommands() now receive the extension commands, prompt templates and skills of the session in every mode, as Pi's session binds them.
  • Root cause: Pi's PackageManager/DefaultPackageManager (package-manager.ts) was a set of cmd/pig helper functions taking (cwd, settings). They move to internal/packagemanager as PackageManager with setProgressCallback, listConfiguredPackages, getInstalledPath, install, installAndPersist, remove, removeAndPersist, addSourceToSettings and removeSourceFromSettings; cmd/pig builds one per command with packageManagerFor and its ~90 call sites (production and tests) use the methods. Behaviour is unchanged (cmd/pig package/install/remove/settings/npm/git tests pass).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 8d990c5d54 (2026-10-07), a79de03e7f (2026-10-07), fe4a0bf7a4 (2026-10-07), 5b15407a4b (2026-10-08)
  • Tests: coding/cli/session_extension_actions_listing_test.go::TestSessionSlashCommandsPlaceResourcesWithoutSourceInfoByTheSessionDirectories, coding/extension/host/inproc/upstream_scoped_models_test.go::TestContextThinkingLevelReadsTheBoundRuntimeLive, internal/codingagent/docs_path_test.go::TestGetDocsPathIsAbsoluteForARelativeConfigRoot, internal/codingagent/docs_path_test.go::TestPromptDocsSectionNamesGetDocsPathWithATildeConfigRoot, internal/codingagent/interactive_get_commands_test.go::TestInteractiveWiringBindsGetCommandsToTheRuntime, internal/codingagent/tools/bash_spawn_hook_test.go::TestBashSpawnHookAdjustsCommandCwdAndEnv, internal/codingagent/tools/powershell_operations_test.go::TestPowerShellToolCallsCustomOperations, internal/configroot/configroot_test.go::TestDirPrefersPigHomeThenXDGThenTheHomeDirectory and 4 more in docs/bug-ledger.json
  • Release note: In-process extensions that call pi.getCommands() now receive the extension commands, prompt templates and skills of the session in every mode, as Pi's session binds them.

BF-5b1d5b1

A resource pattern that starts with ~ (!~/claude-backup/skills/pdf, +~/…, -~/…, wildcards) now…

  • Source: PiG's own defect
  • Symptom: A resource pattern that starts with ~ (!~/claude-backup/skills/pdf, +~/…, -~/…, wildcards) now expands the home directory like a plain entry instead of silently matching nothing, and a file:// URL pattern is its path, as Pi 1.2.0 does (pi-mono #10765).
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5b1d5b1382 (2026-10-10)
  • Tests: None recorded.
  • Release note: A resource pattern that starts with ~ (!~/claude-backup/skills/pdf, +~/…, -~/…, wildcards) now expands the home directory like a plain entry instead of silently matching nothing, and a file:// URL pattern is its path, as Pi 1.2.0 does (pi-mono #10765).

BF-5b4a3d3

A lazy line no longer continues a quote whose last quoted line is empty (>\nnext is an empty quote then a…

  • Source: PiG's own defect
  • Symptom: A lazy line no longer continues a quote whose last quoted line is empty (>\nnext is an empty quote then a paragraph), and a quote that renders no rows still adds its spacing row.
  • Root cause: marked's blockquote tokenizer joins a lazy line only when the quoted line before it is not empty (a line of spaces counts); an empty quote is still a token whose spacing row is not merged with a blank. 10 new rows in TestMarkdownRenderMatchesPi (5 red before). Known gap: '> a\n> \nb' (the lazy line joins the paragraph with the spaces line kept).
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5b4a3d393e (2026-10-10), cdf0be76e4 (2026-10-10)
  • Tests: tui/markdown_render_oracle_test.go::TestMarkdownRenderMatchesPi
  • Release note: A lazy line no longer continues a quote whose last quoted line is empty (>\nnext is an empty quote then a paragraph), and a quote that renders no rows still adds its spacing row.

BF-5bc4773

RPC set_model applies after its auth check, as Pi's awaited setModel does, so a cycle_model in the same…

  • Source: PiG's own defect
  • Symptom: RPC set_model applies after its auth check, as Pi's awaited setModel does, so a cycle_model in the same input batch cycles from the previous model and answers first.
  • Root cause: Also pins sdk.ts:224-231 in the existing restore test (row 19 gate needs the provider's auth).
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2df65dffcc (2026-10-09), 5bc4773278 (2026-10-09)
  • Tests: coding/cli/rpc_set_model_await_test.go::TestRPCSetModelAppliesAfterTheBatchesLaterCommands, coding/session_context_settings_upstream_test.go::TestRestoreSessionRuntimeStateExplicitModelSelection, coding/session_resume_auth_gate_test.go::TestResumedSessionDoesNotRestoreAModelWithoutAuth
  • Release note: RPC set_model applies after its auth check, as Pi's awaited setModel does, so a cycle_model in the same input batch cycles from the previous model and answers first. A session resumed without credentials no longer restores its saved model: it starts on the unknown placeholder, as Pi does.

BF-5bdbf20

fix(cli): keep scratch install output off the validate-only report

  • Source: PiG's own defect
  • Symptom: fix(cli): keep scratch install output off the validate-only report
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 5bdbf201de (2026-10-22)
  • Tests: None recorded.

BF-5c4167a

fix(extension): hold the theme colors map the wire palette memo is keyed on

  • Source: PiG's own defect
  • Symptom: fix(extension): hold the theme colors map the wire palette memo is keyed on
  • Root cause: ActiveExtensionThemeWire identified the terminal-resolved colors by the address of the map theme.Colors returned, stored as a uintptr. The theme drops that map when the terminal's reported colors change, so the collector could free it and give its address to a later colors map with other values; the memo would then return the old palette and the state push would skip the theme as unchanged. The memo now holds the map it is keyed on, so the address cannot be reused while it identifies the palette.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 5c4167aad9 (2026-10-11)
  • Tests: internal/codingagent/ext_ui_theme_palette_test.go::TestActiveExtensionThemeWireHoldsTheColorsMapItIsKeyedOn

BF-5c9d23f

A provider object's OAuth login receives Pi's login options, as oauth.login(interaction, options) does in Pi

  • Source: PiG's own defect
  • Symptom: A provider object's flow got no options, so a flow that needs the device ID could not get it.
  • Root cause: Python: pig_sdk.LoginOptions(get_device_id, agent_name), with the same dispatch and proxy behavior. Only oauth.login receives it, because Pi declares apiKey.login with the interaction alone (types.ts:175).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 95d51d5fa0 (2026-10-09), da99854d1b (2026-10-09), f7e3843ab1 (2026-10-09), 5c9d23fa6c (2026-10-10)
  • Tests: ai/oauth_credential_members_test.go::TestOAuthBuildersWriteCredentialMembersInPiOrder, ai/oauth_credential_members_test.go::TestRefreshFirstOAuthBuildersWriteCredentialMembersInPiOrder, coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeAuthInteractionAnswersTheLoginDialog, coding/extension/host/subprocess/native_provider_oauth_test.go::TestNativeProviderOAuthLoginCallbacks, coding/extension/host/subprocess/native_provider_oauth_test.go::TestNativeProviderOAuthLoginGetsTheDeviceID, test/extension-conformance/provider_object_test.go::TestNodeRemoteProviderObjectCarrier, test/extension-conformance/provider_object_test.go::TestProviderObjectsAcrossSDKs and 1 more in docs/bug-ledger.json
  • Release note: A provider object's OAuth login receives Pi's login options, as oauth.login(interaction, options) does in Pi. options.getDeviceId() returns the app's device ID, and the app creates that ID only when a flow first calls it. options.agentName is passed when the app sets it. Before, a provider object's flow got no options, so a flow that needs the device ID could not get it.

BF-5d004ae

fix: give Windows git children core.longpaths through the environment, keeping Pi's argv

  • Source: PiG's own defect
  • Symptom: fix: give Windows git children core.longpaths through the environment, keeping Pi's argv
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: Unreleased
  • Fix commits: 5d004aef80 (2026-10-22)
  • Tests: coding/cli/git_checkout_path_test.go::TestGitLongPathsEnvPerPlatform

BF-5d36a58

fix(agent): end the tool flow at custom user messages and hold system messages mid-flow

  • Source: PiG's own defect
  • Symptom: fix(agent): end the tool flow at custom user messages and hold system messages mid-flow
  • Root cause: Pi converts messages to LLM form and then runs transformMessages (packages/ai/src/api/transform-messages.ts), where a branchSummary, compactionSummary, bashExecution or custom message is already a user message and closes the pending tool calls (closePendingToolCalls). PiG normalizes AgentMessages before convertToLLM, so those custom messages did not interrupt the flow and the placeholders landed behind them. NormalizeMessages now treats a custom message that convertToLLM sends as a user message as a user turn, and holds a system message that sits between a tool call and its results until after the results (transform-messages.ts heldSystemMessages).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 5d36a5821d (2026-10-09)
  • Tests: agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, coding/session_tree_wire_oracle_test.go::TestNavigateTreeEveryPointSendsValidToolFlow

BF-5d6bb25

The request-string walk that splices cached strings into a request body finds a cycle by the pointer, map or…

  • Source: PiG's own defect
  • Symptom: The request-string walk that splices cached strings into a request body finds a cycle by the pointer, map or slice it is inside instead of stopping at a depth of 1,000: a payload that holds itself still fails as the marshaler fails, and a payload nested deeper than 1,000 levels has its large strings spliced like any other (the body is the same bytes).
  • Root cause: The map and slice cycles report the marshaler's error.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5d6bb25cdc (2026-10-10), 6a6fffa638 (2026-10-10)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/request_string_splice_test.go::TestMarshalSplicedWalksAnyDepthAndReportsEveryKindOfCycle, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain, test/longsession/session_oracle_test.go::TestSessionManagerMatchesPi and 1 more in docs/bug-ledger.json
  • Release note: The request-string walk that splices cached strings into a request body finds a cycle by the pointer, map or slice it is inside instead of stopping at a depth of 1,000: a payload that holds itself still fails as the marshaler fails, and a payload nested deeper than 1,000 levels has its large strings spliced like any other (the body is the same bytes).

BF-5d8eda1

pig auth check and pig auth print-api-key no longer reject --api-key that another option takes as its…

  • Source: PiG's own defect
  • Symptom: pig auth check and pig auth print-api-key no longer reject --api-key that another option takes as its value (--model --api-key, --provider --api-key): only an --api-key that is itself a flag counts, as in Pi.
  • Root cause: ValidateAuthCommandArgs decided that --api-key was given by scanning the raw arguments for the token, so pig auth check --model --api-key --provider --model (--model takes --api-key as its value) failed with "Auth commands only accept --provider and --model". Pi tests args.apiKey !== undefined, which only a parsed --api-key sets. parseArgs records that (apiKeySet, also for an empty value) and the validator reads it; the raw-argument parameter is no longer read. Found by seed sweeps K=6 and K=7 of TestAuthCommandMatchesPi.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 5d8eda1fb2 (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, coding/cli/args_upstream_test.go::TestArgsUpstream, coding/cli/auth_command_oracle_test.go::TestAuthCommandMatchesPi, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain and 2 more in docs/bug-ledger.json
  • Release note: pig auth check and pig auth print-api-key no longer reject --api-key that another option takes as its value (--model --api-key, --provider --api-key): only an --api-key that is itself a flag counts, as in Pi.

BF-5dbfed1

A scripted faux response can carry DurationMs

  • Source: PiG's own defect
  • Symptom: A scripted faux response can carry DurationMs.
  • Root cause: It closes ClientOptions.transportFactory, ByteTransportFactory and ByteTransport.send (gaps 403 -> 395). The callback is the Promise settlement the client keeps (internal/experimental/client/transport.go:27, connection.go:170).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5dbfed1576 (2026-10-08)
  • Tests: ai/faux_duration_test.go::TestFauxScriptedDurationMsReachesEveryMessage, ai/faux_scripted_fields_oracle_test.go::TestFauxScriptedMessageMembersMatchPi
  • Release note: A scripted faux response can carry DurationMs. It stays on every partial and the final message, as in Pi, and the stream measures a duration only when none is scripted.

BF-5e36d87

The assistant-message frame encoder no longer re-parses and re-serializes a whole tool call's arguments for…

  • Source: PiG's own defect
  • Symptom: The assistant-message frame encoder no longer re-parses and re-serializes a whole tool call's arguments for every delta while it catches up to the start snapshot (8,000 deltas allocated 19 GB; they now allocate 12 MB).
  • Root cause: Catch-up re-parsed and re-serialized the whole argument text per delta (19 GB for 8,000 deltas). Use the incremental streamingArgumentsParser, cache the parsed snapshot, and skip serialization while a top-level string of the snapshot is longer than the same member so far (neither equal nor a prefix).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5e36d8772b (2026-10-09)
  • Tests: None recorded.
  • Release note: The assistant-message frame encoder no longer re-parses and re-serializes a whole tool call's arguments for every delta while it catches up to the start snapshot (8,000 deltas allocated 19 GB; they now allocate 12 MB). The frames it emits are unchanged.

BF-5e3e346

An explicit anthropic-beta header, from a model's headers or the request, no longer makes a…

  • Source: A Pi defect that PiG fixed first (divergence D130)
  • Symptom: An explicit anthropic-beta header, from a model's headers or the request, no longer makes a managed-effort Claude model (claude-opus-5, opus-5-5, sonnet-5-5, haiku-5-5, fable-5-1) fail with 400.
  • Root cause: buildParams now computes the betas before converting the messages. - The markers, including the historical per-turn ones, go only with the mid-conversation beta. - applySampling sends block_binding only with the binding beta. - Without the markers, the active effort is the top-level output_config effort instead of "high". Without an override, or with one listing both betas, the request is Pi's. tool_addition blocks without inline-tools-2026-09-15 are left as Pi has them; the issue did not test that case.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10759
  • Removed when Pi fixes it: Pi derives block_binding and the mid-conversation markers from the betas it sends.
  • Fix commits: 5e3e346a69 (2026-10-10)
  • Tests: ai/anthropic_beta_override_test.go::TestAnthropicManagedEffortBodyFollowsTheBetasSent
  • Release note: An explicit anthropic-beta header, from a model's headers or the request, no longer makes a managed-effort Claude model (claude-opus-5, opus-5-5, sonnet-5-5, haiku-5-5, fable-5-1) fail with 400. The override still replaces the default betas. PiG now sends thinking.block_binding only with thinking-binding-controls-2026-08-01, and the mid-conversation effort markers only with mid-conversation-output-config-2026-07-01. Without the markers, the effort is sent as the top-level effort. Pi sends both regardless (Pi #10759, D130).

BF-5eab79d

processImage against live Pi: new TestProcessImageMatchesPi…

  • Source: PiG's own defect
  • Symptom: processImage against live Pi: new TestProcessImageMatchesPi (internal/imageprocessing/process_image_oracle_test.go; stacked on r16 because it needs the zero-side fix; 400 seeded inputs: PNG/JPEG/GIF/BMP/TIFF/WebP, garbage and empty bytes, declared types with parameters/case/spaces, unsupported and mismatched types, resizing on/off with small limits; ok, mime type, ordered hints and omission message compared).
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), 6a6fffa638 (2026-10-10), 3d475c443d (2026-10-11), 5eab79d677 (2026-10-11)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult and 21 more in docs/bug-ledger.json
  • Release note: processImage against live Pi: new TestProcessImageMatchesPi (internal/imageprocessing/process_image_oracle_test.go; stacked on r16 because it needs the zero-side fix; 400 seeded inputs: PNG/JPEG/GIF/BMP/TIFF/WebP, garbage and empty bytes, declared types with parameters/case/spaces, unsupported and mismatched types, resizing on/off with small limits; ok, mime type, ordered hints and omission message compared). One real fix, red first (9 of 400, 8 of them this): ResizeImage excluded BMP bytes from the "already within every limit" shortcut (&& srcFormat != "bmp", undocumented, from the first release), so BMP bytes declared as a supported type (png/jpeg/gif/webp) were re-encoded to PNG with a dimension note where Pi returns the bytes unchanged under the declared type; the clause is gone (a BMP declared image/bmp is converted to PNG before resizing, in both). 9 mutations red; 1 equivalent (from == to in the conversion hint: an unsupported declared type never equals the PNG/JPEG result). Not compared: the returned bytes.

BF-5ec5f5f

The built-in bash, read and list tool cards now show an image block that has no MIME type as `[Image…

  • Source: PiG's own defect
  • Symptom: The built-in bash, read and list tool cards now show an image block that has no MIME type as [Image: [image/unknown]], as Pi's getTextOutput does.
  • Root cause: toolResultTextOutput showed an image block without a MIME type as [Image: []]; it now delegates to tools.GetTextOutput (render-utils.ts:54). The clipboard-image.ts mutants no longer edit interactive-mode.ts code (handleClipboardPaste), and the clipboard.ts mutants no longer edit clipboard-command.ts behavior.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5ec5f5f282 (2026-10-09)
  • Tests: internal/codingagent/render_utils_pi_test.go::TestRenderUtilsReplaceTabsAndTextOutputMatchPi
  • Release note: The built-in bash, read and list tool cards now show an image block that has no MIME type as [Image: [image/unknown]], as Pi's getTextOutput does. They previously showed [Image: []].

BF-5f03fa4

An extension that a child agent session's resource loader loads now receives the session's events, as in Pi…

  • Source: PiG's own defect
  • Symptom: An extension that a child agent session's resource loader loads now receives the session's events, as in Pi, where createAgentSession over a DefaultResourceLoader that loads an extension gives it session_start (after bindExtensions), input, before_agent_start, agent_start, turn_*, message_*, context and agent_end.
  • Root cause: A DefaultResourceLoader loaded its extensions under the context of the reload that asked for them. A child agent session is created, and its loader reloaded, inside the wire call of createAgentSession, so the host stopped every extension the loader had loaded when that call returned: the extension had registered its tools and then received no event. The loader now owns the extensions it loaded until its next load or Close, as Pi's reload() has no caller whose end stops them; the context of the reload still ends the load itself.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 5f03fa4a4e (2026-10-21), 78c1d22259 (2026-10-21), d8da9b372a (2026-10-21)
  • Tests: None recorded.
  • Release note: An extension that a child agent session's resource loader loads now receives the session's events, as in Pi, where createAgentSession over a DefaultResourceLoader that loads an extension gives it session_start (after bindExtensions), input, before_agent_start, agent_start, turn_*, message_*, context and agent_end. In the Go, Python and Rust SDKs (through the resources option or a resourceLoader handle) the extension registered its tools and then received nothing: the loader started its extensions under the context of the call that reloaded it, which ends when createAgentSession returns, so the host stopped every extension it had just loaded and reported no error. The loader now owns its extensions until its next load or Close; the context of a reload still ends the reload itself.

BF-5f03fa4-2

The contexts of those extensions read the child session: ctx.model, ctx.scopedModels (the session's…

  • Source: PiG's own defect
  • Symptom: The contexts of those extensions read the child session: ctx.model, ctx.scopedModels (the session's scopedModels, which the child wire accepted and no extension saw), ctx.isIdle(), ctx.hasUI (false) and the headless dialogs (notify, select, input and editor answer nothing, confirm answers false), as the context of a Pi child session has.
  • Root cause: A DefaultResourceLoader loaded its extensions under the context of the reload that asked for them. A child agent session is created, and its loader reloaded, inside the wire call of createAgentSession, so the host stopped every extension the loader had loaded when that call returned: the extension had registered its tools and then received no event. The loader now owns the extensions it loaded until its next load or Close, as Pi's reload() has no caller whose end stops them; the context of the reload still ends the load itself.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 5f03fa4a4e (2026-10-21), d8da9b372a (2026-10-21)
  • Tests: None recorded.
  • Release note: The contexts of those extensions read the child session: ctx.model, ctx.scopedModels (the session's scopedModels, which the child wire accepted and no extension saw), ctx.isIdle(), ctx.hasUI (false) and the headless dialogs (notify, select, input and editor answer nothing, confirm answers false), as the context of a Pi child session has. The loader's extension host has a headless UI bridge for this, and a child session binds the state print mode binds to it.

BF-5f22b85

fix(coding): shut down the extension host of a DefaultResourceLoader an extension constructed when its last…

  • Source: PiG's own defect
  • Symptom: fix(coding): shut down the extension host of a DefaultResourceLoader an extension constructed when its last holder lets go
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 5f22b85fa3 (2026-10-21)
  • Tests: None recorded.

BF-60c4c6f

A tool call whose finished arguments are not a valid JSON object no longer runs with the parsed prefix: its…

  • Source: PiG's own defect
  • Symptom: A tool call whose finished arguments are not a valid JSON object no longer runs with the parsed prefix: its arguments are {} and argumentsError holds the reason and the text the model sent, which tool validation reports back to the model.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 60c4c6f887 (2026-10-14, #10944)
  • Tests: ai/json_parse_upstream_test.go::TestParseToolCallArguments, ai/openai_responses_partial_json_cleanup_upstream_test.go::TestOpenAIResponsesMarksMalformedFinalArguments, ai/rpc33_provider_abort_test.go::TestOpenAIProviderAbortAfterAdmission, ai/rpc33_provider_abort_test.go::TestOpenAIProviderAbortAtParserReadBoundaries, ai/rpc33_provider_scratch_test.go::TestProviderScratchInterleavedTools, ai/stream_builder_deferred_arguments_test.go::TestDeferredToolArgumentsReadAsTheEagerParse, ai/validate_upstream_test.go::TestValidateToolArgumentsReportsArgumentsError
  • Release note: A tool call whose finished arguments are not a valid JSON object no longer runs with the parsed prefix: its arguments are {} and argumentsError holds the reason and the text the model sent, which tool validation reports back to the model. Carried forward from Pi 87f9a364 (earendil-works/pi#10944, #10897), past the 1.2.0+3daa2028 pin.

BF-612955a

The Rust and Go SDKs keep an unpaired surrogate through prepareBranchEntries…

  • Source: PiG's own defect
  • Symptom: The Rust and Go SDKs keep an unpaired surrogate through prepareBranchEntries (prepare_branch_entries_json, PrepareBranchEntriesJSON, Context::get_entries_json), and the token estimate of all three SDKs counts it as JSON.stringify does in a tool call's arguments.
  • Root cause: A 6,000-message differential against Pi's own serializeConversation (node + dist) now has 0 differences in Go and Python. Before the fix it had 53 in Go and 33 in Python. The conformance row cannot see this, because the harness decodes reports with encoding/json, which turns every lone surrogate into U+FFFD.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 058e7a0e4b (2026-10-11), 25bd4f0080 (2026-10-11), 2699291af5 (2026-10-11), 612955add9 (2026-10-11), 891834837e (2026-10-11), a178939d6a (2026-10-11), a4e5b071cb (2026-10-11), be2cfbd1f5 (2026-10-11), e4aa58a078 (2026-10-11), e8473d13c5 (2026-10-11), f51d8b4bea (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, coding/cli/extension_host_wiring_binary_test.go::TestHeadlessExtensionSessionActionsMatchPi, extensions/sdk-py/tests/test_compaction_estimate.py, extensions/sdk-py/tests/test_serialize_conversation_surrogates.py, extensions/sdk/compaction_estimate_test.go::TestEstimateTokensCountsUTF16UnitsAndJSONStringifyLength and 11 more in docs/bug-ledger.json
  • Release note: The Rust and Go SDKs keep an unpaired surrogate through prepareBranchEntries (prepare_branch_entries_json, PrepareBranchEntriesJSON, Context::get_entries_json), and the token estimate of all three SDKs counts it as JSON.stringify does in a tool call's arguments.

BF-61300bb

MCP OAuth reads a non-HTTP redirect or server URL as Node's new URL does: dot segments are removed, a…

  • Source: PiG's own defect
  • Symptom: MCP OAuth reads a non-HTTP redirect or server URL as Node's new URL does: dot segments are removed, a default ws or ftp port is dropped, and a malformed percent escape such as /%zz is kept.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 61300bb3e8 (2026-10-10)
  • Tests: internal/codingagent/interactive_native_login_test.go::TestLoginListsARegisteredNativeAPIKeyProviderInNameOrder, internal/codingagent/session_listing_race_test.go::TestSessionListingSortsConcurrentlyWithoutARace, internal/jsstring/locale_compare_test.go::TestLocaleCompareIsSafeForConcurrentUse, internal/jsstring/locale_compare_test.go::TestLocaleCompareOrdersAsNodeDoes, mcp/oauth/parse_url_node_test.go::TestParseURLMatchesNodeNewURL
  • Release note: MCP OAuth reads a non-HTTP redirect or server URL as Node's new URL does: dot segments are removed, a default ws or ftp port is dropped, and a malformed percent escape such as /%zz is kept.

BF-61300bb-2

Sorting names with localeCompare (MCP servers, sessions, login providers, codemode namespaces, model lists…

  • Source: PiG's own defect
  • Symptom: Sorting names with localeCompare (MCP servers, sessions, login providers, codemode namespaces, model lists, Hugging Face results) no longer shares one collator between goroutines, which raced when sessions were listed concurrently, and uses ICU root collation for every name instead of an ASCII weight table.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), 61300bb3e8 (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/streaming_json_resume_oracle_test.go::TestStreamingArgumentsParserMatchesPiAtEveryChunk and 6 more in docs/bug-ledger.json
  • Release note: Sorting names with localeCompare (MCP servers, sessions, login providers, codemode namespaces, model lists, Hugging Face results) no longer shares one collator between goroutines, which raced when sessions were listed concurrently, and uses ICU root collation for every name instead of an ASCII weight table.

BF-6132277

An MCP OAuth authorization response with an iss parameter is now rejected when no authorization server…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: An MCP OAuth authorization response with an iss parameter is now rejected when no authorization server metadata was discovered, so a malicious MCP server cannot receive a code another authorization server issued (as Pi c5f5b328 does).
  • Root cause: Port of Pi main (RFC 9207: without metadata there is no issuer to compare with, so a code from another authorization server could reach the token endpoint). OAuthIssuerMismatchError.Expected is now *string (nil = none), as Pi's string | undefined.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 61322774f1 (2026-10-10)
  • Tests: mcp/oauth/absent_test.go::TestOAuthIssuerMismatchErrorNamesAMissingIssAsNone, mcp/oauth/absent_test.go::TestOAuthIssuerMismatchErrorQuotesIssuersAsJSONStringify, mcp/oauth/behavior_test.go::TestOAuthErrorMessagesAreTheOnesErrorsTSBuilds, mcp/oauth/error_constructors_upstream_test.go::TestOAuthErrorConstructorsMatchErrorsTs, mcp/oauth/errors_class_test.go::TestOAuthErrorClassesCarryTheirNameAndMessage
  • Release note: An MCP OAuth authorization response with an iss parameter is now rejected when no authorization server metadata was discovered, so a malicious MCP server cannot receive a code another authorization server issued (as Pi c5f5b328 does).

BF-61fc01d

list_mcp_resources, list_mcp_resource_templates and read_mcp_resource return every member a server sent…

  • Source: PiG's own defect
  • Symptom: Members PiG did not know were dropped and the known ones were reordered.
  • Root cause: Before, members PiG did not know were dropped and the known ones were reordered.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 44e0ab8129 (2026-10-05), 49cb28c369 (2026-10-05), 61fc01d975 (2026-10-05)
  • Tests: coding/mcpext/connection_test.go::TestMCPConnectionsAppendsTheLastUTF16UnitsOfAFailedStdioServersStderr, coding/mcpext/extension_test.go::TestAgentSessionMCPResourceToolsKeepTheMembersAndOrderTheServerSent, coding/mcpext/extension_test.go::TestAgentSessionMCPResourceToolsTrimServerArgumentsLikeJavaScript, coding/mcpext/extension_test.go::TestAgentSessionMCPResourceToolsWriteTheServersEntriesAsJSONStringifyDoes, coding/mcpext/fixtures_test.go::TestMain, coding/mcpext/jstrim_internal_test.go::TestParseStatesTreatsJavaScriptWhitespaceAsEmpty, coding/mcpext/jstrim_internal_test.go::TestResponseFromRedirectURLTrimsLikeJavaScript, coding/mcpext/jstrim_internal_test.go::TestWaitForAuthorizationResponseCancelsOnAJavaScriptBlankAnswer and 5 more in docs/bug-ledger.json
  • Release note: list_mcp_resources, list_mcp_resource_templates and read_mcp_resource return every member a server sent for a resource, template or content entry, in Pi's order and in the form JSON.stringify writes: integer-like keys first, numbers such as 2.0 as 2, and <, & and > unescaped. Before, members PiG did not know were dropped and the known ones were reordered.

BF-61fc01d-2

MCP tool descriptions, resource MIME types, a pasted sign-in redirect URL and the resource tools' server…

  • Source: PiG's own defect
  • Symptom: MCP tool descriptions, resource MIME types, a pasted sign-in redirect URL and the resource tools' server and uri arguments are trimmed as JavaScript trims them: a byte order mark (U+FEFF) is removed and U+0085 is kept.
  • Root cause: resources.ts jsonResult (packages/coding-agent/src/extensions/mcp/resources.ts:182-187) returns JSON.stringify(payload) as text and the payload object as structured content, and read (resources.ts:325-333) returns the contents as JSON.parse read them. The listed entries carried the server's raw member bytes, so PiG wrote integer-like keys in sent order, kept 2.0, 1e2, \u00e9 and escaped <&> as \u003c\u0026\u003e. jsonResult and the read's structured content now pass through jsonstringify.Canonicalize.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 471c46e063 (2026-10-05), 49cb28c369 (2026-10-05), 61fc01d975 (2026-10-05)
  • Tests: coding/mcpext/extension_test.go::TestAgentSessionMCPHidesToolsInTheOrderTheServerListedThem, coding/mcpext/extension_test.go::TestAgentSessionMCPResourceToolsKeepTheMembersAndOrderTheServerSent, coding/mcpext/extension_test.go::TestAgentSessionMCPResourceToolsTrimServerArgumentsLikeJavaScript, coding/mcpext/extension_test.go::TestAgentSessionMCPResourceToolsWriteTheServersEntriesAsJSONStringifyDoes, coding/mcpext/jstrim_internal_test.go::TestParseStatesTreatsJavaScriptWhitespaceAsEmpty, coding/mcpext/jstrim_internal_test.go::TestResponseFromRedirectURLTrimsLikeJavaScript, coding/mcpext/jstrim_internal_test.go::TestWaitForAuthorizationResponseCancelsOnAJavaScriptBlankAnswer, coding/mcpext/log_rotation_test.go::TestMcpServerLogComparesTheTrackedAndTheCurrentSizeStrictlyWithTheLimit and 4 more in docs/bug-ledger.json
  • Release note: MCP tool descriptions, resource MIME types, a pasted sign-in redirect URL and the resource tools' server and uri arguments are trimmed as JavaScript trims them: a byte order mark (U+FEFF) is removed and U+0085 is kept.

BF-6235532

Filterable selector lists (theme, images, tree-filter-like pickers) check up/down before confirm/cancel like…

  • Source: PiG's own defect
  • Symptom: Filterable selector lists (theme, images, tree-filter-like pickers) check up/down before confirm/cancel like Pi's SelectList when a key is bound to several of them.
  • Root cause: The assignment is removed. SelectSubmenuComponent now has OnSelectionChange (set by newSelectSubmenuStep, its production caller) fired after up/down. Frames compare as text (Pi bold/fg closers vs Pig SGR 0 differ, open renderer question). Overlapping rebinds (a key shared by cancel and up) are not probed: FilterableList checks cancel before up unlike Pi's SelectList; follow-up.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d1f258f335 (2026-10-06), 082355f166 (2026-10-07), 0ae3fcf17c (2026-10-07), 4058f34548 (2026-10-07), 623553262c (2026-10-07), a7be906e71 (2026-10-07), c8e3d86378 (2026-10-07), ccaade27af (2026-10-07), f2a2b3409b (2026-10-07)
  • Tests: internal/codingagent/extension_dialog_remapping_test.go::TestExtensionDialogRemappingThroughOwnerLoop, internal/codingagent/remote_overlay_production_review_test.go::TestRemoteOverlayProductionRejectsCachedTerminalWidth, internal/codingagent/session_selector_oracle_test.go::TestSessionSelectorInputMatchesPi, internal/codingagent/settings_submenu_oracle_test.go::TestSettingsSubmenusMatchPi, internal/codingagent/thinking_selector_oracle_test.go::TestThinkingSelectorInputMatchesPi, internal/codingagent/tree_row_format_test.go::TestFormatTreeRow_AllEntryTypes, internal/codingagent/tree_selector_oracle_test.go::TestTreeSelectorInputMatchesPi, internal/codingagent/trust_selector_oracle_test.go::TestTrustSelectorInputMatchesPi and 16 more in docs/bug-ledger.json
  • Release note: Filterable selector lists (theme, images, tree-filter-like pickers) check up/down before confirm/cancel like Pi's SelectList when a key is bound to several of them.

BF-6258d27

fix(rpc): a replacement aborts the compactions of its batch at its teardown only

  • Source: PiG's own defect
  • Symptom: fix(rpc): a replacement aborts the compactions of its batch at its teardown only
  • Root cause: A compact yields after compaction_start until every replacement requested by the end of its batch tore down (which cancels it) or gave up, as in Pi where the teardown reaches dispose before any compaction I/O. A refused or failed replacement (fork of an unknown entry, switch_session to a directory) leaves the compaction alone and Pi writes the entry. The compaction-cancel registry was keyed by new(struct{}), which every compact shared, so only the last compact of a batch could be cancelled. An abort before the summary request is Pi's getAuth rejection with the signal's reason (prepareSummarizationRequest), not a summarizer error.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 6258d2784c (2026-10-10)
  • Tests: coding/cli/rpc_replacement_order_test.go::TestRPCRefusedReplacementLeavesTheCompactionOfItsBatchAlone, coding/cli/rpc_replacement_order_test.go::TestRPCReplacementAbortsTheCompactionOfItsBatch, coding/session_test.go::TestCompactCancelsSynchronouslyFromStartEvent

BF-625a32e

Display text that PiG cuts at a length (the /tree listing, the Piglet system-prompt preview, an extension…

  • Source: PiG's own defect
  • Symptom: Display text that PiG cuts at a length (the /tree listing, the Piglet system-prompt preview, an extension load error cause) is cut at characters, not bytes, so a multi-byte character at the cut no longer becomes invalid UTF-8.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 625a32e078 (2026-10-10)
  • Tests: coding/extension/host/subprocess/load_error_cause_test.go::TestStderrCauseCutsAtCharactersNotBytes, internal/codingagent/tree_ascii_truncation_test.go::TestRenderTreeASCIICutsMessagesAtCharactersNotBytes
  • Release note: Display text that PiG cuts at a length (the /tree listing, the Piglet system-prompt preview, an extension load error cause) is cut at characters, not bytes, so a multi-byte character at the cut no longer becomes invalid UTF-8.

BF-626dccc

OpenAI-compatible Chat Completions requests resolve chat_template_args (the baseten thinking format) and…

  • Source: PiG's own defect
  • Symptom: OpenAI-compatible Chat Completions requests resolve chat_template_args (the baseten thinking format) and chat_template_kwargs (the chat-template format) as Pi does.
  • Root cause: openai-completions.ts:1034-1075 resolves compat.chatTemplateArgs (baseten, line 916) and compat.chatTemplateKwargs (chat-template, line 906) with one function: {$var:thinking.budget} is the clamped thinking budget, and {$var:thinking.effort} is thinkingLevelMap[level] (the level when absent, nothing when null) or thinkingLevelMap.off when reasoning is off, nothing unless it is a string. PiG's baseten branch re-implemented the loop without thinking.budget, so a budget variable was dropped there, and both formats sent "none" for an effort variable when reasoning was off with no off entry, and sent the raw level for a null entry. Baseten now calls resolveChatTemplateValues like chat-template…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 626dccc66c (2026-10-10)
  • Tests: ai/anthropic_messages_payload_oracle_test.go::TestAnthropicMessagesPayloadMatchesPi, ai/openai_completions_chat_template_values_test.go::TestOpenAICompletionsChatTemplateEffortFollowsTheThinkingLevelMap, ai/openai_completions_chat_template_values_test.go::TestOpenAICompletionsChatTemplateValuesResolveTheSameForKwargsAndArgs, ai/thinking_level_map_clamp_test.go::TestThinkingLevelMapDecidesSupportedAndClampedLevels
  • Release note: OpenAI-compatible Chat Completions requests resolve chat_template_args (the baseten thinking format) and chat_template_kwargs (the chat-template format) as Pi does. A thinking.budget variable in chat_template_args now sends the clamped thinking budget instead of being dropped. A thinking.effort variable sends the model's thinkingLevelMap.off value when reasoning is off and is left out when that entry is absent or not a string, instead of sending "none". A null entry for the requested level also leaves it out, instead of sending the level name.

BF-62a1c87

The ExtensionSelector and ExtensionInput oracles also run with the Kitty keyboard protocol active (CSI-u…

  • Source: PiG's own defect
  • Symptom: The ExtensionSelector and ExtensionInput oracles also run with the Kitty keyboard protocol active (CSI-u enter, escape, ctrl+j, ctrl+c, shift+enter, letters, modified arrows) in Pi and Pig; no difference.
  • Root cause: No difference.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 62a1c87b2a (2026-10-11)
  • Tests: internal/codingagent/settings_selector_oracle_test.go::TestSettingsSelectorMatchesPi, tui/extension_input_oracle_test.go::TestExtensionInputMatchesPi, tui/extension_selector_oracle_test.go::TestExtensionSelectorMatchesPi
  • Release note: The ExtensionSelector and ExtensionInput oracles also run with the Kitty keyboard protocol active (CSI-u enter, escape, ctrl+j, ctrl+c, shift+enter, letters, modified arrows) in Pi and Pig; no difference.

BF-62ae30d

Durable: reading the history of a conversation whose parent conversation is not stored now fails with Pi's…

  • Source: PiG's own defect
  • Symptom: Durable: reading the history of a conversation whose parent conversation is not stored now fails with Pi's error ("Cannot read properties of undefined (reading 'parent')") at the point where Pi's lineage walk reaches the missing record, instead of returning a truncated history.
  • Root cause: Differences found: every message used Go's %q where Pi interpolates the name between plain double quotes (constrained-sampling.ts:136-150,188,226,259,267), so a tool named 'we"ird' got backslashes; isStructuredSchema tested properties/items with != nil although JS tests !== undefined (an explicit null counts) and searched a type array through toStringSlice, which gave up when any element was not a string where Array.includes does not. Mutants killed: each of those, the delta no-op and closed-buffer rules, the JS trim of grammar variants, the nullable-wrap and required checks. Go cannot hold a non-object tool.parameters, so boolean/array/string roots are not probed (type-level difference).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), 97e178f7f6 (2026-10-09), 1dcaef738b (2026-10-10), 62ae30d413 (2026-10-10), 749840b14d (2026-10-10), 752c93fb19 (2026-10-10), f71185dc7a (2026-10-10)
  • Tests: ai/constrained_sampling_oracle_test.go::TestConstrainedSamplingMatchesPi, ai/streaming_json_oracle_test.go::TestStreamingJSONMatchesPiOnSeededInput, durable/storage/jsonl/recovery_rewrite_test.go::TestJsonlRecoveryIgnoresALeadingByteOrderMarkOnEachLine, durable/storage/jsonl/recovery_rewrite_test.go::TestJsonlRecoveryRewritesReclaimedSidecarRecordsCanonically, durable/storage/jsonl_pi_oracle_test.go::TestJsonlStorageMatchesPiOnSeededPrograms, durable/storage/jsonl_recovery_pi_oracle_test.go::TestJsonlRecoveryMatchesPiOnDamagedDirectories, durable/storage/memory_dangling_parent_test.go::TestMemoryStorageLineageThroughAMissingParentConversationFails, durable/storage/memory_pi_oracle_test.go::TestMemoryStorageMatchesPiOnSeededPrograms and 2 more in docs/bug-ledger.json
  • Release note: Durable: reading the history of a conversation whose parent conversation is not stored now fails with Pi's error ("Cannot read properties of undefined (reading 'parent')") at the point where Pi's lineage walk reaches the missing record, instead of returning a truncated history. The SQLite storage reports the same failure with Pi's SQLite error ("Cannot read properties of undefined (reading 'id')") instead of "Fork parent conversation is missing".

BF-62c3691

grep with a large context no longer holds every context line in memory: its memory is bounded by the 50KB…

  • Source: PiG's own defect
  • Symptom: grep with a large context no longer holds every context line in memory: its memory is bounded by the 50KB output it returns, with the same text and truncation details (#9276).
  • Root cause: grep formats limit x (2*context+1) lines, joins them and keeps the first 50KB (Pi grep.ts:176-282). A large context therefore held memory proportional to limit x file for a fixed-size result: 65 MB at a 2,000-line file, out of memory on large files.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 10aebd5c05 (2026-10-09), 62c3691863 (2026-10-10, #9276), 82d1808c64 (2026-10-10)
  • Tests: ai/complexity_scaling_norace_test.go::TestScalingProviderRequestPerTurn, ai/complexity_scaling_norace_test.go::TestScalingProviderStreamPerDelta, coding/cli/complexity_scaling_norace_test.go::TestScalingExtensionEventStatePush, coding/complexity_scaling_norace_test.go::TestScalingSessionTurn, coding/extension/host/subprocess/complexity_scaling_norace_test.go::TestScalingMessageUpdatePayload, coding/session_context_usage_reuse_test.go::TestContextUsageReusesTheEstimateUntilTheLogOrWindowChanges, internal/codingagent/compaction/complexity_scaling_norace_test.go::TestScalingCompactionAndContextEstimate, internal/codingagent/complexity_scaling_norace_test.go::TestScalingSessionLogOperations and 10 more in docs/bug-ledger.json
  • Release note: grep with a large context no longer holds every context line in memory: its memory is bounded by the 50KB output it returns, with the same text and truncation details (#9276).

BF-6323c8e

fix(ai): a Bedrock model outside the catalog is a bedrock-converse-stream model of amazon-bedrock, so its own…

  • Source: PiG's own defect
  • Symptom: fix(ai): a Bedrock model outside the catalog is a bedrock-converse-stream model of amazon-bedrock, so its own replay keeps tool-call ids (the fallback Model had no provider or api, so isSameModel was false and bedrock-converse-stream.ts' normalizeToolCallId rewrote them).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 6323c8eb2f (2026-10-10)
  • Tests: ai/bedrock_uncataloged_model_test.go::TestBedrockUncatalogedModelIsTheSameModelAsItsOwnReplay

BF-63b4fc8

NodeExecutionEnv.Watch gives a directory replaced at the same path a new watcher, reports changes to the…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: NodeExecutionEnv.Watch gives a directory replaced at the same path a new watcher, reports changes to the file a symbolic-link target points to, no longer lets a non-recursive target stop an overlapping recursive target from covering subdirectories, and installs no watcher once closed.
  • Root cause: createPowerShellTool over argv exec; read keeps a log that grows while read; OutputBuffer drops a byte-order mark only at the start of the output; NodeExecutionEnv.watch fails with permission_denied, follows a symbolic link to a file, keeps recursive coverage under an overlapping non-recursive target, re-installs a watcher on a replaced directory and installs none after Close; the env conformance suite gains the three watch checks (packages/durable 1.0.4).
  • User impact: Behavior differed from Pi
  • Platform: macOS
  • Fixed in: 0.5.0
  • Fix commits: 63b4fc80e7 (2026-10-05)
  • Tests: chord/output/output_test.go::TestOutputBuffer, chord/output/output_test.go::TestToolOutputBounds, durable/harness/harness_registry_test.go::TestSettingsKeepTheDefaultOfAProgressIntervalGivenAsUndefined, durable/tools/powershell_upstream_test.go::TestPowerShellFallsBackToWindowsPowerShellAndReportsTheLastStartFailure, durable/tools/powershell_upstream_test.go::TestPowerShellRunsRealPowerShellWithUTF8Output, durable/tools/powershell_upstream_test.go::TestPowerShellRunsTheCommandWithPwshAsOneArgumentForcingUTF8Output, durable/tools/powershell_upstream_test.go::TestReadReadsALogThatGrowsWhileItIsRead
  • Release note: NodeExecutionEnv.Watch gives a directory replaced at the same path a new watcher, reports changes to the file a symbolic-link target points to, no longer lets a non-recursive target stop an overlapping recursive target from covering subdirectories, and installs no watcher once closed. On macOS it reports changes to the file that a watched symbolic link points to.

BF-63bb0b7

pig extension upgrade now rewrites a Context getter call that is the only argument of a variadic call…

  • Source: PiG's own defect
  • Symptom: pig extension upgrade now rewrites a Context getter call that is the only argument of a variadic call, such as fmt.Println(ctx.ConfigHome()).
  • Root cause: That call still compiles against the current SDK because the value and the error both become arguments, so the error was printed or passed on instead of handled and the upgrade reported nothing.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 63bb0b7eac (2026-10-12), 974050757c (2026-10-12), d305b76bda (2026-10-12)
  • Tests: coding/cli/extension_upgrade_test.go::TestUpgraderRestoresEveryFileWhenTheRebuildFails, coding/cli/extension_upgrade_test.go::TestUpgraderRestoresWrittenFilesWhenAWriteFails, coding/extension/host/runtimecell/go_sdk_override_test.go::TestGoSDKPathOverrideIgnoresRetiredStagedSDK, coding/extension/host/runtimecell/go_sdk_override_test.go::TestGoSDKPathOverrideKeepsCurrentStagedSDK, coding/extension/host/runtimecell/go_sdk_override_test.go::TestRustSDKPathOverrideIgnoresRetiredStagedSDK, coding/extension/host/subprocess/builder_test.go::TestGoNeedsStagedSDK, coding/extension/host/subprocess/builder_test.go::TestRustNeedsStagedSDK, extensions/sdk/upgrade/fixtures_test.go::TestFixturesUpgradeAndBuild and 3 more in docs/bug-ledger.json
  • Release note: pig extension upgrade now rewrites a Context getter call that is the only argument of a variadic call, such as fmt.Println(ctx.ConfigHome()). That call still compiles against the current SDK because the value and the error both become arguments, so the error was printed or passed on instead of handled and the upgrade reported nothing. It is now rewritten like the other uses: the function returns the error, or, for ConfigHome in a function that does not return an error, the line is listed for you to change.

BF-644a9f2

Changelog link rewriting and git source parsing match a URL scheme case-insensitively as Pi's regex with…

  • Source: PiG's own defect
  • Symptom: Changelog link rewriting and git source parsing match a URL scheme case-insensitively as Pi's regex with the i flag does: ſ (U+017F) and K (U+212A) no longer count as s and k.
  • Root cause: Regenerated with make bug-ledger.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 644a9f2fde (2026-10-13)
  • Tests: test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: Changelog link rewriting and git source parsing match a URL scheme case-insensitively as Pi's regex with the i flag does: ſ (U+017F) and K (U+212A) no longer count as s and k.

BF-644a9f2-2

A Node extension's ctx.ui.custom factory can call tui.showOverlay, tui.hideOverlay and tui.setFocus…

  • Source: PiG's own defect
  • Symptom: A Node extension's ctx.ui.custom factory can call tui.showOverlay, tui.hideOverlay and tui.setFocus, as in Pi.
  • Root cause: Regenerated with make bug-ledger.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 644a9f2fde (2026-10-13)
  • Tests: test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: A Node extension's ctx.ui.custom factory can call tui.showOverlay, tui.hideOverlay and tui.setFocus, as in Pi. showOverlay shows the component as a raw overlay at once and returns a synchronous handle (hide, setHidden, isHidden, focus, unfocus, isFocused, getBounds), so Pi's overlay-focus, overlay-passive and overlay-streaming example extensions work. tui.requestRender() redraws every component the extension shows. A raw overlay ends with its handle's hide(), or when the extension's process exits or reloads.

BF-644b3e0

A codemode script that calls text() tens of thousands of times no longer blocks the process: adjacent text…

  • Source: PiG's own defect
  • Symptom: A codemode script that calls text() tens of thousands of times no longer blocks the process: adjacent text items are joined in linear time (Pi #10678).
  • Root cause: execute.ts joinAdjacentText rebuilds the joined string per item; the port did too. 40k items allocated 28 GB (11 s); now linear. Output identical (differential test vs the per-item rebuild).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 644b3e0a2a (2026-10-09)
  • Tests: coding/extension/builtin/codemode/join_adjacent_text_test.go::TestJoinAdjacentTextIsLinearInTheNumberOfItems, coding/extension/builtin/codemode/join_adjacent_text_test.go::TestJoinAdjacentTextMatchesUpstreamOnMixedItems
  • Release note: A codemode script that calls text() tens of thousands of times no longer blocks the process: adjacent text items are joined in linear time (Pi #10678).

BF-648ff3e

A child session's message_update event carries the message so far and the assistant event's partial, as…

  • Source: PiG's own defect
  • Symptom: A child session's message_update event carries the message so far and the assistant event's partial, as Pi's AgentSession.subscribe hands them to a listener, where RPC mode's trimmed shape left both out.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A child session's message_update event carries the message so far and the assistant event's partial, as Pi's AgentSession.subscribe hands them to a listener, where RPC mode's trimmed shape left both out.

BF-648ff3e-10

pig --list-models now prints only the models the model runtime reports as available, as Pi does

  • Source: PiG's own defect
  • Symptom: It used to append every bundled model of each provider that had a login or API key, so after pig update --models it still listed OpenRouter models that the saved personalized catalog had dropped, such as openrouter/auto and the :batch variants.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: pig --list-models now prints only the models the model runtime reports as available, as Pi does. It used to append every bundled model of each provider that had a login or API key, so after pig update --models it still listed OpenRouter models that the saved personalized catalog had dropped, such as openrouter/auto and the :batch variants.

BF-648ff3e-100

An SDK's ctx.hasPendingMessages() in interactive mode counts queued user messages only, as Pi does, and no…

  • Source: PiG's own defect
  • Symptom: An SDK's ctx.hasPendingMessages() in interactive mode counts queued user messages only, as Pi does, and no longer counts the custom messages an extension queued with sendMessage.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: An SDK's ctx.hasPendingMessages() in interactive mode counts queued user messages only, as Pi does, and no longer counts the custom messages an extension queued with sendMessage.

BF-648ff3e-101

A Node extension that changes the active tools or registers a tool now reads the system prompt back as Pi does

  • Source: PiG's own defect
  • Symptom: A Node extension that changes the active tools or registers a tool now reads the system prompt back as Pi does.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A Node extension that changes the active tools or registers a tool now reads the system prompt back as Pi does. ctx.getSystemPrompt() and ctx.getSystemPromptOptions() answered from the state the host pushes with the next event, so a read after pi.setActiveTools or pi.registerTool in the same handler returned the prompt of before the write, while the Go, Python and Rust SDKs read the live value. From the first such write on, the reads ask the host.

BF-648ff3e-102

ctx.isIdle() after pi.sendMessage(..., { triggerTurn: true }) in an agent_settled handler of a Node…

  • Source: PiG's own defect
  • Symptom: ctx.isIdle() after pi.sendMessage(..., { triggerTurn: true }) in an agent_settled handler of a Node extension now reports true, as the Go, Python and Rust SDKs do.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: ctx.isIdle() after pi.sendMessage(..., { triggerTurn: true }) in an agent_settled handler of a Node extension now reports true, as the Go, Python and Rust SDKs do. The session holds such a message until the handlers return, as in Pi, and Node had assumed the run started.

BF-648ff3e-103

A Node extension's ctx.ui.select, confirm and input honor options.signal, as Pi's RPC and interactive…

  • Source: PiG's own defect
  • Symptom: The signal was ignored: the dialog stayed open until the user or its timeout answered, and the extension waited for it.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A Node extension's ctx.ui.select, confirm and input honor options.signal, as Pi's RPC and interactive modes do. A signal that is already aborted resolves the default (undefined, or false for confirm) without showing the dialog or writing an RPC request, and a signal that aborts while the dialog is open resolves the default at once and dismisses the dialog (RPC: the pending request is dropped). Before, the signal was ignored: the dialog stayed open until the user or its timeout answered, and the extension waited for it.

BF-648ff3e-104

A start could fail with a bare missing-file error when another pig's cache collection removed the cached…

  • Source: PiG's own defect
  • Symptom: A start could fail with a bare missing-file error when another pig's cache collection removed the cached extension build it had just reused.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A start could fail with a bare missing-file error when another pig's cache collection removed the cached extension build it had just reused. The collection (the daily automatic one, or pig extensions cache prune) decides which entries are expired and removes them a moment later. A start that reuses an old entry in between refreshed its use time but holds no lease until its extension process spawns, so the entry was removed under it, the lease then came back empty instead of failing, and the spawn reported a missing file. The collection now reads each entry again under its exclusive lock and skips one that another pig used since, counting it as in use. A reuse takes the entry's lock while it checks and records the entry, so it never reuses an entry that is being removed and builds it again instead. A lease for an entry that is gone fails with cache_lease_failed and says the entry was removed. The SDK fingerprint file of a published entry is replaced through a rename and written only when it changes, so a collection can no longer read it half written and expire a build that a start is using.

BF-648ff3e-105

A tool's prepareLoadout hook now runs again before every later turn of a run, as in Pi, so the descriptions…

  • Source: PiG's own defect
  • Symptom: A tool's prepareLoadout hook now runs again before every later turn of a run, as in Pi, so the descriptions it sets and the declarations it hides follow the state the hook reads.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A tool's prepareLoadout hook now runs again before every later turn of a run, as in Pi, so the descriptions it sets and the declarations it hides follow the state the hook reads. The hooks ran when a run started and when the loadout changed, so a hook that read settings or extension state kept the first turn's answer for the rest of the run.

BF-648ff3e-106

Fixed the chord host keeping a disposed object reachable until its ready timeout (a minute by default) had…

  • Source: PiG's own defect
  • Symptom: Fixed the chord host keeping a disposed object reachable until its ready timeout (a minute by default) had passed: a stopped host timer stayed in the runtime's timer heap with its callback, and the callback captured the object.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Fixed the chord host keeping a disposed object reachable until its ready timeout (a minute by default) had passed: a stopped host timer stayed in the runtime's timer heap with its callback, and the callback captured the object. A stopped timer now releases its callback at once.

BF-648ff3e-107

The /tree "Summarize branch?" selector and "Custom summarization instructions" editor, the /import…

  • Source: PiG's own defect
  • Symptom: The /tree "Summarize branch?" selector and "Custom summarization instructions" editor, the /import confirmation and the missing-session-cwd confirmation of /resume and /import are dialogs on the same stack as an extension's, as in Pi.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The /tree "Summarize branch?" selector and "Custom summarization instructions" editor, the /import confirmation and the missing-session-cwd confirmation of /resume and /import are dialogs on the same stack as an extension's, as in Pi. A dialog an extension opens while one of them is shown takes the keys, closing the prompt shows the earlier dialog again instead of hiding it, each reports its blocked status to the terminal, and a session replacement or /reload removes the prompt with the other dialogs. /bug keeps its own prompts outside the stack and no longer reports a blocked status for its description editor, as in Pi (Pi 38847563, bug-report.ts).

BF-648ff3e-108

OpenAI Responses requests keep the prompt cache across tool changes (Pi #11142)

  • Source: PiG's own defect
  • Symptom: OpenAI Responses requests keep the prompt cache across tool changes (Pi #11142).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: OpenAI Responses requests keep the prompt cache across tool changes (Pi #11142). Every tool is declared in additional_tools items from the head of the input, a removal is announced as text with the still-unavailable tools restated, and synthetic tool search and Kimi tool messages move the top-level tool list only at the last removal or redefinition, so a later tool load no longer rewrites the cached prefix. Applies to openai-responses, azure-openai-responses, openai-codex-responses and Kimi on openai-completions.

BF-648ff3e-109

On a file system that refuses file locks (an NFS mount without a lock manager, a FUSE or SMB share), starting…

  • Source: PiG's own defect
  • Symptom: On a file system that refuses file locks (an NFS mount without a lock manager, a FUSE or SMB share), starting an extension failed with wait for cell build lock: flock: no locks available, or with a bare lock error from the lease of a cached build, the cache collector or the SDK staging, and nothing named the cause.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: On a file system that refuses file locks (an NFS mount without a lock manager, a FUSE or SMB share), starting an extension failed with wait for cell build lock: flock: no locks available, or with a bare lock error from the lease of a cached build, the cache collector or the SDK staging, and nothing named the cause. The error now names the directory, says the file system does not support file locks, and names the remedy: point PIG_HOME at a directory on a file system that does. The cache still needs its locks, so a file system without them cannot hold it.

BF-648ff3e-11

/reload no longer reports `Extension "builtin:codemode" error: This extension ctx is stale after session…

  • Source: PiG's own defect
  • Symptom: /reload no longer reports Extension "builtin:codemode" error: This extension ctx is stale after session replacement or reload. A tool registered while an in-process extension factory runs refreshed the live Session, which ran the prepareLoadout hook of the previous load's tool through the API the reload had just retired.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: /reload no longer reports Extension "builtin:codemode" error: This extension ctx is stale after session replacement or reload. A tool registered while an in-process extension factory runs refreshed the live Session, which ran the prepareLoadout hook of the previous load's tool through the API the reload had just retired. Like Pi's loading runtime, whose refreshTools is a no-op until the runner binds, a registration during factory load now leaves the Session alone and the reload's own rebuild declares the tools once.

BF-648ff3e-110

In RPC mode, a toast an extension deferred with setTimeout(0) from a model_select or session_start…

  • Source: PiG's own defect
  • Symptom: In RPC mode, a toast an extension deferred with setTimeout(0) from a model_select or session_start listener reached the output before the response of the new_session, fork, clone, switch_session, set_model or cycle_model command that ran the listener, or of the prompt whose extension command started the new Session.
  • Root cause: Pi writes the response first, because its response follows the listeners in microtasks and the timer needs a later turn of the event loop.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: In RPC mode, a toast an extension deferred with setTimeout(0) from a model_select or session_start listener reached the output before the response of the new_session, fork, clone, switch_session, set_model or cycle_model command that ran the listener, or of the prompt whose extension command started the new Session. Pi writes the response first, because its response follows the listeners in microtasks and the timer needs a later turn of the event loop. The host now holds such UI calls (ui.notify, ui.setStatus, ui.setWidget, ui.setTitle, ui.setEditorText) until the command's response is written. Seen with @liyu1981/pi-tweaks' remember-model extension.

BF-648ff3e-111

In RPC mode, a message an extension's session_start listener sent while a command replaced the Session was…

  • Source: PiG's own defect
  • Symptom: In RPC mode, a message an extension's session_start listener sent while a command replaced the Session was written to the output.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: In RPC mode, a message an extension's session_start listener sent while a command replaced the Session was written to the output. Pi subscribes its output writer after session_start, so it writes nothing for it; PiG now publishes the new Session's events after session_start too.

BF-648ff3e-112

In RPC mode, an extension command that sends a user message and then awaits ctx.waitForIdle() (the init…

  • Source: PiG's own defect
  • Symptom: In RPC mode, an extension command that sends a user message and then awaits ctx.waitForIdle() (the init command of a Pigpen warden) had the run's agent_start written before the prompt's response, for the Go, Python, Rust and Node SDKs alike.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: In RPC mode, an extension command that sends a user message and then awaits ctx.waitForIdle() (the init command of a Pigpen warden) had the run's agent_start written before the prompt's response, for the Go, Python, Rust and Node SDKs alike. Pi's waitForIdle returns at once while the run the command started has not begun, so the command returns, the response is written, and agent_start follows. The host now settles such a wait at once and no longer counts the handler as suspended.

BF-648ff3e-113

Two Bun runtime installs, or an install and a status check, in different processes no longer leave a moment…

  • Source: PiG's own defect
  • Symptom: Two Bun runtime installs, or an install and a status check, in different processes no longer leave a moment where <agent dir>/runtimes/typescript/bun does not exist: /bun-runtime install, remove, verify and status take a lock on the slot, and an install removes the bun.old-* directory that a killed install left behind.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Two Bun runtime installs, or an install and a status check, in different processes no longer leave a moment where <agent dir>/runtimes/typescript/bun does not exist: /bun-runtime install, remove, verify and status take a lock on the slot, and an install removes the bun.old-* directory that a killed install left behind.

BF-648ff3e-114

A slow toolchain version check no longer blocks other starts without limit

  • Source: PiG's own defect
  • Symptom: A slow toolchain version check no longer blocks other starts without limit.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A slow toolchain version check no longer blocks other starts without limit. Go, Rust and Python extension builds probe go version, rustc --version and the like; the probe is now ended after two minutes, a start that finds another start probing the same tool waits at most five minutes before probing for itself, and two projects with different go.mod or rust-toolchain.toml selectors each keep their own cached version instead of probing again on every alternate start.

BF-648ff3e-115

Fixed the chord host manager letting other code read it as stopped, or start the child again, before the…

  • Source: PiG's own defect
  • Symptom: Fixed the chord host manager letting other code read it as stopped, or start the child again, before the onExit callback for the child that just exited had run: upstream sets the status and calls onExit in one step, so the manager now keeps other goroutines waiting until onExit returns, while onExit itself sees it stopped and may start the child again.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Fixed the chord host manager letting other code read it as stopped, or start the child again, before the onExit callback for the child that just exited had run: upstream sets the status and calls onExit in one step, so the manager now keeps other goroutines waiting until onExit returns, while onExit itself sees it stopped and may start the child again.

BF-648ff3e-116

Fixed a chord link closed with a reason reporting "peer closed" to its close handlers instead when the peer…

  • Source: PiG's own defect
  • Symptom: Fixed a chord link closed with a reason reporting "peer closed" to its close handlers instead when the peer answered the goodbye before the closing side had marked the link closed; the link now ends first, with the reason it was closed with, as upstream's does.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Fixed a chord link closed with a reason reporting "peer closed" to its close handlers instead when the peer answered the goodbye before the closing side had marked the link closed; the link now ends first, with the reason it was closed with, as upstream's does.

BF-648ff3e-119

Concurrent extension select, confirm, input, editor dialogs and inline custom() components in…

  • Source: PiG's own defect
  • Symptom: Concurrent extension select, confirm, input, editor dialogs and inline custom() components in interactive mode no longer replace each other: a second dialog used to take the editor slot and leave the first one's promise pending.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Concurrent extension select, confirm, input, editor dialogs and inline custom() components in interactive mode no longer replace each other: a second dialog used to take the editor slot and leave the first one's promise pending. They stack, the last one is visible, closing it shows the previous one again, and a hidden dialog's abort or timeout removes only itself. /reload and session replacement dispose every stacked dialog, and an editor-slot custom component restores the editor text it found (Pi 38847563, #11166, #9909).

BF-648ff3e-12

Prompts submitted after /scoped-models refreshed the model catalogs and Ctrl+P switched the model no longer…

  • Source: PiG's own defect
  • Symptom: Prompts submitted after /scoped-models refreshed the model catalogs and Ctrl+P switched the model no longer fail with "No model selected".
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Prompts submitted after /scoped-models refreshed the model catalogs and Ctrl+P switched the model no longer fail with "No model selected". The scope update installed catalog descriptors that carry no backend; it now installs the Session's runtime-bound models, as Pi does.

BF-648ff3e-120

Pasting in the shell no longer hangs after copying text in pig and suspending it with Ctrl+Z on Linux: the…

  • Source: PiG's own defect
  • Symptom: Pasting in the shell no longer hangs after copying text in pig and suspending it with Ctrl+Z on Linux: the wl-copy, xclip or xsel process that keeps serving the clipboard now runs in its own session and is not stopped with pig (Pi f8ab362c, #11179).
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: Linux
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Pasting in the shell no longer hangs after copying text in pig and suspending it with Ctrl+Z on Linux: the wl-copy, xclip or xsel process that keeps serving the clipboard now runs in its own session and is not stopped with pig (Pi f8ab362c, #11179).

BF-648ff3e-123

mcp.json files that start with a UTF-8 BOM, which Windows PowerShell 5.1 adds when it rewrites a file, load…

  • Source: PiG's own defect
  • Symptom: mcp.json files that start with a UTF-8 BOM, which Windows PowerShell 5.1 adds when it rewrites a file, load their MCP servers again, and pig mcp add and pig mcp remove work on them (Pi bdd9f039, #11191).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: mcp.json files that start with a UTF-8 BOM, which Windows PowerShell 5.1 adds when it rewrites a file, load their MCP servers again, and pig mcp add and pig mcp remove work on them (Pi bdd9f039, #11191).

BF-648ff3e-124

A Go or Rust extension edited while it was being built could be cached under the hash of the source from…

  • Source: PiG's own defect
  • Symptom: A Go or Rust extension edited while it was being built could be cached under the hash of the source from before the edit.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A Go or Rust extension edited while it was being built could be cached under the hash of the source from before the edit. The compiler reads the source after the build hashed it, past the wait for another pig's build of the same extension and through its own compile, and nothing read it again, so an edit saved in that time was compiled into a build stored under the original source's hash. Putting the original source back then ran the build of the edit. A build now reads its inputs again when the compiler returns (the extension source, the SDK, workspace modules and toolchain) and caches nothing when they changed: it fails with changed while it was being built, and the next start builds the current source. A Rust crate's Cargo.lock, which cargo writes during the build, does not count as an edit.

BF-648ff3e-125

A Node extension that registers or unregisters a provider through ctx.modelRegistry (or…

  • Source: PiG's own defect
  • Symptom: A Node extension that registers or unregisters a provider through ctx.modelRegistry (or pi.registerProvider/unregisterProvider) sees the write on the next line, as Pi does: find, getAll, getAvailable, hasConfiguredAuth, getProviderAuthStatus and isUsingOAuth read the registry the registration left, and an unregistration removes the provider's models (a built-in provider keeps its own).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A Node extension that registers or unregisters a provider through ctx.modelRegistry (or pi.registerProvider/unregisterProvider) sees the write on the next line, as Pi does: find, getAll, getAvailable, hasConfiguredAuth, getProviderAuthStatus and isUsingOAuth read the registry the registration left, and an unregistration removes the provider's models (a built-in provider keeps its own). The runtime keeps its own writes over the host's registry snapshot until the host answers the call, so a publication that predates the write cannot undo it.

BF-648ff3e-126

ctx.abort() aborts the run's ctx.signal, and the signal a tool receives, before it returns in a Node…

  • Source: PiG's own defect
  • Symptom: ctx.abort() aborts the run's ctx.signal, and the signal a tool receives, before it returns in a Node extension, as Pi's agent.abort() does.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: ctx.abort() aborts the run's ctx.signal, and the signal a tool receives, before it returns in a Node extension, as Pi's agent.abort() does. The host sends the run's run_signal frame ahead of the reply to the abort call, so the Go, Python and Rust SDKs read the signal aborted as soon as abort() returns instead of when a watcher's frame happens to win the race.

BF-648ff3e-127

pi.getCommands() in a Node extension lists a command registered after load by the same turn's…

  • Source: PiG's own defect
  • Symptom: pi.getCommands() in a Node extension lists a command registered after load by the same turn's pi.registerCommand, as Pi's live command Map does.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: pi.getCommands() in a Node extension lists a command registered after load by the same turn's pi.registerCommand, as Pi's live command Map does.

BF-648ff3e-128

A Node extension tool that awaits real work and then takes the file mutation queue (the upstream subagent…

  • Source: PiG's own defect
  • Symptom: A Node extension tool that awaits real work and then takes the file mutation queue (the upstream subagent example, which writes a prompt file under withFileMutationQueue after a mkdtemp) no longer waits forever when it runs outside the dispatcher's parallel batch of an agent run, for instance when a tool is called directly or its batch runs sequentially.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A Node extension tool that awaits real work and then takes the file mutation queue (the upstream subagent example, which writes a prompt file under withFileMutationQueue after a mkdtemp) no longer waits forever when it runs outside the dispatcher's parallel batch of an agent run, for instance when a tool is called directly or its batch runs sequentially. The runtime holds a tool's next queue call after it reports that the handler awaits, until the host says the edit and write calls waiting for the tool's start window registered; a tool that holds no start window had nobody waiting, and the host never said so.

BF-648ff3e-129

RPC mode: an extension_ui_response whose value is not a string, whose confirmed is not a boolean, or…

  • Source: PiG's own defect
  • Symptom: RPC mode: an extension_ui_response whose value is not a string, whose confirmed is not a boolean, or whose cancelled is not a boolean no longer leaves the dialog pending.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: RPC mode: an extension_ui_response whose value is not a string, whose confirmed is not a boolean, or whose cancelled is not a boolean no longer leaves the dialog pending. The dialog resolves with the client's JSON as Pi's does: a Node, Go, Python or Rust extension sees a number, null, an object or an array from select, input and editor, and the client's confirmed from confirm; cancelled is read by JavaScript truthiness.

BF-648ff3e-13

Two parallel edit calls through two names of one existing file (a hard link, or a case alias on a…

  • Source: A Pi defect that PiG fixed first (divergence D190)
  • Symptom: Two parallel edit calls through two names of one existing file (a hard link, or a case alias on a case-insensitive filesystem such as the macOS default) no longer lose one of the edits.
  • Root cause: both edits of two parallel edit calls through two names of one file read the same original text and each writes the whole file, so the later write silently drops the earlier edit while both calls report success.
  • User impact: Lost or altered data
  • Platform: macOS
  • Fixed in: 0.5.2
  • Pi issue: none recorded
  • Removed when Pi fixes it: Pi orders hard-link aliases (and, on a case-insensitive filesystem where its realpath keeps case, case aliases) by file identity, or an owner decision reverses this one; then the identity check, its marker, the allowance test and this record go.
  • Fix commits: 2f03eb55db (2026-10-22, #288), 648ff3e2b0 (2026-10-22, #288)
  • Tests: internal/codingagent/tools/mutation_queue_alias_test.go::TestEditPreservesParallelAliasChanges, internal/codingagent/tools/mutation_queue_alias_test.go::TestHardLinkAliasesDifferFromPi, internal/codingagent/tools/mutation_queue_alias_test.go::TestMutationAliasReservationsKeepDispatchOrder, internal/codingagent/tools/mutation_queue_oracle_test.go::TestWithFileMutationQueueMatchesPi
  • Release note: Two parallel edit calls through two names of one existing file (a hard link, or a case alias on a case-insensitive filesystem such as the macOS default) no longer lose one of the edits. The built-in file mutation queue made them run at once, so both read the original text and the later write dropped the earlier edit while both calls reported success; it now orders them in call order. Pi does not order hard-link aliases (D190).

BF-648ff3e-130

A YAML date used as a mapping key is named with the time zone names of Node 24.22 and later, the Node Pi runs…

  • Source: PiG's own defect
  • Symptom: A YAML date used as a mapping key is named with the time zone names of Node 24.22 and later, the Node Pi runs on (tz 2026e).
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A YAML date used as a mapping key is named with the time zone names of Node 24.22 and later, the Node Pi runs on (tz 2026e). Morocco and Western Sahara print "Western European Standard Time" and "Western European Summer Time" instead of "GMT+00:00" and "GMT+01:00" (Node 24.22.1 does so from 2026-09-20 01:00 UTC, when Morocco returned to permanent UTC); EST5EDT, CST6CDT, MST7MDT and PST8PDT print their offset (GMT-05:00) instead of the names of New York, Chicago, Denver and Los Angeles; and the switches between standard and daylight names of Alberta, Manitoba, the Northwest Territories, Colombia (1992) and Iran (1979) follow tz 2026e. Node 22.19 and 24.19 print other names for these zones.

BF-648ff3e-14

The Cloudflare host's pi-durable facade sends Pi v1.4.0's client identity in every model request's options…

  • Source: PiG's own defect
  • Symptom: The Cloudflare host's pi-durable facade sends Pi v1.4.0's client identity in every model request's options (last key, after sessionId), merged over DEFAULT_HARNESS_IDENTITY (provider attribution to pi) with HarnessOptions.identity and a request's own identity, as Pi's Harness does through withClientIdentity (Pi adc5fa9f, 04a93bad).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The Cloudflare host's pi-durable facade sends Pi v1.4.0's client identity in every model request's options (last key, after sessionId), merged over DEFAULT_HARNESS_IDENTITY (provider attribution to pi) with HarnessOptions.identity and a request's own identity, as Pi's Harness does through withClientIdentity (Pi adc5fa9f, 04a93bad). The facade exports DEFAULT_HARNESS_IDENTITY.

BF-648ff3e-15

The Go, Rust and Python extension SDKs' isContextOverflow treats an HTTP 413 error as a context overflow…

  • Source: PiG's own defect
  • Symptom: The Go, Rust and Python extension SDKs' isContextOverflow treats an HTTP 413 error as a context overflow whatever the provider or the body says: a failed message that begins with 413 (then a space, a colon or the end) or with <provider> API error (413):.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The Go, Rust and Python extension SDKs' isContextOverflow treats an HTTP 413 error as a context overflow whatever the provider or the body says: a failed message that begins with 413 (then a space, a colon or the end) or with <provider> API error (413):. Carried forward from Pi 1.4.0 (packages/ai/src/utils/overflow.ts).

BF-648ff3e-16

The Go, Rust and Python extension SDKs' isRetryableAssistantError retries on a status code (429, 500, 502…

  • Source: PiG's own defect
  • Symptom: The Go, Rust and Python extension SDKs' isRetryableAssistantError retries on a status code (429, 500, 502, 503, 504, 520, 524) only when it stands alone, not inside a request id or another number, retries on stream disconnected before completion, and does not retry a ChatGPT Business workspace usage-limit error (subscription_sharing_workplace_individual_usage_limit_exceeded).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The Go, Rust and Python extension SDKs' isRetryableAssistantError retries on a status code (429, 500, 502, 503, 504, 520, 524) only when it stands alone, not inside a request id or another number, retries on stream disconnected before completion, and does not retry a ChatGPT Business workspace usage-limit error (subscription_sharing_workplace_individual_usage_limit_exceeded). Carried forward from Pi 6e491cfa, 29057ece and cedefc53.

BF-648ff3e-17

CI: TestNodeCellAddedMembersStayWithinTheirOwnState no longer fails under load

  • Source: PiG's own defect
  • Symptom: CI: TestNodeCellAddedMembersStayWithinTheirOwnState no longer fails under load.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: CI: TestNodeCellAddedMembersStayWithinTheirOwnState no longer fails under load. It compared the RSS of a one-member Node cell with the RSS of a five-member cell against 6 MiB per added member; one member's RSS read 131.8 to 152.2 MiB (6.4 to 27.5 MiB below its own peak) in 19 runs, so the figure ranged 0.8 to 7.2 MiB and failed once. The probe now reads the retained V8 heap plus external memory right after the forced collection, which gives 0.20 to 0.25 MiB per added member in every run, and the budget is 2 MiB.

BF-648ff3e-18

CI: a Node extension no longer holds a request the host sends it behind the highlight.js language load

  • Source: PiG's own defect
  • Symptom: CI: a Node extension no longer holds a request the host sends it behind the highlight.js language load.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: CI: a Node extension no longer holds a request the host sends it behind the highlight.js language load. In TUI mode the runtime loads Pi's complete grammar set after the startup events, and lib/index.js evaluates its ~190 grammars in one synchronous pass on the thread that serves requests: 125 ms on an idle machine and more than the five-second renderer inactivity lease (D56) on a starved one, so a Markdown transform, render or mouse event sent meanwhile was abandoned while the extension was healthy and the proxy returned its input (got "" in TestMarkdownContextsAcrossSDKs/subprocess-node-packed). Each grammar module is now required on its own event-loop turn before that pass, which only registers them as in Pi.

BF-648ff3e-19

CI: waitForRenderProxy, the wait of the render proxy and packed padding tests for the extension's answer…

  • Source: PiG's own defect
  • Symptom: CI: waitForRenderProxy, the wait of the render proxy and packed padding tests for the extension's answer, is bounded by the hang budget testbudget.Wait instead of a fixed one second; TestPackedMessageOutputPadAcrossSDKs/go failed once with "render proxy condition was not met" on a loaded host (not reproduced since: 0 of 108 runs under 150 busy loops, before and after).
  • Root cause: CI: waitForRenderProxy, the wait of the render proxy and packed padding tests for the extension's answer, is bounded by the hang budget testbudget.Wait instead of a fixed one second; TestPackedMessageOutputPadAcrossSDKs/go failed once with "render proxy condition was not met" on a loaded host (not reproduced since: 0 of 108 runs under 150 busy loops, before and after).
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: CI: waitForRenderProxy, the wait of the render proxy and packed padding tests for the extension's answer, is bounded by the hang budget testbudget.Wait instead of a fixed one second; TestPackedMessageOutputPadAcrossSDKs/go failed once with "render proxy condition was not met" on a loaded host (not reproduced since: 0 of 108 runs under 150 busy loops, before and after).

BF-648ff3e-2

A streamed child-session text costs wire bytes linear in its length: message_update events travel as a…

  • Source: PiG's own defect
  • Symptom: A streamed child-session text costs wire bytes linear in its length: message_update events travel as a keyframe followed by operations (set and append) and the SDK hands the listener the full event.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A streamed child-session text costs wire bytes linear in its length: message_update events travel as a keyframe followed by operations (set and append) and the SDK hands the listener the full event. A 4096-delta, 98 KB reply cost 406 MB on the connection and 826 MB of host allocation per prompt; it now costs 1.8 MB and 19 MB.

BF-648ff3e-20

CI: TestStartupReusesPreTrustExtensionsAcrossEntrypoints no longer reads an extension process that is still…

  • Source: PiG's own defect
  • Symptom: CI: TestStartupReusesPreTrustExtensionsAcrossEntrypoints no longer reads an extension process that is still dying as a leak.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: CI: TestStartupReusesPreTrustExtensionsAcrossEntrypoints no longer reads an extension process that is still dying as a leak. The RPC entrypoint SIGKILLs its extension processes and exits without waiting, and the test polled kill(pid, 0) for five seconds, which also succeeds while the killed process waits for a CPU turn and while it is an unreaped zombie. The test now waits for the process's exit event (pidfd, kqueue NOTE_EXIT, process handle).

BF-648ff3e-21

CI: the scoped-model selector parity scenarios no longer capture the selector before the catalog refresh…

  • Source: PiG's own defect
  • Symptom: CI: the scoped-model selector parity scenarios no longer capture the selector before the catalog refresh settles.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: CI: the scoped-model selector parity scenarios no longer capture the selector before the catalog refresh settles. Both binaries show "Refreshing model catalogs…" until refreshModelCatalogs settles and "Model catalogs refreshed." after it. 13-scoped-models-fuzzy-filter finished at "> mtw", and 08-scoped-models-selector and 13-scoped-google-catalog waited a fixed two seconds, so under load a capture could hold either binary's refreshing line (6 of 25 runs of the first and 21 of 50 of the other two failed on main with 250 busy loops); each step now waits for the status on the visible screen.

BF-648ff3e-22

CI: a Node extension no longer holds the first request the host sends it behind its bytecode-cache flush

  • Source: PiG's own defect
  • Symptom: CI: a Node extension no longer holds the first request the host sends it behind its bytecode-cache flush.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: CI: a Node extension no longer holds the first request the host sends it behind its bytecode-cache flush. The runtime flushed Node's compile cache when it processed the host's ready message, on the thread that serves requests; the flush serializes every compiled module and took 4.6 s on a starved machine, so a Markdown transform sent right after registration passed the five-second renderer inactivity lease (D56) while the extension was healthy and the proxy returned its input (got "" in TestMarkdownContextsAcrossSDKs/subprocess-node-packed). The flush now runs when the factory has loaded, before the member registers.

BF-648ff3e-23

CI: TestTerminalProgramGetsDefaultSignalsWhenTheDaemonStartedIgnoringThem no longer fails every run under…

  • Source: PiG's own defect
  • Symptom: CI: TestTerminalProgramGetsDefaultSignalsWhenTheDaemonStartedIgnoringThem no longer fails every run under make test-fast.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: CI: TestTerminalProgramGetsDefaultSignalsWhenTheDaemonStartedIgnoringThem no longer fails every run under make test-fast. It required every ignored-signal bit of the terminal's program to be clear, but Pi's pty.rs (and PiG's daemon, which mirrors it) resets signals 1 through 31 only, and GNU make 4.4.1 starts its recipes with signals 32 and 33 ignored, so the program ended with SigIgn 0x80000000 under both daemons. The test asserts signals 1 through 31, the daemon's contract.

BF-648ff3e-24

A package manifest glob walks as Node 24.22 and later walk it

  • Source: PiG's own defect
  • Symptom: A package manifest glob walks as Node 24.22 and later walk it.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A package manifest glob walks as Node 24.22 and later walk it. Pi runs on that Node, and its globSync no longer ends a directory listing at an entry the walk already holds, nor queues the segment after a globstar below a symlink: a/**/../*.md lists the root's .md files as well, Sub/**/../* lists the siblings of Sub, and **/x/**/y lists every x/**/y below the root.

BF-648ff3e-25

An HTTP 413 from any provider is treated as request-size overflow, so the agent compacts and retries instead…

  • Source: PiG's own defect
  • Symptom: An HTTP 413 from any provider is treated as request-size overflow, so the agent compacts and retries instead of showing a raw API error.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: An HTTP 413 from any provider is treated as request-size overflow, so the agent compacts and retries instead of showing a raw API error. GitHub Copilot answers a request over its byte-size limit (for example a history of many images) with a 413 and a generic failed to parse request body that the token-count patterns never matched (Pi 73f8de80, #11016).

BF-648ff3e-26

On Windows, PiG finds PowerShell in its installation directories when it is not on PATH: PowerShell 7 under…

  • Source: PiG's own defect
  • Symptom: On Windows, PiG finds PowerShell in its installation directories when it is not on PATH: PowerShell 7 under %ProgramFiles%\PowerShell\7 after PATH, and Windows PowerShell under %SystemRoot%\System32\WindowsPowerShell\v1.0 as the last fallback, on any drive (Pi 61060ddf, #9490).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: On Windows, PiG finds PowerShell in its installation directories when it is not on PATH: PowerShell 7 under %ProgramFiles%\PowerShell\7 after PATH, and Windows PowerShell under %SystemRoot%\System32\WindowsPowerShell\v1.0 as the last fallback, on any drive (Pi 61060ddf, #9490).

BF-648ff3e-27

On Windows, the legacy WSL bash.exe launcher is recognized under the actual Windows directory in…

  • Source: PiG's own defect
  • Symptom: On Windows, the legacy WSL bash.exe launcher is recognized under the actual Windows directory in SystemRoot (System32 and Sysnative), not only under C:\Windows, so its command goes over stdin (Pi 61060ddf, #9490).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: Windows, WSL
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: On Windows, the legacy WSL bash.exe launcher is recognized under the actual Windows directory in SystemRoot (System32 and Sysnative), not only under C:\Windows, so its command goes over stdin (Pi 61060ddf, #9490).

BF-648ff3e-28

On Windows, an empty SystemRoot takes C:\Windows for taskkill.exe, as an unset one already did, in the…

  • Source: PiG's own defect
  • Symptom: On Windows, an empty SystemRoot takes C:\Windows for taskkill.exe, as an unset one already did, in the shell tools and the Durable node environment (Pi 61060ddf, #9490).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: On Windows, an empty SystemRoot takes C:\Windows for taskkill.exe, as an unset one already did, in the shell tools and the Durable node environment (Pi 61060ddf, #9490).

BF-648ff3e-29

On Windows, shells that are runnable Microsoft Store aliases are accepted: a configured shell path…

  • Source: PiG's own defect
  • Symptom: On Windows, shells that are runnable Microsoft Store aliases are accepted: a configured shell path, bash.exe and pwsh.exe found by where, and the Git Bash locations are checked with an attribute check (access(F_OK)) instead of a stat that follows the alias (Pi 28bea006, nodejs/node#36790).
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: Windows
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: On Windows, shells that are runnable Microsoft Store aliases are accepted: a configured shell path, bash.exe and pwsh.exe found by where, and the Git Bash locations are checked with an attribute check (access(F_OK)) instead of a stat that follows the alias (Pi 28bea006, nodejs/node#36790). Not yet run on a Windows host.

BF-648ff3e-3

coding.CreateAgentSession binds the Session it returns to the Extension Host its resource loader started…

  • Source: PiG's own defect
  • Symptom: coding.CreateAgentSession binds the Session it returns to the Extension Host its resource loader started, as it does for a child session an extension creates: the loader's extensions read the Session's model, scopedModels, idle state, tools and session, with no UI.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: coding.CreateAgentSession binds the Session it returns to the Extension Host its resource loader started, as it does for a child session an extension creates: the loader's extensions read the Session's model, scopedModels, idle state, tools and session, with no UI. The helpers that bind a Session to subprocess extensions in every mode (ExtensionHostSessionActions, BindSessionExtensionActions, BindSessionReadActions, BindSessionModelOperations, WatchSessionRunSignal) moved from the CLI package to coding, so print, JSON, RPC and interactive mode and a library caller run the same code.

BF-648ff3e-30

URL parsing follows Node 24.22 and later

  • Source: PiG's own defect
  • Symptom: URL parsing follows Node 24.22 and later.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: URL parsing follows Node 24.22 and later. Pi runs on that Node, whose URL parser only lowercases an ASCII host: https://xn--.example and ftp:xn--mailto: are valid, file://xn--a.b/x is a UNC path on Windows, and a provider or Radius base such as file:///C:ab/c or file:///u:p@[::1]:9000 no longer keeps its first path segment as a drive letter when a path is resolved against it.

BF-648ff3e-31

A packed Go, Python or Rust cell no longer hangs when a later member's factory calls the host before an…

  • Source: PiG's own defect
  • Symptom: A packed Go, Python or Rust cell no longer hangs when a later member's factory calls the host before an earlier member's factory has run.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A packed Go, Python or Rust cell no longer hangs when a later member's factory calls the host before an earlier member's factory has run. The generated runner started every member on a thread of its own and let them race for the factory turn; the host accepts the members in order and serves a connection only after the members before it, so a later member whose factory subscribed to the event bus held the turn and waited for a host that waited for the earlier member. The runner now starts each member's factory in the cell's order.

BF-648ff3e-32

An extension's ctx.hasPendingMessages() now asks the Host instead of reading a cached state snapshot, so it…

  • Source: PiG's own defect
  • Symptom: An extension's ctx.hasPendingMessages() now asks the Host instead of reading a cached state snapshot, so it reports a steering or follow-up message queued while a handler is awaited, as Pi's live getter does.
  • Root cause: Extensions that gate delivery on that flag, such as pi-queue-steer, no longer report Pi did not accept the queued message at this delivery boundary after a message that was delivered.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: An extension's ctx.hasPendingMessages() now asks the Host instead of reading a cached state snapshot, so it reports a steering or follow-up message queued while a handler is awaited, as Pi's live getter does. Extensions that gate delivery on that flag, such as pi-queue-steer, no longer report Pi did not accept the queued message at this delivery boundary after a message that was delivered.

BF-648ff3e-33

Durable with telemetry off (no telemetry or the no-op span) no longer builds span attributes, events or…

  • Source: PiG's own defect
  • Symptom: Durable with telemetry off (no telemetry or the no-op span) no longer builds span attributes, events or metric attribute bags, and ai.Models.Stream without a parent span captures nothing for the telemetry observer, so both allocate as before the telemetry integration; Durable's reflective restore also caches each struct type's field list.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Durable with telemetry off (no telemetry or the no-op span) no longer builds span attributes, events or metric attribute bags, and ai.Models.Stream without a parent span captures nothing for the telemetry observer, so both allocate as before the telemetry integration; Durable's reflective restore also caches each struct type's field list. The AI telemetry schema is built on first use instead of at process start, and non-numeric attribute names no longer allocate in jsstring.ArrayIndex, which cuts the allocations of a recorded ai stream.

BF-648ff3e-34

The grep tool passes --no-config to ripgrep, as Pi does at 8c4e8738, so a user's RIPGREP_CONFIG_PATH…

  • Source: PiG's own defect
  • Symptom: The grep tool passes --no-config to ripgrep, as Pi does at 8c4e8738, so a user's RIPGREP_CONFIG_PATH file cannot change the tool's contract (literal, glob, limit).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The grep tool passes --no-config to ripgrep, as Pi does at 8c4e8738, so a user's RIPGREP_CONFIG_PATH file cannot change the tool's contract (literal, glob, limit).

BF-648ff3e-35

Recovery of a truncated assistant attempt (retry and overflow/length compaction) finds the tool results that…

  • Source: PiG's own defect
  • Symptom: Recovery of a truncated assistant attempt (retry and overflow/length compaction) finds the tool results that answered the attempt's calls in the agent transcript and omits them with it, so a resumed session no longer persists orphaned tool results that providers reject (Pi 02726086, #11227).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Recovery of a truncated assistant attempt (retry and overflow/length compaction) finds the tool results that answered the attempt's calls in the agent transcript and omits them with it, so a resumed session no longer persists orphaned tool results that providers reject (Pi 02726086, #11227).

BF-648ff3e-36

Classifier models take images (Pi 7f225f25): models.classify() accepts images for TypeSafe's Jev and…

  • Source: PiG's own defect
  • Symptom: Classifier models take images (Pi 7f225f25): models.classify() accepts images for TypeSafe's Jev and Cloudflare's Clef models, and provider-level compat in models.json now also applies to classifier models, with the System One fields imageInput and sendImageOptInHeader.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Classifier models take images (Pi 7f225f25): models.classify() accepts images for TypeSafe's Jev and Cloudflare's Clef models, and provider-level compat in models.json now also applies to classifier models, with the System One fields imageInput and sendImageOptInHeader. A model without image input rejects images with a clear error.

BF-648ff3e-37

The catalog tolerates the free field Pi v1.4.0's radius data carries (recursive/flint), keeping it in the…

  • Source: PiG's own defect
  • Symptom: The catalog tolerates the free field Pi v1.4.0's radius data carries (recursive/flint), keeping it in the model record as Pi does.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The catalog tolerates the free field Pi v1.4.0's radius data carries (recursive/flint), keeping it in the model record as Pi does.

BF-648ff3e-38

The upstream pin moves to Pi v1.4.0

  • Source: PiG's own defect
  • Symptom: The upstream pin moves to Pi v1.4.0.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The upstream pin moves to Pi v1.4.0.

BF-648ff3e-39

The durable turn classifier retries on a status code (429, 500, 502, 503, 504, 520, 524) only when it stands…

  • Source: PiG's own defect
  • Symptom: The durable turn classifier retries on a status code (429, 500, 502, 503, 504, 520, 524) only when it stands alone, not inside a request id or another number, and on stream disconnected before completion; a ChatGPT Business workspace usage-limit error is not retried.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The durable turn classifier retries on a status code (429, 500, 502, 503, 504, 520, 524) only when it stands alone, not inside a request id or another number, and on stream disconnected before completion; a ChatGPT Business workspace usage-limit error is not retried. Carried forward from Pi 6e491cfa, 29057ece and cedefc53.

BF-648ff3e-4

Extension tools of one parallel batch reach the host in Pi's order

  • Source: PiG's own defect
  • Symptom: Extension tools of one parallel batch reach the host in Pi's order.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Extension tools of one parallel batch reach the host in Pi's order. Pi runs a batch on one thread, so the host calls the tools make in their first tick (appendEntry, ui.notify, ui.setStatus, setActiveTools, sendMessage, and the rest) apply in the order the extension sent them, whichever tool made each; before, eight parallel tools that each called the host applied their calls in scheduler order, and in a Node extension that also reordered the tools' answers, so tool_execution_end and the tool_result hooks came in a different order on every run.

BF-648ff3e-44

Automatic retry no longer treats digits inside a request id or another number (for example req_011CU503xyz…

  • Source: PiG's own defect
  • Symptom: Automatic retry no longer treats digits inside a request id or another number (for example req_011CU503xyz or 5242880 bytes) as a retryable HTTP status code: 429, 500, 502, 503, 504, 520 and 524 count only as standalone numbers.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Automatic retry no longer treats digits inside a request id or another number (for example req_011CU503xyz or 5242880 bytes) as a retryable HTTP status code: 429, 500, 502, 503, 504, 520 and 524 count only as standalone numbers. Carried forward from Pi 6e491cfa (earendil-works/pi#11057).

BF-648ff3e-45

Automatic retry now triggers for stream disconnected before completion errors that a proxy wrapping the…

  • Source: PiG's own defect
  • Symptom: Automatic retry now triggers for stream disconnected before completion errors that a proxy wrapping the Codex CLI relays.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Automatic retry now triggers for stream disconnected before completion errors that a proxy wrapping the Codex CLI relays. Carried forward from Pi 29057ece (earendil-works/pi#11057).

BF-648ff3e-46

Sign in with ChatGPT: a ChatGPT Business workspace usage-limit error…

  • Source: PiG's own defect
  • Symptom: Sign in with ChatGPT: a ChatGPT Business workspace usage-limit error (subscription_sharing_workplace_individual_usage_limit_exceeded) is not retried, and its message tells the user to ask their workspace admin to increase the limit.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Sign in with ChatGPT: a ChatGPT Business workspace usage-limit error (subscription_sharing_workplace_individual_usage_limit_exceeded) is not retried, and its message tells the user to ask their workspace admin to increase the limit. Carried forward from Pi cedefc53.

BF-648ff3e-47

Context and the types that hold one (EventBus, ModelRegistry, ReplacedSessionContext…

  • Source: PiG's own defect
  • Symptom: Context and the types that hold one (EventBus, ModelRegistry, ReplacedSessionContext, SessionManager, SetupSessionManager) in the Go extension SDK are comparable again: the tool-call input a Context carries (ToolInput, ToolParams) is a pointer to the request's arguments instead of a slice, so adding it is no longer an incompatible change.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Context and the types that hold one (EventBus, ModelRegistry, ReplacedSessionContext, SessionManager, SetupSessionManager) in the Go extension SDK are comparable again: the tool-call input a Context carries (ToolInput, ToolParams) is a pointer to the request's arguments instead of a slice, so adding it is no longer an incompatible change.

BF-648ff3e-48

pi.events.emit in a Go, Rust or Python factory behaves as Pi's does: the first factory-time bus call…

  • Source: PiG's own defect
  • Symptom: The emit was queued until after ready, and its failure only reached stderr.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: pi.events.emit in a Go, Rust or Python factory behaves as Pi's does: the first factory-time bus call connects to the host (as parseFrontmatter does) and Run reuses the connection; a listener the factory subscribes is registered with the host at once and answers the host's dispatches on that connection while the factory waits for a host call, so it hears another extension's emit made while the factory runs, a nested reply to the factory's own emit included, as Pi's shared emitter delivers it; the call returns once the host has delivered the emit to every listener, in registration order (the extensions loaded before this one, then this one's own), and each has finished; a listener that throws or panics is written to stderr as Event handler error (channel): and reaches neither the emitter nor the next listener; an error emit that nobody hears fails with the emitted error. Before, the emit was queued until after ready, and its failure only reached stderr. A factory with no host (no PIG_EXT_SOCKET) gets an error from its first bus call. Go gains sdk.ServeFactory(conn, factory) for a fused binary, which gives the factory the pipe as its connection.

BF-648ff3e-49

The Python SDK test test_provider_transport_shutdown_does_not_settle_owned_stream[True-*] no longer fails…

  • Source: PiG's own defect
  • Symptom: The Python SDK test test_provider_transport_shutdown_does_not_settle_owned_stream[True-*] no longer fails when the worker thread is still returning at the moment the shutdown finds no thread to wait for: it waits for the worker, as the assertion means "ends", not "has already ended".
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The Python SDK test test_provider_transport_shutdown_does_not_settle_owned_stream[True-*] no longer fails when the worker thread is still returning at the moment the shutdown finds no thread to wait for: it waits for the worker, as the assertion means "ends", not "has already ended".

BF-648ff3e-5

The tool_result handlers of a parallel batch start in the order the tools settled, each running to its…

  • Source: PiG's own defect
  • Symptom: The tool_result handlers of a parallel batch start in the order the tools settled, each running to its first await before the next one starts, and a tool's onUpdate reaches the listeners in the order the tools called it, across tools; before, both followed goroutine wake order.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The tool_result handlers of a parallel batch start in the order the tools settled, each running to its first await before the next one starts, and a tool's onUpdate reaches the listeners in the order the tools called it, across tools; before, both followed goroutine wake order.

BF-648ff3e-50

A Go extension SDK loop that is served without Run (a test on its own connection) no longer waits for a…

  • Source: PiG's own defect
  • Symptom: A Go extension SDK loop that is served without Run (a test on its own connection) no longer waits for a loaded gate that never closes; TestToolRequestAllocBudget hung for the whole test timeout.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A Go extension SDK loop that is served without Run (a test on its own connection) no longer waits for a loaded gate that never closes; TestToolRequestAllocBudget hung for the whole test timeout.

BF-648ff3e-51

The Node extension API probe of a failed factory (TestUpstreamFailedFactoryDisablesCapturedAPI) names…

  • Source: PiG's own defect
  • Symptom: The Node extension API probe of a failed factory (TestUpstreamFailedFactoryDisablesCapturedAPI) names onLoaded, which the runtime already rejects after a failed load; the test failed with an uncaught assertion since onLoaded was added.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The Node extension API probe of a failed factory (TestUpstreamFailedFactoryDisablesCapturedAPI) names onLoaded, which the runtime already rejects after a failed load; the test failed with an uncaught assertion since onLoaded was added.

BF-648ff3e-52

A listener a Go, Rust or Python factory subscribes hears another extension's emit made while the factory…

  • Source: PiG's own defect
  • Symptom: The listener was registered when Run connected and missed such an emit.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A listener a Go, Rust or Python factory subscribes hears another extension's emit made while the factory runs, including a nested reply to the factory's own emit, as Pi's shared emitter delivers it: the listener is registered with the host when it is subscribed, a factory emit goes only through the host's ordered registry, and the factory's connection answers the host's dispatches while the factory waits for a host call. Before, the listener was registered when Run connected and missed such an emit.

BF-648ff3e-53

A command that an extension started with pi.exec and an AbortSignal kept running after the signal…

  • Source: PiG's own defect
  • Symptom: A command that an extension started with pi.exec and an AbortSignal kept running after the signal aborted: the signal never reached the host in the Go, Rust and Python SDKs, and the Node runtime sent it as an empty object.
  • Root cause: A command that an extension started with pi.exec and an AbortSignal kept running after the signal aborted: the signal never reached the host in the Go, Rust and Python SDKs, and the Node runtime sent it as an empty object.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A command that an extension started with pi.exec and an AbortSignal kept running after the signal aborted: the signal never reached the host in the Go, Rust and Python SDKs, and the Node runtime sent it as an empty object. In print, JSON and RPC mode the command did not even end with the request, because the host ran it under a context that nothing cancels.

BF-648ff3e-54

Extension API parity ledger: removed the stale ctx.ui.custom(options.onHandle) exception (Go, Rust and…

  • Source: PiG's own defect
  • Symptom: Extension API parity ledger: removed the stale ctx.ui.custom(options.onHandle) exception (Go, Rust and Python implement the overlay handle; TestCustomOverlayHandleSDKsMatch runs it on every SDK), reworded the keybinding-hint, highlight and createAgentSession rows that said "Node runtime only", and added a "Native package-export gaps" table naming the closing lane for every export Node serves and the native SDKs lack.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Extension API parity ledger: removed the stale ctx.ui.custom(options.onHandle) exception (Go, Rust and Python implement the overlay handle; TestCustomOverlayHandleSDKsMatch runs it on every SDK), reworded the keybinding-hint, highlight and createAgentSession rows that said "Node runtime only", and added a "Native package-export gaps" table naming the closing lane for every export Node serves and the native SDKs lack.

BF-648ff3e-55

Conformance fixtures now reach the realized SDK export cells the export ledger listed as unreached: the…

  • Source: PiG's own defect
  • Symptom: Conformance fixtures now reach the realized SDK export cells the export ledger listed as unreached: the editor component's insert_text_at_cursor/add_to_history overrides (Go, Rust, Python), the Go Editor queries and setters, DEFAULT_MAX_LINES/DEFAULT_MAX_BYTES, the session manager's isPersisted, usesDefaultSessionDir, getChildren and buildSessionContext, Rust's typed serialize_conversation and strip_frontmatter in Python, each compared with Pi's own answer on the Node runtime or the host's.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Conformance fixtures now reach the realized SDK export cells the export ledger listed as unreached: the editor component's insert_text_at_cursor/add_to_history overrides (Go, Rust, Python), the Go Editor queries and setters, DEFAULT_MAX_LINES/DEFAULT_MAX_BYTES, the session manager's isPersisted, usesDefaultSessionDir, getChildren and buildSessionContext, Rust's typed serialize_conversation and strip_frontmatter in Python, each compared with Pi's own answer on the Node runtime or the host's. pi-tui parseColor (Python) and styleTextWithAnsi (Python, Rust) move from native to gap:SdkTuiHelpers: the Python parse_color validates a wire color, not Pi's parseColor, and the Rust function is crate-private.

BF-648ff3e-56

Releasing a handle of an object a child session was created over (a ModelRuntime first of all) no longer…

  • Source: PiG's own defect
  • Symptom: An extension that built a runtime, created a child over it and let the runtime go out of scope had the runtime's model tasks stopped while the child still ran on it.
  • Root cause: Not recorded.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Releasing a handle of an object a child session was created over (a ModelRuntime first of all) no longer closes the object under the session. The host counts the holders of every session graph object, the extension's handle and each child session created over it, and releases the object when the last one lets go. Before, an extension that built a runtime, created a child over it and let the runtime go out of scope had the runtime's model tasks stopped while the child still ran on it.

BF-648ff3e-57

Removing a local package reports start and complete remove progress events, as Pi's remove does for…

  • Source: PiG's own defect
  • Symptom: Removing a local package reports start and complete remove progress events, as Pi's remove does for every source kind.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Removing a local package reports start and complete remove progress events, as Pi's remove does for every source kind.

BF-648ff3e-58

registerProvider of a model without an api fails with Pi's Set it at model level or provider level.…

  • Source: PiG's own defect
  • Symptom: registerProvider of a model without an api fails with Pi's Set it at model level or provider level. wording.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: registerProvider of a model without an api fails with Pi's Set it at model level or provider level. wording.

BF-648ff3e-59

An extension's SessionManager.appendContextEdit(id, null) clears the content instead of failing, and…

  • Source: PiG's own defect
  • Symptom: An extension's SessionManager.appendContextEdit(id, null) clears the content instead of failing, and appendCompaction without fromHook writes no fromHook member.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: An extension's SessionManager.appendContextEdit(id, null) clears the content instead of failing, and appendCompaction without fromHook writes no fromHook member.

BF-648ff3e-6

The Go, Python and Rust SDKs start the handlers of concurrent event and command requests in the order the…

  • Source: PiG's own defect
  • Symptom: The Go, Python and Rust SDKs start the handlers of concurrent event and command requests in the order the requests arrive, as they already did for tool_call; before, two tool_result hooks in flight on one connection could start their handlers out of order (the Go SDK did so in 25 of 3000 probes).
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The Go, Python and Rust SDKs start the handlers of concurrent event and command requests in the order the requests arrive, as they already did for tool_call; before, two tool_result hooks in flight on one connection could start their handlers out of order (the Go SDK did so in 25 of 3000 probes).

BF-648ff3e-60

Extension SDKs: formatKeyText/keyDisplayText upper-cased the first rune of a part, where Pi's…

  • Source: PiG's own defect
  • Symptom: Extension SDKs: formatKeyText/keyDisplayText upper-cased the first rune of a part, where Pi's charAt(0).toUpperCase() leaves a character beyond the Basic Multilingual Plane (half a surrogate pair) as it is.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Extension SDKs: formatKeyText/keyDisplayText upper-cased the first rune of a part, where Pi's charAt(0).toUpperCase() leaves a character beyond the Basic Multilingual Plane (half a surrogate pair) as it is.

BF-648ff3e-61

Extensions: ctx.ui.setTheme(theme) with a theme object makes that object the active theme as it is, as Pi's…

  • Source: PiG's own defect
  • Symptom: Extensions: ctx.ui.setTheme(theme) with a theme object makes that object the active theme as it is, as Pi's setThemeInstance does.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Extensions: ctx.ui.setTheme(theme) with a theme object makes that object the active theme as it is, as Pi's setThemeInstance does. PiG re-created the object in the terminal's color depth, so a truecolor theme set on a terminal without truecolor became a 256-color copy (and the other way round).

BF-648ff3e-62

A PiG TLS client no longer accepts a certificate chain through a compromised eMudhra intermediate or a root…

  • Source: PiG's own defect
  • Symptom: A PiG TLS client no longer accepts a certificate chain through a compromised eMudhra intermediate or a root Mozilla removed after it; the error names the CA.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A PiG TLS client no longer accepts a certificate chain through a compromised eMudhra intermediate or a root Mozilla removed after it; the error names the CA.

BF-648ff3e-63

The CI images no longer ship the three removed emSign roots: ca-certificates 20261020-r0 in the ci-go lock…

  • Source: PiG's own defect
  • Symptom: The CI images no longer ship the three removed emSign roots: ca-certificates 20261020-r0 in the ci-go lock, 20260909-r3 and a newer Wolfi base (glibc 2.44) in the ci-parity lock, which also takes c-ares 1.34.9 and nodejs-24 24.22.1.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The CI images no longer ship the three removed emSign roots: ca-certificates 20261020-r0 in the ci-go lock, 20260909-r3 and a newer Wolfi base (glibc 2.44) in the ci-parity lock, which also takes c-ares 1.34.9 and nodejs-24 24.22.1.

BF-648ff3e-64

Shutting a host down while a child session is live no longer leaves its extension host running

  • Source: PiG's own defect
  • Symptom: Shutting a host down while a child session is live no longer leaves its extension host running.
  • Root cause: A session an extension created with createAgentSession outlives the extension's dispose() of it, and a DefaultResourceLoader an extension built outlives the extension's handle while a session uses it; the host closed both only when the extension's connection closed, on a goroutine Shutdown did not wait for, and never stopped the loader's extension host.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Shutting a host down while a child session is live no longer leaves its extension host running. A session an extension created with createAgentSession outlives the extension's dispose() of it, and a DefaultResourceLoader an extension built outlives the extension's handle while a session uses it; the host closed both only when the extension's connection closed, on a goroutine Shutdown did not wait for, and never stopped the loader's extension host. Shutdown now closes every live child session and releases every object it held before it returns, and a released loader stops the extension host it started.

BF-648ff3e-65

Extensions: in a packed Go, Rust or Python cell, a member that stops or is replaced no longer releases a file…

  • Source: PiG's own defect
  • Symptom: Extensions: in a packed Go, Rust or Python cell, a member that stops or is replaced no longer releases a file mutation turn (withFileMutationQueue) that another member's task still holds.
  • Root cause: Not recorded.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Extensions: in a packed Go, Rust or Python cell, a member that stops or is replaced no longer releases a file mutation turn (withFileMutationQueue) that another member's task still holds. The turns of a packed process now belong to the process: they are released by fileMutation.exit over any of its connections, or when its last connection closes.

BF-648ff3e-66

Extensions: withFileMutationQueue is now one queue for the built-in edit and write tools and every…

  • Source: PiG's own defect
  • Symptom: Each extension process kept its own queue, so a Go, Rust, Python or Node extension tool and a built-in tool on one file, or two extensions, could lose an update.
  • Root cause: Not recorded.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Extensions: withFileMutationQueue is now one queue for the built-in edit and write tools and every extension process, as in Pi. Before, each extension process kept its own queue, so a Go, Rust, Python or Node extension tool and a built-in tool on one file, or two extensions, could lose an update. The SDKs ask the host's queue for the file's turn (fileMutation.enter, fileMutation.exit); the turn is released if the extension's connection closes.

BF-648ff3e-67

A Python extension's ctx.session_name and a Rust extension's ctx.session_name() returned an empty string…

  • Source: PiG's own defect
  • Symptom: A Python extension's ctx.session_name and a Rust extension's ctx.session_name() returned an empty string for the life of the process, because nothing ever read the name from the state the host sends.
  • Root cause: A Python extension's ctx.session_name and a Rust extension's ctx.session_name() returned an empty string for the life of the process, because nothing ever read the name from the state the host sends.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A Python extension's ctx.session_name and a Rust extension's ctx.session_name() returned an empty string for the life of the process, because nothing ever read the name from the state the host sends. Every SDK now keeps the session name current from each state update, and a Go extension has the same accessor, Context.SessionName.

BF-648ff3e-68

ctx.model is the model's id in every SDK, as in Pi (ctx.model.id)

  • Source: PiG's own defect
  • Symptom: ctx.model is the model's id in every SDK, as in Pi (ctx.model.id).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: ctx.model is the model's id in every SDK, as in Pi (ctx.model.id). A Python extension's ctx.model and a Rust extension's ctx.model() returned the model's display name (Claude Sonnet 4.5), so a comparison with claude-sonnet-4-5 or a provider/model string built from it failed there and passed in Go. Python and Rust also have model_provider and model_qualified, as Go has ModelProvider and ModelQualified.

BF-648ff3e-69

A read of the session name, the session log or the model right after setLabel, setSessionName…

  • Source: PiG's own defect
  • Symptom: A read of the session name, the session log or the model right after setLabel, setSessionName, sendMessage or setModel could return the old value in a Go, Python or Rust extension.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A read of the session name, the session log or the model right after setLabel, setSessionName, sendMessage or setModel could return the old value in a Go, Python or Rust extension. The host now sends its state before the result of those calls, and the Go SDK waits for it, so the next read sees the write as it does in Pi.

BF-648ff3e-7

An edit or write call of a parallel batch registers in the file mutation queue after the extension tools…

  • Source: PiG's own defect
  • Symptom: The built-in call registered in the dispatcher's loop, a process hop before the extension's registration arrived, so [extension tool, write] applied the write first.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: An edit or write call of a parallel batch registers in the file mutation queue after the extension tools before it in the batch took the queue, as Pi does: Pi registers a call in the queue when it calls withFileMutationQueue and runs each call's first statements in call order. Before, the built-in call registered in the dispatcher's loop, a process hop before the extension's registration arrived, so [extension tool, write] applied the write first. The Go, Python, Rust and Node SDKs, isolated, packed and fused, now agree. A native tool that waits on something other than a host call before it takes the queue still registers first, and a write behind a native tool that makes no host call waits for the tool; both are recorded in the parity row.

BF-648ff3e-70

An extension that reads ctx.model, pi.getSettings(), pi.getSessionName(), pi.getThinkingLevel() or…

  • Source: PiG's own defect
  • Symptom: An extension that reads ctx.model, pi.getSettings(), pi.getSessionName(), pi.getThinkingLevel() or its footer's git branch and provider count from a timer, thread or goroutine between requests now sees a change the host makes itself.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: An extension that reads ctx.model, pi.getSettings(), pi.getSessionName(), pi.getThinkingLevel() or its footer's git branch and provider count from a timer, thread or goroutine between requests now sees a change the host makes itself. A model switch (Ctrl+P, /model, cycling, RPC set_model), a settings edit, /name or RPC set_session_name, a resumed session, a branch checkout and a login updated the host but reached an extension only at its next request, so a background reader held the old value; Pi reads the live session. The host now pushes the new state to every connected extension when it changes, off the UI loop, and sends nothing to an extension that already holds it.

BF-648ff3e-71

In a Rust extension's before_agent_start handler, ctx.get_system_prompt() returns the prompt the…

  • Source: PiG's own defect
  • Symptom: In a Rust extension's before_agent_start handler, ctx.get_system_prompt() returns the prompt the handler's own edit of systemPromptOptions renders, as Pi's ctx.getSystemPrompt() and the Go, Python and Node SDKs do.
  • Root cause: It returned the prompt rendered before the edit, because the handler held the event exclusively and the context could not read it.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: In a Rust extension's before_agent_start handler, ctx.get_system_prompt() returns the prompt the handler's own edit of systemPromptOptions renders, as Pi's ctx.getSystemPrompt() and the Go, Python and Node SDKs do. It returned the prompt rendered before the edit, because the handler held the event exclusively and the context could not read it. The handler now edits the options through the BeforeAgentStartEvent the context shares, and a read after an edit asks the host to render the edited options.

BF-648ff3e-72

RPC mode no longer writes entry_appended for a pi.appendEntry or session_info_changed for a…

  • Source: PiG's own defect
  • Symptom: RPC mode no longer writes entry_appended for a pi.appendEntry or session_info_changed for a pi.setSessionName that an extension's session_start handler makes, and --mode json no longer writes anything a session_start handler produced: its entry_appended, session_info_changed, and the message_start and message_end of a sendMessage.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: RPC mode no longer writes entry_appended for a pi.appendEntry or session_info_changed for a pi.setSessionName that an extension's session_start handler makes, and --mode json no longer writes anything a session_start handler produced: its entry_appended, session_info_changed, and the message_start and message_end of a sendMessage. Pi subscribes its stdout writer after the extensions are bound, so none of these reach a client; the JSON stream now starts at the prompt, after the session header. The session_start of a replacement command (new_session, switch_session, fork) that runs after the writer exists is still written, as in Pi.

BF-648ff3e-73

Docs: another PiG build that shares the config root no longer removes the documentation bundle a running…

  • Source: PiG's own defect
  • Symptom: Docs: another PiG build that shares the config root no longer removes the documentation bundle a running session's system prompt names.
  • Root cause: Not recorded.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Docs: another PiG build that shares the config root no longer removes the documentation bundle a running session's system prompt names. A session leases its bundle until it exits; before, a session that outlived four newer builds' use lost its pages and nothing wrote them again.

BF-648ff3e-74

RPC mode now writes the members of an extension_ui_request that Pi writes empty, as Pi's JSON.stringify…

  • Source: PiG's own defect
  • Symptom: In the Go, Python and Rust SDKs SetStatus(key, "") is now an empty status text (it used to remove the entry), and ClearStatus / clear_status is Pi's setStatus(key, undefined); an empty placeholder or prefill is the unset member (Python: None).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: RPC mode now writes the members of an extension_ui_request that Pi writes empty, as Pi's JSON.stringify does: message: "" of notify, title: "" of setTitle, text: "" of set_editor_text (how a client clears its editor), statusText: "" of setStatus and an empty options list of select were dropped, so a client could not tell empty from absent. input and editor no longer write placeholder: "" / prefill: "" when the extension passed none. In the Go, Python and Rust SDKs SetStatus(key, "") is now an empty status text (it used to remove the entry), and ClearStatus / clear_status is Pi's setStatus(key, undefined); an empty placeholder or prefill is the unset member (Python: None). The interactive footer is unchanged.

BF-648ff3e-75

Starting a second Piglet Binary instance no longer fails on Windows while the first is running

  • Source: PiG's own defect
  • Symptom: Starting a second Piglet Binary instance no longer fails on Windows while the first is running.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: Windows
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Starting a second Piglet Binary instance no longer fails on Windows while the first is running. Extraction left the embedded Go, Rust and Python cell binaries rewritten at every start, and Windows refuses to replace the executable of a running process (Access is denied); a cell binary that already holds the embedded bytes is now left in place, and only a different one is replaced, with a retry on a transient lock.

BF-648ff3e-76

RPC mode writes one setWidget request per ctx.ui.setWidget call from the Go, Python and Rust SDKs, in…

  • Source: PiG's own defect
  • Symptom: RPC mode writes one setWidget request per ctx.ui.setWidget call from the Go, Python and Rust SDKs, in call order and before the command's response, as Pi does.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: RPC mode writes one setWidget request per ctx.ui.setWidget call from the Go, Python and Rust SDKs, in call order and before the command's response, as Pi does. A string list with no options went out as an unanswered frame that the host applied on a lane of its own: the last list a command set could be written after the command's response or after a later notify, a repeat of the same list was dropped, and a list set after a clear stayed cleared on the client. In RPC mode the SDKs now send the list as the host call every other widget shape uses.

BF-648ff3e-77

In print and JSON mode, a /skill:name prompt whose skill file cannot be read now reports the skill's read…

  • Source: PiG's own defect
  • Symptom: In print and JSON mode, a /skill:name prompt whose skill file cannot be read now reports the skill's read error (Extension error (<SKILL.md path>): ENOENT: ...) before the missing-credentials error, as Pi does.
  • Root cause: Not recorded.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: In print and JSON mode, a /skill:name prompt whose skill file cannot be read now reports the skill's read error (Extension error (<SKILL.md path>): ENOENT: ...) before the missing-credentials error, as Pi does. Skill commands and prompt templates are expanded before the selected model's authentication is checked; they were expanded after it, so the skill error never appeared when the provider had no credentials.

BF-648ff3e-78

Images are now resized with the image profile of the model that answered last under a virtual model selection

  • Source: PiG's own defect
  • Symptom: Images are now resized with the image profile of the model that answered last under a virtual model selection.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Images are now resized with the image profile of the model that answered last under a virtual model selection. Prompt images and tool-result images used the selected virtual model, which declares no image limits, so a routed model's inputLimits.images.resize never applied. Pi sizes both with the physical model of the latest routed response, and the selected model otherwise.

BF-648ff3e-79

The system prompt a run forced no longer shows while the bash and custom messages held during that run are…

  • Source: PiG's own defect
  • Symptom: The system prompt a run forced no longer shows while the bash and custom messages held during that run are flushed at its end.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The system prompt a run forced no longer shows while the bash and custom messages held during that run are flushed at its end. A listener of a flushed custom message that read the system prompt saw the run's forced prompt, where Pi ends the run's prompt options first and reports the base prompt.

BF-648ff3e-8

The Node runtime reports a tool whose execute awaits something that is not a host call, and the host closes…

  • Source: PiG's own defect
  • Symptom: The Node runtime reports a tool whose execute awaits something that is not a host call, and the host closes the tool's start window after the calls the extension sent before the report, not after the call its handler sends next.
  • Root cause: Not recorded.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The Node runtime reports a tool whose execute awaits something that is not a host call, and the host closes the tool's start window after the calls the extension sent before the report, not after the call its handler sends next.

BF-648ff3e-80

An extension that reads pi.getMcpServers() from a timer, thread or goroutine and has no…

  • Source: PiG's own defect
  • Symptom: An extension that reads pi.getMcpServers() from a timer, thread or goroutine and has no mcp_servers_change handler now sees a server another extension registers or removes.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: An extension that reads pi.getMcpServers() from a timer, thread or goroutine and has no mcp_servers_change handler now sees a server another extension registers or removes. The host updated the registry but reached an extension only at its next request, so the reader kept the old list; Pi reads the registry live. The host now pushes the new list to every connected extension when the registry changes, off the call that changed it, and sends nothing to an extension that already holds it.

BF-648ff3e-81

An extension that reads ctx.ui.theme from a timer, thread or goroutine now sees the theme the user selects…

  • Source: PiG's own defect
  • Symptom: An extension that reads ctx.ui.theme from a timer, thread or goroutine now sees the theme the user selects (/settings, a theme file edit, the terminal's reported colors).
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: An extension that reads ctx.ui.theme from a timer, thread or goroutine now sees the theme the user selects (/settings, a theme file edit, the terminal's reported colors). The host replaced the active theme but sent the palette only with the extension's next request; Pi's ctx.ui.theme is the global theme, read at every use. The host now pushes the new palette to every connected extension when the theme changes.

BF-648ff3e-82

A Node extension that registers or unregisters a virtual model…

  • Source: PiG's own defect
  • Symptom: A Node extension that registers or unregisters a virtual model (pi.registerVirtualModel/unregisterVirtualModel) sees the write on the next line, as Pi does: ctx.modelRegistry.find, getAll and getAvailable list the model, a provider of only virtual models is configured and available at once, and an unregistration removes the model.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A Node extension that registers or unregisters a virtual model (pi.registerVirtualModel/unregisterVirtualModel) sees the write on the next line, as Pi does: ctx.modelRegistry.find, getAll and getAvailable list the model, a provider of only virtual models is configured and available at once, and an unregistration removes the model. The host also publishes the catalog when it serves either call, so another extension's registry sees the model.

BF-648ff3e-83

CI: the nightly -race run now covers the packages that own most of PiG's goroutines

  • Source: PiG's own defect
  • Symptom: CI: the nightly -race run now covers the packages that own most of PiG's goroutines.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: CI: the nightly -race run now covers the packages that own most of PiG's goroutines. coding, coding/cli (run as four shards), coding/extension, the extension hosts (inproc, runtimecell, cellpack), internal/packagemanager, internal/pilock, internal/pigsdklock, coding/mcpext, coding/rpcclient, the extensions/sdk Go module, internal/codingagent/tools and the concurrency libraries were in no -race job before. make race-coverage (part of check-core and ci-drift) fails when a package that starts goroutines or timers and has tests is in no nightly group of automation/ci/race-packages.txt and not exempt with a reason. The first -race run of all of them found no data race.

BF-648ff3e-84

A pig session on another machine or container that shares your ~/.pig (an NFS home, a bind mount) is no…

  • Source: PiG's own defect
  • Symptom: A pig session on another machine or container that shares your ~/.pig (an NFS home, a bind mount) is no longer reported as crashed by the next pig you start.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A pig session on another machine or container that shares your ~/.pig (an NFS home, a bind mount) is no longer reported as crashed by the next pig you start. Session markers in crash-output/ record the host and PID namespace, and a marker from another one is left alone instead of being removed with a false "ended without recording an exit" line.

BF-648ff3e-85

The upstream test inventory records Pi's shell-path-exists.test.ts as ported instead of partial: Pi mocks…

  • Source: PiG's own defect
  • Symptom: The upstream test inventory records Pi's shell-path-exists.test.ts as ported instead of partial: Pi mocks accessSync and spawnSync, PiG's Windows alias and Unix cases match them case for case, and the native Windows check ran and passed on the Windows runner.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: Windows
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The upstream test inventory records Pi's shell-path-exists.test.ts as ported instead of partial: Pi mocks accessSync and spawnSync, PiG's Windows alias and Unix cases match them case for case, and the native Windows check ran and passed on the Windows runner. The release gate no longer reports it as an open hot-path gap.

BF-648ff3e-86

Fixed a chord host root failing with superseded: code version N is not retained although it was started in…

  • Source: PiG's own defect
  • Symptom: Fixed a chord host root failing with superseded: code version N is not retained although it was started in the current code version: the host picked the current code version and counted the root in it in two separate steps, and a reload's retirement of that code version could fall between them.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Fixed a chord host root failing with superseded: code version N is not retained although it was started in the current code version: the host picked the current code version and counted the root in it in two separate steps, and a reload's retirement of that code version could fall between them. The root now starts in one step, as upstream's does.

BF-648ff3e-87

Print mode and --mode json no longer run an extension command a second time on text an input handler or a…

  • Source: PiG's own defect
  • Symptom: Print mode and --mode json no longer run an extension command a second time on text an input handler or a prompt template produced.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Print mode and --mode json no longer run an extension command a second time on text an input handler or a prompt template produced. A handler that rewrote the prompt into an extension command's invocation, or a template whose body is that invocation, ran the command and sent nothing to the model. Pi checks for a command on the raw text only and sends the produced text to the model.

BF-648ff3e-88

Shutting down a host while a crashed extension waits out its restart backoff no longer leaves a goroutine…

  • Source: PiG's own defect
  • Symptom: Shutting down a host while a crashed extension waits out its restart backoff no longer leaves a goroutine sleeping for up to 30 seconds.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Shutting down a host while a crashed extension waits out its restart backoff no longer leaves a goroutine sleeping for up to 30 seconds. Shutdown ends the wait and waits for it, so embedders and tests see no goroutine after the host is shut down.

BF-648ff3e-89

Forking a session from a file no longer overwrites a session file that already has the new session's…

  • Source: PiG's own defect
  • Symptom: Forking a session from a file no longer overwrites a session file that already has the new session's timestamp and id.
  • Root cause: Not recorded.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Forking a session from a file no longer overwrites a session file that already has the new session's timestamp and id. The fork now fails with EEXIST: file already exists and leaves the file untouched, as Pi does, instead of truncating it.

BF-648ff3e-9

A Node extension tool that awaits the event loop before it takes the file mutation queue registers after the…

  • Source: PiG's own defect
  • Symptom: A Node extension tool that awaits the event loop before it takes the file mutation queue registers after the edit or write call behind it, as in Pi; before, the two raced and the extension won every round.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A Node extension tool that awaits the event loop before it takes the file mutation queue registers after the edit or write call behind it, as in Pi; before, the two raced and the extension won every round. The runtime holds the tool's next fileMutation.enter until the host reports the writes behind the tool registered.

BF-648ff3e-90

Extension SDKs: ModelRegistry.find reports a host or transport failure instead of "no such model"

  • Source: PiG's own defect
  • Symptom: Extension SDKs: ModelRegistry.find reports a host or transport failure instead of "no such model".
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Extension SDKs: ModelRegistry.find reports a host or transport failure instead of "no such model". The Go Find returns (model, error) and the Rust find returns io::Result<Option<_>>; Python already raised. A host with no getModel or sessionRead binding answers an error, so a list-typed session read is never a null.

BF-648ff3e-91

Python SDK: has_configured_auth and is_using_oauth answer False for a model with no provider, as Pi and…

  • Source: PiG's own defect
  • Symptom: Python SDK: has_configured_auth and is_using_oauth answer False for a model with no provider, as Pi and the Go, Rust and Node SDKs do, instead of raising KeyError.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Python SDK: has_configured_auth and is_using_oauth answer False for a model with no provider, as Pi and the Go, Rust and Node SDKs do, instead of raising KeyError.

BF-648ff3e-92

Go SDK: Context.CancellationReason returns the reason the host gave when it cancelled the request, as the…

  • Source: PiG's own defect
  • Symptom: Go SDK: Context.CancellationReason returns the reason the host gave when it cancelled the request, as the Python and Rust SDKs do.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Go SDK: Context.CancellationReason returns the reason the host gave when it cancelled the request, as the Python and Rust SDKs do.

BF-648ff3e-93

A compiled-in extension factory, inline or built in, no longer panics "Extension runtime not initialized"…

  • Source: PiG's own defect
  • Symptom: A compiled-in extension factory, inline or built in, no longer panics "Extension runtime not initialized" when it calls pi.sendMessage, appendEntry, setSessionName, getSessionName, setModel, setThinkingLevel or getThinkingLevel under the interactive mode.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A compiled-in extension factory, inline or built in, no longer panics "Extension runtime not initialized" when it calls pi.sendMessage, appendEntry, setSessionName, getSessionName, setModel, setThinkingLevel or getThinkingLevel under the interactive mode. The mode rebinds its in-process runner after the Session binds it and replaced the Session's actions with three of them; it now binds every action Pi's _bindExtensionCore binds, through the code the wire host actions of the same names run.

BF-648ff3e-94

The interactive mode no longer rewrites every provider request's system prompt into one head

  • Source: PiG's own defect
  • Symptom: The interactive mode no longer rewrites every provider request's system prompt into one head.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: The interactive mode no longer rewrites every provider request's system prompt into one head. It set the agent's system prompt for every run and again after every tool change and reload, so a mid-session change to the prompt or the active tools reached the model as a rewritten head with the transcript's system patch messages dropped. Pi sends the prompt as the transcript's sections and patch messages and forces text onto the request only for the systemPrompt a before_agent_start handler returned; the interactive mode now does the same.

BF-648ff3e-95

Markdown bold and italic render next to CJK punctuation in the terminal, for example…

  • Source: PiG's own defect
  • Symptom: Markdown bold and italic render next to CJK punctuation in the terminal, for example **这是测试。**后面 (Pi #10730).
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Markdown bold and italic render next to CJK punctuation in the terminal, for example **这是测试。**后面 (Pi #10730). Text without CJK keeps the CommonMark flanking rules.

BF-648ff3e-96

A message sent with deliverAs: "nextTurn" while a run was starting could join that run instead of the next…

  • Source: PiG's own defect
  • Symptom: A message sent with deliverAs: "nextTurn" while a run was starting could join that run instead of the next one.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: A message sent with deliverAs: "nextTurn" while a run was starting could join that run instead of the next one. A run takes the pending nextTurn messages when its preparation ends, before the bash results and custom messages held while it started are appended, as Pi does. The Session took them after that, when it claimed the Agent, so a Session event listener that answered a held custom message's message_end with a nextTurn message (a Go program embedding the Session; no extension handler sees those events) had it reach the model a run early.

BF-648ff3e-97

Markdown bold and italic next to CJK punctuation render in HTML exports and in Markdown that Node extensions…

  • Source: PiG's own defect
  • Symptom: Markdown bold and italic next to CJK punctuation render in HTML exports and in Markdown that Node extensions render through pi-tui, matching the terminal (Pi #11168, #10730).
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Markdown bold and italic next to CJK punctuation render in HTML exports and in Markdown that Node extensions render through pi-tui, matching the terminal (Pi #11168, #10730).

BF-648ff3e-98

An extension's UI closures now run only in the TUI, as in Pi

  • Source: PiG's own defect
  • Symptom: An extension's UI closures now run only in the TUI, as in Pi.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: An extension's UI closures now run only in the TUI, as in Pi. In RPC mode the Node runtime no longer runs setFooter/setHeader factories (a throwing render leaked a notify error), keeps setEditorComponent and onTerminalInput handlers, forwards the working-indicator calls, answers getEditorComponent with the installed factory, or fails setTheme(Theme); they are no-ops, getEditorComponent answers undefined and setTheme answers {success:false,error:"Theme switching not supported in RPC mode"}. The Go, Python and Rust SDKs no longer run footer and header renderers in RPC, print or JSON mode, and Custom in RPC mode resolves nil/None without running the component.

BF-648ff3e-99

Interactive mode now routes a message an extension sends with sendMessage while a run streams as Pi does

  • Source: PiG's own defect
  • Symptom: A message with no deliverAs steers: the run delivers it after the current tool batch, before the next model call, where it used to wait until the run would stop.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 648ff3e2b0 (2026-10-22, #288)
  • Tests: None recorded.
  • Release note: Interactive mode now routes a message an extension sends with sendMessage while a run streams as Pi does. A message with no deliverAs steers: the run delivers it after the current tool batch, before the next model call, where it used to wait until the run would stop. A message with triggerTurn: false is held until the turn's tool results are complete and appended without a model call, where it used to be queued as a follow-up that cost the run another model call. sendMessage no longer rejects an empty customType or an unknown deliverAs (Pi accepts the first and steers the second).

BF-65426f3

fix(rpc): pipelined set_models apply in input order, after the batch's synchronous responses

  • Source: PiG's own defect
  • Symptom: fix(rpc): pipelined set_models apply in input order, after the batch's synchronous responses
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 65426f3ab3 (2026-10-10)
  • Tests: coding/cli/rpc_model_type_binary_test.go::TestRPCModelObjectsCarryTheRegistryModelType, coding/cli/rpc_resume_integration_test.go::TestRPCModeSessionReplacementAndExport, coding/cli/rpc_set_model_await_test.go::TestRPCSetModelAppliesAfterTheBatchesLaterCommands, coding/cli/rpc_set_model_pipeline_test.go::TestRPCPipelinedSetModelsApplyInInputOrder

BF-65932e6

fix(extensions): run a tick call after the tool_execution_end of the tool call that queued its callback…

  • Source: PiG's own defect
  • Symptom: fix(extensions): run a tick call after the tool_execution_end of the tool call that queued its callback, whichever reaches the host first
  • Root cause: A timer or immediate callback a Node tool queued while a sibling tool still awaits runs, in Pi, right after the first tool's tool_execution_end. The runtime is another process: it writes the callback's host call a few hundred microseconds after the tool's answer, and the host passed the tick boundary of that tool in about the same time. A call that arrived after the boundary was held for the next tool_execution_end of any tool, so scenario 69 (rpc-sibling-tool-timer-call-order) wrote the immediate and timeout0 notifications behind the sibling's own notification in 8 of 20 single-pair runs on ordering/d5-timer-calls alone and 4 of 20 on the train.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 65932e6efb (2026-10-21)
  • Tests: coding/extension/host/subprocess/callpayload_shape_test.go::TestCallPayloadKeepsUnkeyedLiteralShape, coding/extension/host/subprocess/timer_turns_test.go::TestNodeTickCallsOfAToolRunAfterItsEndWhenTheyArriveLate, coding/extension/host/subprocess/timer_turns_test.go::TestNodeTimerCallbackCallsWaitForTheLoopTurn, internal/jsloop/turns_test.go::TestTurnsForgetATickBoundaryWhenTheToolCallStartsAgain, internal/jsloop/turns_test.go::TestTurnsRunATickAfterItsToolCallsEndWhicheverArrivesFirst, internal/jsloop/turns_test.go::TestTurnsRunTicksAtTheTickBoundaryBeforeTimers, test/extension-conformance/timer_calls_test.go::TestNodeTimerCallbackNotificationsWaitForTheEndOfTheRun

BF-65932e6-2

A notification, status or message that an extension queued with setImmediate or setTimeout from a tool…

  • Source: PiG's own defect
  • Symptom: A notification, status or message that an extension queued with setImmediate or setTimeout from a tool that returned while a sibling tool in the same batch still awaited reached an RPC client behind the sibling's own output in up to two runs in five, where Pi writes it right after the first tool's tool_execution_end.
  • Root cause: A timer or immediate callback a Node tool queued while a sibling tool still awaits runs, in Pi, right after the first tool's tool_execution_end. The runtime is another process: it writes the callback's host call a few hundred microseconds after the tool's answer, and the host passed the tick boundary of that tool in about the same time. A call that arrived after the boundary was held for the next tool_execution_end of any tool, so scenario 69 (rpc-sibling-tool-timer-call-order) wrote the immediate and timeout0 notifications behind the sibling's own notification in 8 of 20 single-pair runs on ordering/d5-timer-calls alone and 4 of 20 on the train.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 4ac21c86c4 (2026-10-21), 65932e6efb (2026-10-21)
  • Tests: coding/extension/host/subprocess/callpayload_shape_test.go::TestCallPayloadKeepsUnkeyedLiteralShape, coding/extension/host/subprocess/timer_turns_test.go::TestNodeTickCallsOfAToolRunAfterItsEndWhenTheyArriveLate, coding/extension/host/subprocess/timer_turns_test.go::TestNodeTimerCallbackCallsWaitForTheLoopTurn, internal/jsloop/turns_test.go::TestTurnsForgetATickBoundaryWhenTheToolCallStartsAgain, internal/jsloop/turns_test.go::TestTurnsRunALateTickAtOnceWhileNoWaitIsOpen, internal/jsloop/turns_test.go::TestTurnsRunATickAfterItsToolCallsEndWhicheverArrivesFirst, internal/jsloop/turns_test.go::TestTurnsRunTicksAtTheTickBoundaryBeforeTimers, test/extension-conformance/timer_calls_test.go::TestNodeTimerCallbackNotificationsWaitForTheEndOfTheRun
  • Release note: A notification, status or message that an extension queued with setImmediate or setTimeout from a tool that returned while a sibling tool in the same batch still awaited reached an RPC client behind the sibling's own output in up to two runs in five, where Pi writes it right after the first tool's tool_execution_end. The Node runtime is another process: it wrote the callback's host call a few hundred microseconds after the tool's answer, and the host passed that tool's tool_execution_end in about the same time, so a call that arrived after it waited for the next tool_execution_end of any tool. The call now says which tool call queued its callback (origin_tool_call_id). The host runs it behind that call's tool_execution_end when it arrives before the event, and at once when it arrives after, where the runtime says a sibling tool still runs and the loop turns.

BF-663e84b

The built-in models page lists the real thinking levels (off, minimal, low, medium, high, xhigh, max) instead…

  • Source: PiG's own defect
  • Symptom: The built-in models page lists the real thinking levels (off, minimal, low, medium, high, xhigh, max) instead of a five-step ladder that started at none.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 663e84b2d8 (2026-10-14)
  • Tests: None recorded.
  • Release note: The built-in models page lists the real thinking levels (off, minimal, low, medium, high, xhigh, max) instead of a five-step ladder that started at none. Reported by @SkrOYC (#194).

BF-663e84b-2

The built-in models page documents the models.json provider and model fields (models[], contextWindow…

  • Source: PiG's own defect
  • Symptom: The built-in models page documents the models.json provider and model fields (models[], contextWindow, maxTokens, modelOverrides) with an example, plus provider cost tiers, local models and troubleshooting.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 663e84b2d8 (2026-10-14)
  • Tests: None recorded.
  • Release note: The built-in models page documents the models.json provider and model fields (models[], contextWindow, maxTokens, modelOverrides) with an example, plus provider cost tiers, local models and troubleshooting. Reported by @SkrOYC (#193).

BF-663e84b-3

The built-in docs gain the compaction summary format, branch summaries and extension hooks, the RPC, JSON…

  • Source: PiG's own defect
  • Symptom: The built-in docs gain the compaction summary format, branch summaries and extension hooks, the RPC, JSON mode, CLI integration, session format, message types, llama.cpp, security and usage pages, and "PiG" replaces "Pig" throughout.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 663e84b2d8 (2026-10-14)
  • Tests: None recorded.
  • Release note: The built-in docs gain the compaction summary format, branch summaries and extension hooks, the RPC, JSON mode, CLI integration, session format, message types, llama.cpp, security and usage pages, and "PiG" replaces "Pig" throughout. The platform pages stay on the site only, by choice. Reported by @SkrOYC (#195).

BF-663e84b-4

The packages page says how to build and run pig-acp, which npm:@pi-in-go/pigpen-acp needs but does not…

  • Source: PiG's own defect
  • Symptom: The packages page says how to build and run pig-acp, which npm:@pi-in-go/pigpen-acp needs but does not install, and how to point Zed at it.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 663e84b2d8 (2026-10-14)
  • Tests: None recorded.
  • Release note: The packages page says how to build and run pig-acp, which npm:@pi-in-go/pigpen-acp needs but does not install, and how to point Zed at it. Reported by @klonuo (#210).

BF-6646adc

A durable transaction that settles a submission with a detail that is not strict JSON now counts as having…

  • Source: PiG's own defect
  • Symptom: A durable transaction that settles a submission with a detail that is not strict JSON now counts as having written a table, as Pi's settleSubmission does: later table reads in the same commit are refused, and on a settled transaction the settled error is reported before the JSON check.
  • Root cause: Differences found: every message used Go's %q where Pi interpolates the name between plain double quotes (constrained-sampling.ts:136-150,188,226,259,267), so a tool named 'we"ird' got backslashes; isStructuredSchema tested properties/items with != nil although JS tests !== undefined (an explicit null counts) and searched a type array through toStringSlice, which gave up when any element was not a string where Array.includes does not. Mutants killed: each of those, the delta no-op and closed-buffer rules, the JS trim of grammar variants, the nullable-wrap and required checks. Go cannot hold a non-object tool.parameters, so boolean/array/string roots are not probed (type-level difference).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 172a6e3cd9 (2026-10-10), 6646adc3b2 (2026-10-10), 749840b14d (2026-10-10)
  • Tests: ai/constrained_sampling_oracle_test.go::TestConstrainedSamplingMatchesPi, durable/entry_edits_presence_test.go::TestEntryEditsKeepAPresentEmptyList, durable/session/entry_edits_copy_test.go::TestCopyEntryRecordKeepsAPresentEmptyEditList, durable/session/settle_submission_order_test.go::TestSettleSubmissionChecksOpenAndMarksTheWriteBeforeCopying, durable/storage/jsonl_pi_oracle_test.go::TestJsonlStorageMatchesPiOnSeededPrograms, durable/storage/memory_pi_oracle_test.go::TestMemoryStorageMatchesPiOnSeededPrograms, durable/storage/session_pi_oracle_test.go::TestSessionTransactionsMatchPiOnSeededPrograms
  • Release note: A durable transaction that settles a submission with a detail that is not strict JSON now counts as having written a table, as Pi's settleSubmission does: later table reads in the same commit are refused, and on a settled transaction the settled error is reported before the JSON check.

BF-666acff

Package filters, manifest !/+/- overrides, settings resource patterns and built-in extension overrides…

  • Source: PiG's own defect
  • Symptom: Package filters, manifest !/+/- overrides, settings resource patterns and built-in extension overrides follow minimatch 10.2.6 and its brace-expansion 5.0.12 limits, as in Pi.
  • Root cause: 17 fail a test; the other lets the last combine's shortcut ignore dropEmpties, which is equivalent because the values loop already drops those empties. The port still equals Pi's copy on the 200,000-pattern corpus.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 49798a80d2 (2026-10-07), a79de03e7f (2026-10-07), 1cbb6b6fb2 (2026-10-09), 4401fa4630 (2026-10-09), 4cc5ef904b (2026-10-09), 6584435bb6 (2026-10-09), 6e6b9132c2 (2026-10-09), 926f5d2e63 (2026-10-09), aff3d7266f (2026-10-09), d586d71f85 (2026-10-09), ef007a4e5e (2026-10-09), 6410ef49f6 (2026-10-10), 666acff687 (2026-10-10), 6edaafdfe2 (2026-10-10), 6f8d83861a (2026-10-10), c1055c449e (2026-10-10), fce99d43d7 (2026-10-10)
  • Tests: coding/cli/cli_package_glob_test.go::TestCLIGitSourceRefreshesOncePerLoad, coding/cli/cli_package_glob_test.go::TestCLIPackageSourceGlobThrowFirstSourceWins, coding/cli/cli_package_glob_test.go::TestCLIPackageSourceGlobThrowFollowsPi, coding/cli/cli_package_glob_test.go::TestCLIPackageSourceInstallFailureStopsStartup, coding/cli/cli_package_glob_test.go::TestReloadSnapshotReportsCLIPackageGlobThrow, coding/cli/cli_package_glob_test.go::TestStartupStopsAtACLIPackageGlobThrow, coding/cli/settings_file_url_upstream_test.go::TestNpmPackageManifestGlobThrowRejectsResolution, coding/cli/settings_file_url_upstream_test.go::TestSettingsPatternErrorsFollowPi and 58 more in docs/bug-ledger.json
  • Release note: Package filters, manifest !/+/- overrides, settings resource patterns and built-in extension overrides follow minimatch 10.2.6 and its brace-expansion 5.0.12 limits, as in Pi.

BF-666acff-2

A pattern that Pi cannot compile now fails where Pi fails: a local Package keeps only the resource kinds…

  • Source: PiG's own defect
  • Symptom: A pattern that Pi cannot compile now fails where Pi fails: a local Package keeps only the resource kinds collected before it, an npm or git Package (configured or -e) stops startup and /reload with the JavaScript error, and so does a settings pattern or an override on auto-discovered resources.
  • Root cause: 17 fail a test; the other lets the last combine's shortcut ignore dropEmpties, which is equivalent because the values loop already drops those empties. The port still equals Pi's copy on the 200,000-pattern corpus.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 311609427b (2026-10-07), 49798a80d2 (2026-10-07), 630f56def0 (2026-10-07), 66bb7180b3 (2026-10-07), 947d0669ee (2026-10-07), 965ef796b1 (2026-10-07), a79de03e7f (2026-10-07), a7b6786111 (2026-10-07), f235c0bcc5 (2026-10-07), 1cbb6b6fb2 (2026-10-09), 4401fa4630 (2026-10-09), 4cc5ef904b (2026-10-09), 6584435bb6 (2026-10-09), 6e6b9132c2 (2026-10-09), 926f5d2e63 (2026-10-09), 9fa02166f5 (2026-10-09), aff3d7266f (2026-10-09), d586d71f85 (2026-10-09), ef007a4e5e (2026-10-09), 6410ef49f6 (2026-10-10), 666acff687 (2026-10-10), 6edaafdfe2 (2026-10-10), 6f8d83861a (2026-10-10), c1055c449e (2026-10-10), fce99d43d7 (2026-10-10)
  • Tests: ai/simple_options_test.go::TestBuildBaseOptions, coding/cli/cli_package_glob_test.go::TestCLIGitSourceRefreshesOncePerLoad, coding/cli/cli_package_glob_test.go::TestCLIPackageSourceGlobThrowFirstSourceWins, coding/cli/cli_package_glob_test.go::TestCLIPackageSourceGlobThrowFollowsPi, coding/cli/cli_package_glob_test.go::TestCLIPackageSourceInstallFailureStopsStartup, coding/cli/cli_package_glob_test.go::TestReloadSnapshotReportsCLIPackageGlobThrow, coding/cli/cli_package_glob_test.go::TestStartupStopsAtACLIPackageGlobThrow, coding/cli/resource_loader_gaps_upstream_test.go::TestPrintModeInvalidDiscoveredFileURLFailsBind and 72 more in docs/bug-ledger.json
  • Release note: A pattern that Pi cannot compile now fails where Pi fails: a local Package keeps only the resource kinds collected before it, an npm or git Package (configured or -e) stops startup and /reload with the JavaScript error, and so does a settings pattern or an override on auto-discovered resources.

BF-668b3fa

Fix the shared attribute-order store case: advance the clock before flush and compare metric row names only

  • Source: PiG's own defect
  • Symptom: Fix the shared attribute-order store case: advance the clock before flush and compare metric row names only
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 668b3fa13b (2026-10-19)
  • Tests: telemetry/store_upstream_test.go::TestInMemoryMetricRowsKeepAttributeInsertionOrder

BF-66b0fc0

The Anthropic workload-identity token endpoint check parses the base URL with the WHATWG parser, as the…

  • Source: PiG's own defect
  • Symptom: The Anthropic workload-identity token endpoint check parses the base URL with the WHATWG parser, as the Anthropic SDK does: loopback hosts reached with userinfo, mixed case or unusual spellings are accepted, and a base URL containing a backslash, quote or non-ASCII text appears unescaped in the error message.
  • Root cause: getStandardBedrockEndpointRegion (cut from Pi's bedrock-converse-stream.ts:1223, testdata/bedrock_region.mjs) runs on 3006 seeded base URLs (regions, fips, cn partition, mixed case, full-width letters, tabs/newlines, userinfo, ports, schemes, schemeless and non-URL text). net/url differed on every URL with a tab/newline inside or a full-width/uppercase host (the WHATWG parser strips and folds them) and returned a region for a schemeless '//host' that new URL rejects (red before). bedrockEndpointRegion now reads nodeurl.ParseURL's hostname. Survivor: dropping the empty-string guard, which is equivalent because ParseURL rejects ''.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0b4e1e3376 (2026-10-09), 15a5c0b670 (2026-10-09), 2c04c1445a (2026-10-09), 31f4517fbe (2026-10-09), 66b0fc0539 (2026-10-09), 7c4d874438 (2026-10-09), 90882933f6 (2026-10-09), a739b6dcf2 (2026-10-09), c4d51d5c1a (2026-10-09), d1b3ad0151 (2026-10-09), d1cbde3321 (2026-10-09), d80c1cd150 (2026-10-09), da8a8704c6 (2026-10-09)
  • Tests: ai/anthropic_token_endpoint_oracle_test.go::TestAnthropicTokenEndpointRuleMatchesSDK, ai/authorization_input_oracle_test.go::TestAuthorizationInputParsersMatchPi, ai/azure_config_oracle_test.go::TestAzureBaseURLNormalizationMatchesPi, ai/bedrock_region_oracle_test.go::TestBedrockEndpointRegionMatchesPi, ai/chatgpt_manual_input_oracle_test.go::TestChatGPTManualInputMatchesPiOnSeededProbes, ai/codex_account_id_oracle_test.go::TestCodexAccountIDMatchesPi, ai/codex_vertex_urls_oracle_test.go::TestCodexWebSocketAndVertexVersionURLsMatchPi, ai/githubcopilot_oracle_test.go::TestGitHubCopilotHelpersMatchPi and 6 more in docs/bug-ledger.json
  • Release note: The Anthropic workload-identity token endpoint check parses the base URL with the WHATWG parser, as the Anthropic SDK does: loopback hosts reached with userinfo, mixed case or unusual spellings are accepted, and a base URL containing a backslash, quote or non-ASCII text appears unescaped in the error message.

BF-66db311

The login dialog now holds TUI focus while it is shown, so the prompt cursor appears in the right place for…

  • Source: PiG's own defect
  • Symptom: The login dialog now holds TUI focus while it is shown, so the prompt cursor appears in the right place for IME input.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 66db311977 (2026-10-06)
  • Tests: tui/key_helper_test.go::TestKeyBuildersMatchInput, tui/key_helper_test.go::TestKeyMatchesUpstreamKeyObject, tui/login_dialog_pi_test.go::TestLoginDialogFocusPropagatesToPromptInput, tui/login_dialog_pi_test.go::TestLoginDialogMaskDisabledMatchesPi, tui/theme_tokens_test.go::TestThemeColorsAreKeyedByEveryThemeToken
  • Release note: The login dialog now holds TUI focus while it is shown, so the prompt cursor appears in the right place for IME input.

BF-6708fbb

When a Go, Rust or Python extension that shares a process registers under a different name than the one it…

  • Source: PiG's own defect
  • Symptom: It said only packed extension registered as "<name>".
  • Root cause: A Go, Rust or Python member of a packed process whose factory registers a name other than its selected identity failed with only 'packed extension registered as "<name>"'. The isolated path already names both identities, the source, and how to fix the selection. Both paths now build that error with registeredIdentityMismatch.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0631c73a3f (2026-10-09), 6708fbb379 (2026-10-09), b751326e80 (2026-10-09)
  • Tests: coding/extension/host/subprocess/liveness_test.go::TestPackedMemberTransportClosureDoesNotProveProcessDeath, coding/extension/host/subprocess/packed_go_test.go::TestHost_KillingPackedProcessNeverClaimsItRemainedAlive, coding/extension/host/subprocess/packed_go_test.go::TestHost_LoadGoPackedCellRejectsRegisteredIdentityMismatchActionably, extensions/sdk-py/tests/test_overlay_width.py, extensions/sdk/overlay_width_test.go::TestRemoteRenderWidthFollowsUpstreamOverlayLayout, test/extension-conformance/overlay_width_conformance_test.go::TestConformance_OverlayRenderWidth
  • Release note: When a Go, Rust or Python extension that shares a process registers under a different name than the one it was selected by, the error now names both identities, the source, and how to fix the selection, as it already did for an extension in its own process. Before, it said only packed extension registered as "<name>".

BF-677ded1

An indented line after a quoted heading, rule or fence in a blockquote renders as the code block Pi shows

  • Source: PiG's own defect
  • Symptom: An indented line after a quoted heading, rule or fence in a blockquote renders as the code block Pi shows.
  • Root cause: marked joins an indented lazy line to a quoted paragraph with its four spaces removed only when the quoted line before it is text; after a heading, rule or fence it is a code block of its own. Pig stripped the spaces whatever the quoted line was. The strip now requires markdownIsTextLine of the last quoted line.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 677ded1860 (2026-10-11)
  • Tests: tui/markdown_quote_lazy_oracle_test.go::TestMarkdownQuoteIndentedLazyLineAfterABlockMatchesPi
  • Release note: An indented line after a quoted heading, rule or fence in a blockquote renders as the code block Pi shows.

BF-68a4ed2

PageUp and PageDown move the selection by a page in selection lists and selectors (settings, model…

  • Source: PiG's own defect
  • Symptom: PageUp and PageDown move the selection by a page in selection lists and selectors (settings, model, extension, session, tree, trust, login and the open editor autocomplete) and no longer scroll the fullscreen transcript behind them (Pi #7629, PR #7680).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 68a4ed2b54 (2026-10-09)
  • Tests: internal/codingagent/fullscreen_modal_viewport_test.go::TestFullscreenSelectionKeepsItsPageKeys, internal/codingagent/fullscreen_modal_viewport_test.go::TestRegularModeSelectorPageKeyMovesItsSelection, internal/codingagent/llama/ui_test.go::TestHuggingFaceSearchPageKeysMoveTenResults, internal/codingagent/modal_viewport_input_test.go::TestEditorSlotSelectorPageKeyMovesSelectionInEveryMode, internal/codingagent/select_page_keys_test.go::TestCustomEditorGivesSelectionPageKeysToAnOpenAutocomplete, internal/codingagent/select_page_keys_test.go::TestFirstTimeSetupPageKeysMoveToTheEnds, internal/codingagent/select_page_keys_test.go::TestSessionSelectorCapturesSelectionPageKeys, internal/codingagent/select_page_keys_test.go::TestTrustSelectorPageKeysMoveToTheEnds and 8 more in docs/bug-ledger.json
  • Release note: PageUp and PageDown move the selection by a page in selection lists and selectors (settings, model, extension, session, tree, trust, login and the open editor autocomplete) and no longer scroll the fullscreen transcript behind them (Pi #7629, PR #7680).

BF-68a63f2

A tool namespace registered with aliases: [] keeps the empty array in pi.getAllTools() and the replicated…

  • Source: PiG's own defect
  • Symptom: A tool namespace registered with aliases: [] keeps the empty array in pi.getAllTools() and the replicated tool list, as Pi returns the authored namespace object.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 68a63f2459 (2026-10-13)
  • Tests: coding/extension/host/subprocess/state_test.go::TestGetAllToolsKeepsANamespacesEmptyAliases
  • Release note: A tool namespace registered with aliases: [] keeps the empty array in pi.getAllTools() and the replicated tool list, as Pi returns the authored namespace object. extension.ToolNamespace.Aliases omits only a nil slice.

BF-68b129d

/export to a directory that cannot be created on a read-only file system (such as macOS's root) reports…

  • Source: PiG's own defect
  • Symptom: /export to a directory that cannot be created on a read-only file system (such as macOS's root) reports ENOENT: no such file or directory, mkdir ... as Pi does, not EROFS.
  • Root cause: node_file.cc MKDirpSync returns EACCES, ENOSPC, ENOTDIR, EPERM and ENOENT from mkdir as they are; for any other error it stats the directory and reports that error. A directory missing from macOS's read-only root is therefore ENOENT in Node (and Pi's /export), where MkdirAll said EROFS. TestExportSessionToJsonlFailuresAreNodes now takes the expected message from Node's mkdirSync on the running OS (EACCES on Linux, ENOENT on macOS).
  • User impact: An operation failed or was rejected
  • Platform: macOS
  • Fixed in: 0.5.0
  • Fix commits: 68b129d018 (2026-10-13)
  • Tests: internal/codingagent/session_export_test.go::TestExportSessionToJsonlFailuresAreNodes, internal/nodeerrno/fserror_test.go::TestMkdirAllAndFromPathErrorMatchNode
  • Release note: /export to a directory that cannot be created on a read-only file system (such as macOS's root) reports ENOENT: no such file or directory, mkdir ... as Pi does, not EROFS.

BF-68ceee2

Tool-argument validation compares JSON values as JSON Schema does: a number is never equal to a string

  • Source: PiG's own defect
  • Symptom: Tool-argument validation compares JSON values as JSON Schema does: a number is never equal to a string.
  • Root cause: The vendored santhosh-tekuri equals() parsed the right-hand side of a numeric left-hand side with big.Rat.SetString, which accepts "1.5", "0x10", "1e3" and "1/2". A schema {"const":"1"} accepted the number 1 (Pi's typebox rejects it), {"const":{"x":"1"}} accepted {"x":1}, and uniqueItems rejected ["1", 1] or ["0x10", 16] (Pi accepts them) when the string came first.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 32e33923d5 (2026-10-11), 68ceee26fc (2026-10-11)
  • Tests: ai/validate_equality_oracle_test.go::TestValidateToolArgumentsComparesNumbersAndStringsLikePi, ai/validate_wrapped_order_lifetime_test.go::TestValidateToolArgumentsDoesNotKeepWrappedObjectsInTheCachedSchema, third_party/jsonschema/equals_number_test.go::TestEqualsDoesNotEquateANumberWithAString
  • Release note: Tool-argument validation compares JSON values as JSON Schema does: a number is never equal to a string. const, enum and uniqueItems no longer treat 1, 1.5, 16 or 0.5 as equal to "1", "1.5", "0x10" or "1/2" (the schema library parsed the string as a number), matching Pi's validator.

BF-68d0628

The Go, Rust and Python SDKs export Pi's getLanguageFromPath

  • Source: PiG's own defect
  • Symptom: The Go, Rust and Python SDKs export Pi's getLanguageFromPath.
  • Root cause: TestConformance_LanguageFromPathAgreesAcrossSDKs runs every table key in four spellings plus 25 edge paths (no extension, trailing dot, a dot in a directory, spaces, non-ASCII) in every SDK, isolated and packed, against the Node SDK's answer from Pi's own code: all agree. 12 mutants (a table entry, lower-casing, first versus last dot, a dropped entry) are killed in Go, Rust and Python; the empty-extension guard is equivalent because the table has no empty key. A JavaScript object lookup returns an inherited function for 'constructor' or 'toString'; the other SDKs return none, recorded in the parity row.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 68d062839c (2026-10-11)
  • Tests: test/extension-conformance/language_from_path_test.go::TestConformance_LanguageFromPathAgreesAcrossSDKs
  • Release note: The Go, Rust and Python SDKs export Pi's getLanguageFromPath.

BF-68f8761

insecure: true on a provider an extension registers with refreshModels now reaches the request transport

  • Source: PiG's own defect
  • Symptom: insecure: true on a provider an extension registers with refreshModels now reaches the request transport.
  • Root cause: Pi's registerProvider merges a re-registration's defined values over the previous registration and keeps the undefined ones (model-runtime.ts:921-933). Go's zero value is the omitted value, so insecure now follows the rule its neighbouring fields already follow: a re-registration that sets it turns it on, one that omits it keeps the earlier opt-in.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0e05901b4c (2026-10-09), cf648c7b8b (2026-10-09), 68f87619b5 (2026-10-10)
  • Tests: coding/extension/host/subprocess/host_test.go::TestHost_BuildExtension_Tools, coding/extension/host/subprocess/host_test.go::TestProtocol_RegisterRoundTrip, coding/extension/host/subprocess/registration_fields_wiring_test.go::TestRegistrationDeclarationsKeepEveryField, coding/provider_refresh_insecure_test.go::TestRefreshModelsRegistrationKeepsInsecure, internal/codingagent/model_registry_test.go::TestModelRegistry_RegisterProvider_InsecureMergesAsPiMergesItsNeighbours
  • Release note: insecure: true on a provider an extension registers with refreshModels now reaches the request transport. A re-registration that omits insecure keeps an earlier opt-in, as Pi keeps every field a re-registration leaves undefined; before, a provider registered without refreshModels ignored an insecure: true re-registration.

BF-68f98d7

Fixed the error message of a failed lazy API setup, such as a module load or auth failure, using its failure…

  • Source: PiG's own defect
  • Symptom: Fixed the error message of a failed lazy API setup, such as a module load or auth failure, using its failure time as timestamp instead of the request start.
  • Root cause: event-stream.ts AssistantMessageEventStream.#time: the final message of a response gets durationMs measured from the stream's creation on a monotonic clock, unless the message has one or predates the stream (a deferred result fetched later). Plumbed through agent messages, session/RPC JSON (after the provider's keys, before thinkingLevel) and extension message decoding. lazy.ts: a failed setup keeps the request start as its timestamp.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 68f98d7133 (2026-10-07)
  • Tests: agent/agent_loop_upstream_test.go::TestAgentLoop_TranscriptAssistantMessageCarriesResponseDuration, agent/message_json_test.go::TestAgentMessageJSONAssistantDurationMsOrder, ai/event_stream_timing_test.go::TestAssistantMessageEventStreamTimingUpstream, ai/google_sdk_pipeline_test.go::TestGoogleProviderConsumerMatchesNodeTickOrder, ai/lazy_api_upstream_test.go::TestLazyAPIDeferredCapabilitiesUpstream, internal/codingagent/rebind_ui_upstream_test.go::TestRebindResetsProgramStatus
  • Release note: Fixed the error message of a failed lazy API setup, such as a module load or auth failure, using its failure time as timestamp instead of the request start.

BF-6992ffb

A Go, Python or Rust extension that calls appendEntry in a handler that already read the session log now…

  • Source: PiG's own defect
  • Symptom: It saw the entry only on its first read of the log or after the next host request.
  • Root cause: The handler's getEntries() is that SDK's first session read, so the subscription fetched the whole log and counted the entry however appends were replicated: with the Go SDK's applySessionUpdate made a no-op the go case still passed.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 8d990c5d54 (2026-10-07), 31cde9f009 (2026-10-09), 6992ffba71 (2026-10-09), 7489735c3f (2026-10-09), f20cc081d5 (2026-10-09)
  • Tests: coding/cli/rpc_append_entry_sdks_test.go::TestRPCAppendEntryIsVisibleToTheSameHandlerInEverySDK, coding/cli/rpc_append_entry_visible_test.go::TestRPCNodeAppendEntryIsVisibleToTheSameHandlerAndEmitsEntryAppended, coding/extension/host/inproc/upstream_scoped_models_test.go::TestContextThinkingLevelReadsTheBoundRuntimeLive, coding/extension/host/subprocess/builder_node_entry_test.go::TestResolveNodeEntrypointImportsAnIndexMjsDirectoryAsJiti, test/extension-conformance/api_append_entry_test.go::TestConformance_ExtensionAPIAppendEntry, test/parity/runner/snapshot_test.go::TestSnapshotExtensionPathKeepsADirectoryExtensionsName, test/parity/scenarios/extensions-runtime/75-robust-notes-go.toml, test/parity/scenarios/extensions-runtime/75-robust-notes-node.toml and 2 more in docs/bug-ledger.json
  • Release note: A Go, Python or Rust extension that calls appendEntry in a handler that already read the session log now sees the new entry in its next getEntries(), as in Pi. Before, it saw the entry only on its first read of the log or after the next host request.

BF-69b9811

Two PiG builds that share a config root (two installed versions, or stock PiG beside a Piglet Binary) no…

  • Source: PiG's own defect
  • Symptom: Two PiG builds that share a config root (two installed versions, or stock PiG beside a Piglet Binary) no longer rewrite each other's documentation at every start.
  • Root cause: A sync keeps the four most recently used bundles.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 395c46cbf2 (2026-10-09), 69b9811164 (2026-10-09), 852652e3ae (2026-10-09), da99854d1b (2026-10-09)
  • Tests: coding/cli/piglet_verify_cache_startup_test.go::TestSignedPigletBinaryRunsWhenItsVerifyCacheCannotBeWritten, coding/cli/readonly_config_root_binary_test.go::TestReadOnlyConfigRootStartsQuietlyAndNamesOnlyDocsThatExist, coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, internal/codingagent/auth_guidance_test.go::TestNoTextNamesADocsBundleStartupCouldNotWrite, internal/configroot/configroot_test.go::TestDocsDirIsThePigdocsBundleUnderTheConfigRoot, internal/contentdirs/contentdirs_test.go::TestEvictKeepsTheMostRecentlyUsedAndTheCallersOwn, internal/contentdirs/contentdirs_test.go::TestNameRequiresAHexHash, internal/contentdirs/contentdirs_test.go::TestTouchRecordsUseAtMostHourly and 2 more in docs/bug-ledger.json
  • Release note: Two PiG builds that share a config root (two installed versions, or stock PiG beside a Piglet Binary) no longer rewrite each other's documentation at every start. Each build writes its docs to its own directory, ~/.pig/docs/bundle-<hash>/, and the system prompt, login help and codemode name that directory; pig docs path prints it. A sync keeps the four most recently used bundles.

BF-69c175b

fix(models): models.json entries keep exposed limits and cost; bound thinking-only messages in summaries; pin…

  • Source: PiG's own defect
  • Symptom: fix(models): models.json entries keep exposed limits and cost; bound thinking-only messages in summaries; pin #8810 (D140, D141)
  • Root cause: Pi #9566, Pi #9602 (PR #9717). Pi #8810 does not reproduce in PiG (40 x 3 startup orders x 10 runs); the test pins it.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 69c175bf60 (2026-10-09)
  • Tests: coding/model_default_provider_race_test.go::TestExtensionProviderIsConfiguredWhenTheDefaultModelIsResolved

BF-69f6341

The Radius relay WebSocket URL is built with the WHATWG URL parser, as Pi's new URL does: gateway hosts are…

  • Source: PiG's own defect
  • Symptom: The Radius relay WebSocket URL is built with the WHATWG URL parser, as Pi's new URL does: gateway hosts are normalized, userinfo and ports follow Pi, an unparseable gateway reports "Invalid URL", and an unsupported scheme is reported with its trailing colon.
  • Root cause: The URL expression of requestMistralStream (mistral-conversations.ts:303-305: new URL(baseUrl); pathname set to the trimmed path plus '/'; new URL('v1/chat/completions', baseUrl)) runs in Node (testdata/mistral_url.mjs) on 4514 seeded base URLs. It is now ParseURL, the pathname setter (WithPathname) and ResolveRelativePath; the hand-rolled authority normalizer and resolveDirectoryPath are deleted. The file drive-letter rule cannot trigger here because the directory path always ends in '/'.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0b4e1e3376 (2026-10-09), 15a5c0b670 (2026-10-09), 1ee7dea3e4 (2026-10-09), 31f4517fbe (2026-10-09), 66b0fc0539 (2026-10-09), 69f63414c1 (2026-10-09), 7c4d874438 (2026-10-09), 90882933f6 (2026-10-09), 97e178f7f6 (2026-10-09), a739b6dcf2 (2026-10-09), d1b3ad0151 (2026-10-09), d1cbde3321 (2026-10-09), d80c1cd150 (2026-10-09), da8a8704c6 (2026-10-09)
  • Tests: ai/anthropic_token_endpoint_oracle_test.go::TestAnthropicTokenEndpointRuleMatchesSDK, ai/azure_config_oracle_test.go::TestAzureBaseURLNormalizationMatchesPi, ai/bedrock_region_oracle_test.go::TestBedrockEndpointRegionMatchesPi, ai/chatgpt_manual_input_oracle_test.go::TestChatGPTManualInputMatchesPiOnSeededProbes, ai/codex_vertex_urls_oracle_test.go::TestCodexWebSocketAndVertexVersionURLsMatchPi, ai/githubcopilot_oracle_test.go::TestGitHubCopilotHelpersMatchPi, ai/mistral_url_oracle_test.go::TestMistralChatCompletionsURLMatchesPiOnSeededProbes, ai/radius_config_oracle_test.go::TestRadiusConfigMatchesPiOnSeededProbes and 6 more in docs/bug-ledger.json
  • Release note: The Radius relay WebSocket URL is built with the WHATWG URL parser, as Pi's new URL does: gateway hosts are normalized, userinfo and ports follow Pi, an unparseable gateway reports "Invalid URL", and an unsupported scheme is reported with its trailing colon.

BF-6a6fffa

A session message whose integer member, such as timestamp, a usage count or durationMs, is spelled with a…

  • Source: PiG's own defect
  • Symptom: PiG dropped the message from the session context and RPC get_messages.
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain, test/longsession/session_oracle_test.go::TestSessionManagerMatchesPi, test/parity/scenarios/rpc/58-rpc-session-wrong-typed-members.toml
  • Release note: A session message whose integer member, such as timestamp, a usage count or durationMs, is spelled with a fraction or an exponent (Python's json writes 1767225603000.0), holds a fraction, or is beyond the 64-bit range is now kept, as Pi keeps it. Before, PiG dropped the message from the session context and RPC get_messages. The message is written as JSON.stringify writes the parsed number.

BF-6a6fffa-2

formatToolCallWithArgs against live Pi: new TestFormatToolCallWithArgsMatchesPi…

  • Source: PiG's own defect
  • Symptom: formatToolCallWithArgs against live Pi: new TestFormatToolCallWithArgsMatchesPi (tui/tool_call_args_oracle_test.go; real dark and light themes; 3,000 seeded argument documents as JSON text with whitespace, repeated keys, escapes, integer-like keys, odd number text, tabs/CR/newlines/emoji, nested values and non-object arguments; collapsed and expanded, colour escapes included).
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, coding/discover_extensions_oracle_test.go::TestDiscoverExtensionPathsMatchPi, internal/codingagent/armin_test.go::TestArminFramesMatchPinnedPi, internal/codingagent/custom_message_components_oracle_test.go::TestCustomMessageAndEntryComponentsMatchPi, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries and 9 more in docs/bug-ledger.json
  • Release note: formatToolCallWithArgs against live Pi: new TestFormatToolCallWithArgsMatchesPi (tui/tool_call_args_oracle_test.go; real dark and light themes; 3,000 seeded argument documents as JSON text with whitespace, repeated keys, escapes, integer-like keys, odd number text, tabs/CR/newlines/emoji, nested values and non-object arguments; collapsed and expanded, colour escapes included). No bug (the earlier port is faithful, including the 100-unit cut and the 97-unit ellipsis); the cut can split a surrogate pair, which PiG keeps as WTF-8 until the terminal write, so the test compares jsstring.StreamText(got). 11 mutations: 10 red; muted vs toolOutput is equivalent (both resolve to the same colour in the dark and light themes). Related finding, not changed (no production caller): agent.EstimateTokens / agent.EstimateContextTokens (agent/context_tokens.go) count UTF-8 bytes where Pi's estimateTokens counts UTF-16 units; the production path is internal/codingagent/compaction (oracle-tested), so the agent copy is a caller-free duplicate.

BF-6a6fffa-3

getTextOutput against live Pi: new TestGetTextOutputMatchesPi…

  • Source: PiG's own defect
  • Symptom: getTextOutput against live Pi: new TestGetTextOutputMatchesPi (internal/codingagent/tools/render_text_output_oracle_test.go; 2,500 seeded tool results of text blocks with ANSI, CR, control characters and empty text, and PNG/JPEG/GIF/WebP/garbage/empty image blocks with matching, mismatching, empty and unknown mime types, with images off, kitty or iterm2 and showImages on or off).
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/anthropic_messages_payload_oracle_test.go::TestAnthropicMessagesPayloadMatchesPi, coding/discover_extensions_oracle_test.go::TestDiscoverExtensionPathsMatchPi, internal/codingagent/armin_test.go::TestArminFramesMatchPinnedPi, internal/codingagent/custom_message_components_oracle_test.go::TestCustomMessageAndEntryComponentsMatchPi and 10 more in docs/bug-ledger.json
  • Release note: getTextOutput against live Pi: new TestGetTextOutputMatchesPi (internal/codingagent/tools/render_text_output_oracle_test.go; 2,500 seeded tool results of text blocks with ANSI, CR, control characters and empty text, and PNG/JPEG/GIF/WebP/garbage/empty image blocks with matching, mismatching, empty and unknown mime types, with images off, kitty or iterm2 and showImages on or off). No bug; 11 mutations: 9 red, 2 equivalent (the Data != "" and MimeType != "" guards before GetImageDimensions, which returns nothing for empty data or an empty mime type anyway). Not compared: an explicit empty-string mime type (not representable in ai.ImageContent).

BF-6a6fffa-4

Live-Pi oracles for utils/headers.ts: TestHeadersToRecordMatchesPi (800 header sequences over case…

  • Source: PiG's own defect
  • Symptom: Live-Pi oracles for utils/headers.ts: TestHeadersToRecordMatchesPi (800 header sequences over case variants of 15 names, repeated names, set-cookie, empty values; compares headersToRecord with Pi's Headers.entries record) and TestProviderHeadersToRecordMatchesPi (1,500 source lists with null deletes and spelling replacement; providerHeadersToRecord), both in ai/headers_utils_oracle_test.go.
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/headers_utils_oracle_test.go::TestHeadersToRecordMatchesPi, ai/headers_utils_oracle_test.go::TestProviderHeadersToRecordMatchesPi, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain and 2 more in docs/bug-ledger.json
  • Release note: Live-Pi oracles for utils/headers.ts: TestHeadersToRecordMatchesPi (800 header sequences over case variants of 15 names, repeated names, set-cookie, empty values; compares headersToRecord with Pi's Headers.entries record) and TestProviderHeadersToRecordMatchesPi (1,500 source lists with null deletes and spelling replacement; providerHeadersToRecord), both in ai/headers_utils_oracle_test.go. No bug found; 8 of 9 mutations red (last-set-cookie, ", " join, lowercase key, set-cookie special case, delete-before-set, later spelling, undefined result, null handling); the ninth (dropping the empty-value-list skip in headersToRecord) is equivalent because Headers never holds a name with no values. Not covered: two spellings of one name inside a single ai.ProviderHeaders source, which Pi resolves by insertion order and a Go map cannot express (the later key wins in Pi; PiG's result depends on map order).

BF-6a6fffa-5

The Skills page said your own skills live in ~/.pig/skills, that a skill's text is added to the system…

  • Source: PiG's own defect
  • Symptom: The Skills page said your own skills live in ~/.pig/skills, that a skill's text is added to the system prompt, and that a bare skill name works in a Piglet.
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain, test/longsession/session_oracle_test.go::TestSessionManagerMatchesPi, test/parity/scenarios/rpc/58-rpc-session-wrong-typed-members.toml
  • Release note: The Skills page said your own skills live in ~/.pig/skills, that a skill's text is added to the system prompt, and that a bare skill name works in a Piglet. PiG reads ~/.pig/agent/skills, lists each skill's name, description and file in the system prompt (the model reads the text on demand, or you load it with /skill:<name>), and pig piglet validate rejects a skill with no origin. The Skills and Prompt templates pages now carry worked examples with the output PiG produces.

BF-6a6fffa-6

Live-Pi oracle TestOpenAICompletionsCompatAndCacheOptionsPayloadMatchesPi…

  • Source: PiG's own defect
  • Symptom: Live-Pi oracle TestOpenAICompletionsCompatAndCacheOptionsPayloadMatchesPi (ai/openai_completions_compat_options_oracle_test.go, 2,500 probes) for the openai-completions compat members and options the other payload oracles never draw: supportsFinishReason, zaiToolStream, supportsOpenAIGrammarTools, supportsMidConvoSystemMessages/ToolAdditions, supportsThinkingTokenBudget, thinkingTokenBudgetField, cacheControlFormat, supportsLongCacheRetention, openRouterRouting, vercelGatewayRouting, cacheRetention and sessionId, over OpenAI/OpenRouter/Vercel/Z.ai/xAI hosts and 0-2 mid-conversation system messages (text, sections, toolsAdded, toolsRemoved).
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/openai_completions_compat_options_oracle_test.go::TestOpenAICompletionsCompatAndCacheOptionsPayloadMatchesPi, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain, test/longsession/session_oracle_test.go::TestSessionManagerMatchesPi and 1 more in docs/bug-ledger.json
  • Release note: Live-Pi oracle TestOpenAICompletionsCompatAndCacheOptionsPayloadMatchesPi (ai/openai_completions_compat_options_oracle_test.go, 2,500 probes) for the openai-completions compat members and options the other payload oracles never draw: supportsFinishReason, zaiToolStream, supportsOpenAIGrammarTools, supportsMidConvoSystemMessages/ToolAdditions, supportsThinkingTokenBudget, thinkingTokenBudgetField, cacheControlFormat, supportsLongCacheRetention, openRouterRouting, vercelGatewayRouting, cacheRetention and sessionId, over OpenAI/OpenRouter/Vercel/Z.ai/xAI hosts and 0-2 mid-conversation system messages (text, sections, toolsAdded, toolsRemoved). No payload difference found. Mutations: 9 red (zaiToolStream, both routings, long-cache gate, 24h retention, thinking-budget default gate, mid-convo system collapse in both directions, mid-convo tool-addition gate off, prompt-cache gate); equivalent: the true && form of the tool-addition gate (no system message survives collapse without the system flag) and the compatBool default arguments (detected compat always sets each flag). Not reached by this oracle: grammar-tool compat (covered by constrained_sampling_oracle_test.go) and supportsFinishReason (a response-parsing flag). Not covered: sessionId set to the empty string, which Pi sends as prompt_cache_key: "" and ai.StreamOptions.SessionID cannot tell from unset.

BF-6a6fffa-7

The settings documentation now says that images.blockImages replaces each image sent to the model with…

  • Source: PiG's own defect
  • Symptom: It said PiG still sent images.
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain, test/longsession/session_oracle_test.go::TestSessionManagerMatchesPi, test/parity/scenarios/rpc/58-rpc-session-wrong-typed-members.toml
  • Release note: The settings documentation now says that images.blockImages replaces each image sent to the model with Image reading is disabled., as PiG does. Before, it said PiG still sent images. The embedded onboarding page no longer says that skipping the first-time setup with Esc makes it appear again.

BF-6a6fffa-8

In --mode json, SIGTERM or SIGHUP during a tool call now stops the output and the session file where Pi…

  • Source: PiG's own defect
  • Symptom: In --mode json, SIGTERM or SIGHUP during a tool call now stops the output and the session file where Pi does: no tool result, aborted turn or agent_end is written after the signal, and extensions still receive session_shutdown.
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain, test/longsession/session_oracle_test.go::TestSessionManagerMatchesPi, test/parity/scenarios/rpc/58-rpc-session-wrong-typed-members.toml
  • Release note: In --mode json, SIGTERM or SIGHUP during a tool call now stops the output and the session file where Pi does: no tool result, aborted turn or agent_end is written after the signal, and extensions still receive session_shutdown.

BF-6a6fffa-9

TestSettingsSelectorMatchesPi opens each row under the Kitty keyboard protocol with CSI-u keys; no…

  • Source: PiG's own defect
  • Symptom: TestSettingsSelectorMatchesPi opens each row under the Kitty keyboard protocol with CSI-u keys; no difference.
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain, internal/codingagent/settings_selector_oracle_test.go::TestSettingsSelectorMatchesPi, test/longsession/session_oracle_test.go::TestSessionManagerMatchesPi and 4 more in docs/bug-ledger.json
  • Release note: TestSettingsSelectorMatchesPi opens each row under the Kitty keyboard protocol with CSI-u keys; no difference.

BF-6ac4c8c

Ctrl+V pastes clipboard text in Termux

  • Source: A fix Pi shipped that PiG ported
  • Symptom: Ctrl+V pastes clipboard text in Termux.
  • Root cause: Termux reports platform "android" (pi clipboard.ts:55-66 readClipboardText, :133 copyToClipboard, #10391). readClipboardText now runs termux-clipboard-get whenever TERMUX_VERSION is set, outside the linux branch, and the copy error names Termux:API on every platform. Tests: Android read result/empty cases, Android skips Linux commands, Android copy via termux-clipboard-set, the Termux:API error on android and linux, and the Ctrl+V caller path.
  • User impact: An operation failed or was rejected
  • Platform: Linux, Android and Termux
  • Fixed in: 0.5.0
  • Fix commits: 6ac4c8c763 (2026-10-07)
  • Tests: coding/cli/dotenv_autoload_test.go::TestLaunchDirectoryDotenvFilesDoNotReachPigEnvironment, internal/codingagent/clipboard_copy_test.go::TestCopyToClipboardReportsTheTermuxAPIRequirementOnAndroid, internal/codingagent/clipboard_copy_test.go::TestCopyToClipboardTermuxOnAndroidWritesThroughTermuxClipboardSet, internal/codingagent/clipboard_paste_test.go::TestClipboardPasteInsertsTermuxTextOnAndroid, internal/codingagent/clipboard_read_text_test.go::TestReadClipboardTextAndroidSkipsLinuxCommands, internal/codingagent/clipboard_read_text_test.go::TestReadClipboardTextTermuxReadsOnAndroid, internal/codingagent/selfupdate_test.go::TestSelfReplaceAtWithCommitKeepsOnlyTheNewExecutable
  • Release note: Ctrl+V pastes clipboard text in Termux. PiG read termux-clipboard-get only where the platform is linux, and Termux reports android. A failed copy in Termux now also shows the Termux:API install hint on Android.

BF-6cb29f0

Started from a directory reached through a symlink, pig install/remove/list/update resolve local…

  • Source: PiG's own defect
  • Symptom: Started from a directory reached through a symlink, pig install/remove/list/update resolve local sources against the physical working directory, and --session with an empty file records the physical directory as the session cwd, as Pi does.
  • Root cause: package-manager-cli.ts:951 resolves install/remove/list/update sources against process.cwd(), and SessionManager.open starts an empty session file in process.cwd() (session-manager.ts:1782). Pig took os.Getwd's $PWD, so from a symlinked directory 'pig list' printed a project package's path through the link and '--session empty.jsonl' recorded the link as the session cwd. New binary tests run pig through a symlink with PWD=link. Package tests that set up sources from t.TempDir() and then ran the CLI there now start from the physical temp dir, the directory Pi's process.cwd() would report (they mixed /var and /private/var on macOS).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 630eb9ad7a (2026-10-13), 6cb29f0c23 (2026-10-13), b2b6d6f226 (2026-10-13), b607c35564 (2026-10-13)
  • Tests: coding/cli/package_cli_chalk_test.go::TestPackageCommandsStyleOutputLikeChalk, coding/cli/package_cli_output_test.go::TestPackageRemovalProgressAndEmptySettings, coding/cli/package_source_parsing_upstream_test.go::TestPackageBareSourcesNeverInvokeNpm, coding/cli/package_ssh_upstream_test.go::TestBareSCPPackageSourceRemainsLocalAtCLIAndIdentityBoundaries, coding/cli/startup_physical_cwd_test.go::TestEmptySessionFileStartsInThePhysicalWorkingDirectory, coding/cli/startup_physical_cwd_test.go::TestPackageListNamesProjectPackagesThroughThePhysicalWorkingDirectory, coding/cli/startup_physical_cwd_test.go::TestStartupWorkingDirectoryIsThePhysicalPath, internal/codingagent/bug_report_test.go::TestBugExportWritesArchiveRecordsSessionAndLinksPigIssue and 3 more in docs/bug-ledger.json
  • Release note: Started from a directory reached through a symlink, pig install/remove/list/update resolve local sources against the physical working directory, and --session with an empty file records the physical directory as the session cwd, as Pi does.

BF-6cb29f0-2

Started from a directory reached through a symlink, /export <file>.jsonl and /bug now write and name…

  • Source: PiG's own defect
  • Symptom: Started from a directory reached through a symlink, /export <file>.jsonl and /bug now write and name their files under the physical working directory, as Pi does.
  • Root cause: package-manager-cli.ts:951 resolves install/remove/list/update sources against process.cwd(), and SessionManager.open starts an empty session file in process.cwd() (session-manager.ts:1782). Pig took os.Getwd's $PWD, so from a symlinked directory 'pig list' printed a project package's path through the link and '--session empty.jsonl' recorded the link as the session cwd. New binary tests run pig through a symlink with PWD=link. Package tests that set up sources from t.TempDir() and then ran the CLI there now start from the physical temp dir, the directory Pi's process.cwd() would report (they mixed /var and /private/var on macOS).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 630eb9ad7a (2026-10-13), 68b129d018 (2026-10-13), 6cb29f0c23 (2026-10-13), b2b6d6f226 (2026-10-13), b607c35564 (2026-10-13)
  • Tests: coding/cli/package_cli_chalk_test.go::TestPackageCommandsStyleOutputLikeChalk, coding/cli/package_cli_output_test.go::TestPackageRemovalProgressAndEmptySettings, coding/cli/package_source_parsing_upstream_test.go::TestPackageBareSourcesNeverInvokeNpm, coding/cli/package_ssh_upstream_test.go::TestBareSCPPackageSourceRemainsLocalAtCLIAndIdentityBoundaries, coding/cli/startup_physical_cwd_test.go::TestEmptySessionFileStartsInThePhysicalWorkingDirectory, coding/cli/startup_physical_cwd_test.go::TestPackageListNamesProjectPackagesThroughThePhysicalWorkingDirectory, coding/cli/startup_physical_cwd_test.go::TestStartupWorkingDirectoryIsThePhysicalPath, internal/codingagent/bug_report_test.go::TestBugExportWritesArchiveRecordsSessionAndLinksPigIssue and 4 more in docs/bug-ledger.json
  • Release note: Started from a directory reached through a symlink, /export <file>.jsonl and /bug now write and name their files under the physical working directory, as Pi does.

BF-6cb29f0-3

Switching to or importing an empty session file from a directory reached through a symlink starts the session…

  • Source: PiG's own defect
  • Symptom: Switching to or importing an empty session file from a directory reached through a symlink starts the session in the physical working directory, as Pi does.
  • Root cause: package-manager-cli.ts:951 resolves install/remove/list/update sources against process.cwd(), and SessionManager.open starts an empty session file in process.cwd() (session-manager.ts:1782). Pig took os.Getwd's $PWD, so from a symlinked directory 'pig list' printed a project package's path through the link and '--session empty.jsonl' recorded the link as the session cwd. New binary tests run pig through a symlink with PWD=link. Package tests that set up sources from t.TempDir() and then ran the CLI there now start from the physical temp dir, the directory Pi's process.cwd() would report (they mixed /var and /private/var on macOS).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6cb29f0c23 (2026-10-13), b2b6d6f226 (2026-10-13), b607c35564 (2026-10-13)
  • Tests: coding/cli/package_cli_chalk_test.go::TestPackageCommandsStyleOutputLikeChalk, coding/cli/package_cli_output_test.go::TestPackageRemovalProgressAndEmptySettings, coding/cli/package_source_parsing_upstream_test.go::TestPackageBareSourcesNeverInvokeNpm, coding/cli/package_ssh_upstream_test.go::TestBareSCPPackageSourceRemainsLocalAtCLIAndIdentityBoundaries, coding/cli/startup_physical_cwd_test.go::TestEmptySessionFileStartsInThePhysicalWorkingDirectory, coding/cli/startup_physical_cwd_test.go::TestPackageListNamesProjectPackagesThroughThePhysicalWorkingDirectory, coding/cli/startup_physical_cwd_test.go::TestStartupWorkingDirectoryIsThePhysicalPath, internal/codingagent/bug_report_test.go::TestBugExportWritesArchiveRecordsSessionAndLinksPigIssue
  • Release note: Switching to or importing an empty session file from a directory reached through a symlink starts the session in the physical working directory, as Pi does.

BF-6ce1db1

In print and JSON mode, SIGTERM and SIGHUP always leave their line in exit.log

  • Source: PiG's own defect
  • Symptom: In print and JSON mode, SIGTERM and SIGHUP always leave their line in exit.log.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 6ce1db1b0d (2026-10-11)
  • Tests: None recorded.
  • Release note: In print and JSON mode, SIGTERM and SIGHUP always leave their line in exit.log.

BF-6d0ec02

Durable (Pi eb2fce43): a wait that closes a cycle through waits and held owners now fails the task's commit…

  • Source: PiG's own defect
  • Symptom: Every task on such a cycle waited forever.
  • Root cause: A background task may now wait on the owner of its conversation, because that wait is no cycle, and a task may wait on an ended owner.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 6d0ec024f2 (2026-10-12)
  • Tests: durable/core/driver/remote_error_names_test.go::TestRemoteErrorMapsToATypedErrorOnlyByItsClassName, durable/errors_session_failed_test.go::TestStorageRequestErrorAndSessionFailedMatchErrorsTS, durable/harness/harness_structured_test.go::TestWaiting, durable/harness/scheduler_retention_test.go::TestSchedulerRetention, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase and 3 more in docs/bug-ledger.json
  • Release note: Durable (Pi eb2fce43): a wait that closes a cycle through waits and held owners now fails the task's commit with Task <id> cannot wait on <member>: <cycle> is a wait cycle, which names the cycle. Before, every task on such a cycle waited forever. The only cycles caught before were a task waiting on itself or on a task in its owner chain. A background task may now wait on the owner of its conversation, because that wait is no cycle, and a task may wait on an ended owner.

BF-6d3cfc8

A user_bash operations object from an extension is no longer released while a command is running through it

  • Source: PiG's own defect
  • Symptom: A user_bash operations object from an extension is no longer released while a command is running through it.
  • Root cause: The extension's handle is released when the host drops the object, but the running exec did not keep the object reachable, so a garbage collection mid-command could delete the extension's table entry under the command.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 6d3cfc863d (2026-10-21, #278)
  • Tests: coding/extension/host/subprocess/user_bash_operations_test.go::TestUserBashOperationsAreNotReleasedWhileAnExecRuns
  • Release note: A user_bash operations object from an extension is no longer released while a command is running through it. The extension's handle is released when the host drops the object, but the running exec did not keep the object reachable, so a garbage collection mid-command could delete the extension's table entry under the command.

BF-6d9b403

The /config selector keeps the first-seen order of groups that tie on origin, scope and source (auto…

  • Source: PiG's own defect
  • Symptom: The /config selector keeps the first-seen order of groups that tie on origin, scope and source (auto resources from different base directories); it followed Go map order (tui/config_selector.go BuildResourceGroups, config-selector.ts buildGroups; regression TestBuildResourceGroupsTiesKeepFirstSeenOrder).
  • Root cause: Pi's buildGroups uses a Map (insertion order) and a stable sort; Pig collected groups from a Go map, so groups tying on origin, scope and source (auto resources from different baseDirs) came out in random order.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6d9b403c0a (2026-10-11)
  • Tests: tui/config_selector_test.go::TestBuildResourceGroupsTiesKeepFirstSeenOrder
  • Release note: The /config selector keeps the first-seen order of groups that tie on origin, scope and source (auto resources from different base directories); it followed Go map order (tui/config_selector.go BuildResourceGroups, config-selector.ts buildGroups; regression TestBuildResourceGroupsTiesKeepFirstSeenOrder).

BF-6debb12

fix(train): gate repairs after merging train 3 and the ordering stack

  • Source: PiG's own defect
  • Symptom: fix(train): gate repairs after merging train 3 and the ordering stack
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 6debb12f2e (2026-10-21)
  • Tests: None recorded.

BF-6dfa6bf

go install github.com/MichaelKinsy/PiG/cmd/pig@<version> downloads a root module of 181 MB instead of 304 MB

  • Source: PiG's own defect
  • Symptom: go install github.com/MichaelKinsy/PiG/cmd/pig@<version> downloads a root module of 181 MB instead of 304 MB.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 6dfa6bfa3b (2026-10-19, #226)
  • Tests: automation/release/modulesize/main_test.go::TestCommittedBudgetStaysFarBelowTheGoLimit, automation/release/modulesize/main_test.go::TestMeasureCountsOnlyWhatGoPutsInTheModuleZip, automation/release/modulesize/main_test.go::TestOverBudgetListsTheLargestFilesAndDirectories, automation/release/modulesize/main_test.go::TestReadBudget
  • Release note: go install github.com/MichaelKinsy/PiG/cmd/pig@<version> downloads a root module of 181 MB instead of 304 MB. Nine data-only directories (parity interface ledgers, pin ledgers, upstream-sync ledgers and large test data) have their own go.mod, so Go leaves them out of the root module zip. The files stay in the repository.

BF-6e6a0dd

A session message with a member of another JSON type than Pi writes is now kept, as Pi keeps it

  • Source: PiG's own defect
  • Symptom: PiG dropped the message from the session context, the projection and RPC get_messages.
  • Root cause: parseSessionEntryLine keeps any JSON.parse value as an entry's message (session-manager.ts:617-623), and buildSessionContext and the projection push it as is, so Pi keeps a message that is a number, a string or an array in its context, projection and get_messages. PiG dropped it. decodeLax now decodes such a message as a custom message without members that keeps its text, and SourceJSON writes that text. Having no role, it reaches no provider, as Pi's convertToLlm default case returns undefined (messages.ts:189-192). A null message stays undecodable: Pi's context build throws reading its role (getSessionContextSettings).
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6e6a0dd1cc (2026-10-10)
  • Tests: agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain, internal/codingagent/session_undecodable_test.go::TestUndecodableEntriesAreCountedAndOmitted, internal/sessionentry/union_test.go::TestDecodeSessionEntryKeepsRecordsNoMemberDescribes, test/longsession/session_oracle_test.go::TestSessionManagerMatchesPi
  • Release note: A session message with a member of another JSON type than Pi writes is now kept, as Pi keeps it. Examples are a string timestamp, numeric content, a text block whose text is a number, a usage count written as a string, isError: "no", a numeric or missing role, and a message that is a number, a string or an array rather than an object. Before, PiG dropped the message from the session context, the projection and RPC get_messages. The message is written exactly as it was read. PiG reads a wrong-typed member as absent. An object or array member keeps the parts that have the right types. A content block with an unknown or wrong-typed type is left out of the provider request, as Pi's provider conversions skip it.

BF-6ee6eae

A Session delivers each event to its listeners the way Pi's AgentSession does

  • Source: PiG's own defect
  • Symptom: Every delivery used the list as it was when the delivery began.
  • Root cause: EmitMessage/EmitError/EmitClose now visit the next entry registered after the last one run. New tests: the live-set emit, the closed-end drop and placement hook of InMemoryTransport, the redirect mix-up page and context cause of OAuthCallbackServer, SetProtocolVersion's header, McpAbortError's defaults. 71 mutants recorded; equivalents listed in evidence.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1278df8e33 (2026-10-10), 6ee6eae918 (2026-10-10)
  • Tests: coding/codemode_session_upstream_test.go::TestUpstreamCodemodeModels, coding/session_boundaries_drafts_upstream_test.go::TestUpstreamSessionBoundariesDrafts, coding/session_event_listener_upstream_test.go::TestSessionNotifyListenersWalksTheLiveListenerListAsPi, coding/session_event_listener_upstream_test.go::TestSessionSubscribeNotifiesEveryListenerInOrderAndUnsubscribeLeavesTheOthers, coding/session_sections_test.go::TestSessionDefaultSectionsReachEventsProviderAndPersistenceOnce, mcp/errors_abort_test.go::TestMcpAbortErrorHasPiDefaultsNameAndNoCause, mcp/mcptest/inmemory_test.go::TestInMemoryTransportDropsQueuedMessagesOfAClosedEndAndReportsPlacement, mcp/oauth/callback_server_options_test.go::TestOAuthCallbackServerNamesTheRedirectMixUpAndReturnsTheContextCause and 2 more in docs/bug-ledger.json
  • Release note: A Session delivers each event to its listeners the way Pi's AgentSession does. A listener that unsubscribes itself or another listener during delivery changes which listeners that delivery still reaches, and a listener subscribed during delivery receives the same event. Before, every delivery used the list as it was when the delivery began.

BF-6f26a84

Xiaomi MiMo models send only the thinking on/off toggle (no reasoning_effort) and expose only the off and…

  • Source: PiG's own defect
  • Symptom: Xiaomi MiMo models send only the thinking on/off toggle (no reasoning_effort) and expose only the off and high thinking levels, fixing the 400 at minimal.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6f26a84f7f (2026-10-14, #10818)
  • Tests: ai/openai_tool_choice_formats_upstream_test.go::TestOpenAICompletionsToolChoiceReplayAndFormatsUpstream, ai/xiaomi_models_upstream_test.go::TestXiaomiReasoningModelsAreAnOffOnToggleUpstream
  • Release note: Xiaomi MiMo models send only the thinking on/off toggle (no reasoning_effort) and expose only the off and high thinking levels, fixing the 400 at minimal. Carried forward from Pi 6374c2ba and 381a2d8e (earendil-works/pi#10818), past the 1.2.0+3daa2028 pin.

BF-6f4a82f

Validation errors for propertyNames name the failing member in their path, contains reports only "must…

  • Source: PiG's own defect
  • Symptom: Validation errors for propertyNames name the failing member in their path, contains reports only "must contain at least 1 valid item", and a failing then branch reports only must match "then" schema while a failing else branch also lists its errors, as in Pi.
  • Root cause: validateToolArguments (packages/ai/src/utils/validation.ts, with typebox 1.3.27 Compile/Value.Convert/Check/Errors from internal/experimental/protocol/testdata/typebox, testdata/validate_tool_args.mjs) runs on 12000 seeded schemas (scalars, type unions, enum, const, bounds, patterns, objects with required/additionalProperties/patternProperties/dependentRequired, arrays, anyOf/oneOf/allOf, defaults) and argument values (right types, stringified numbers and booleans, nulls, wrong shapes, big numbers). The coerced result or the whole error message must match. 118 of 6000 differed before (red): (1) TypeBox's error context keeps the first 8 errors (Settings.maxErrors) and Go listed all; (2)…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), e8cc072521 (2026-10-08), 6f4a82fd86 (2026-10-09), 97e178f7f6 (2026-10-09), d490a8b161 (2026-10-09)
  • Tests: ai/streaming_json_oracle_test.go::TestStreamingJSONMatchesPiOnSeededInput, ai/validate_tool_args_oracle_test.go::TestValidateToolArgumentsMatchesPiOnSeededSchemas, chord/delta/own_key_order_test.go::TestDiffRevisionsVisitsArrayIndexKeysFirstInNumericOrder, internal/chord/chordjson/own_key_order_test.go::TestCopyFollowsPisKeyOrderAndMessages, internal/evals/plan_oracle_test.go::TestPlanMatchesPiOnSeededProbes, internal/evals/report_oracle_test.go::TestEvalReportMatchesPiOnSeededDesigns
  • Release note: Validation errors for propertyNames name the failing member in their path, contains reports only "must contain at least 1 valid item", and a failing then branch reports only must match "then" schema while a failing else branch also lists its errors, as in Pi.

BF-6f7daf1

fix: clone Windows Git Packages with core.longpaths so a deep file:// source path fits

  • Source: PiG's own defect
  • Symptom: fix: clone Windows Git Packages with core.longpaths so a deep file:// source path fits
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: Unreleased
  • Fix commits: 6f7daf115e (2026-10-22)
  • Tests: None recorded.

BF-6fb1307

openai-codex-responses reads instructions from the initial system message of the resolved transcript before…

  • Source: PiG's own defect
  • Symptom: openai-codex-responses reads instructions from the initial system message of the resolved transcript before transformMessages drops an errored or aborted assistant turn (ai/openai_responses.go, openai-codex-responses.ts:551): a transcript that starts with such a turn followed by a system message no longer sends that message as the prompt.
  • Root cause: Real fixes: anthropic.go buildParams initial system message from the untransformed transcript; managed effort 'high' unless forceAdaptiveThinking; unmapped xhigh/max -> high; managed simple max_tokens adjustment. openai_responses.go: codex instruction role from the selected model's reasoning flag.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 6fb1307525 (2026-10-11), 81e584bdba (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/anthropic_effort_strict_test.go::TestAnthropicManagedEffortMapping, ai/anthropic_messages_compat_options_oracle_test.go::TestAnthropicMessagesCompatAndOptionsPayloadMatchesPi, ai/bedrock_payload_oracle_test.go::TestBedrockConversePayloadMatchesPi, ai/mistral_messages_payload_oracle_test.go::TestMistralMessagesPayloadMatchesPi and 5 more in docs/bug-ledger.json
  • Release note: openai-codex-responses reads instructions from the initial system message of the resolved transcript before transformMessages drops an errored or aborted assistant turn (ai/openai_responses.go, openai-codex-responses.ts:551): a transcript that starts with such a turn followed by a system message no longer sends that message as the prompt. Found by drawing the responses compat members the payload oracle never drew (supportsDeveloperRole, supportsAdditionalTools, supportsToolSearch, supportsExplicitPromptCacheMode, sessionAffinityFormat, supportsOpenAIGrammarTools) in checkResponsesPayload; 5 mutants of those flags and the codex initial-system source go red (grammar tools stay inert there: the drawn tools carry no constrainedSampling, covered by constrained_sampling_oracle_test.go).

BF-70d51e5

Fixed bash and PowerShell results losing Took after reloading a session, and the live Took including…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: Fixed bash and PowerShell results losing Took after reloading a session, and the live Took including wall-clock steps; both now show the recorded execution time.
  • Root cause: durationMs (agent-loop.ts executePreparedToolCall, createToolResultMessage, emitToolExecutionEnd; extensions/types.ts ToolRenderContext and ToolExecutionEndEvent; nested-tool-calls.ts; renderers/bash.ts): - agent loop records execute() time in whole ms on a monotonic clock, excluding hooks, on the tool result message (between isError and timestamp), tool_execution_end (JSON/RPC and extensions, all SDKs) and RunToolCall outcomes; a call that did not run has none; a throwing tool still has one. - the tool render context carries durationMs (nil while partial, for a call that did not run and for legacy results); bash/PowerShell Took shows the recorded value live and after a session reload…
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: 70d51e54f4 (2026-10-07)
  • Tests: agent/agent_loop_upstream_test.go::TestAgentLoop_RecordsDurationOfThrowingTool, agent/agent_loop_upstream_test.go::TestAgentLoop_RecordsToolExecutionDurationExcludingHooks, agent/agent_loop_upstream_test.go::TestRunToolCallOutcomeCarriesDurationMs, agent/message_json_test.go::TestAgentMessageJSONToolResultDurationMs, agent/tool_result_fields_test.go::TestToolResultMessageFieldsMatchUpstream, coding/nested_tool_call_events_test.go::TestNestedToolCallEndEventForwardsOutcomeDurationMs, coding/session_tool_duration_test.go::TestSessionRecordsToolDurationOnEventsAndPersistedResult, internal/codingagent/interactive_resume_tools_test.go::TestInteractiveMode_ResumeShellToolsShowRecordedDuration and 8 more in docs/bug-ledger.json
  • Release note: Fixed bash and PowerShell results losing Took after reloading a session, and the live Took including wall-clock steps; both now show the recorded execution time.

BF-70d51e5-2

Fixed the message-format page: it said PiG does not carry the nestedCalls record on tool result messages…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: Fixed the message-format page: it said PiG does not carry the nestedCalls record on tool result messages, which it has written since 1.0.4, and it now lists the field; the page also stops citing a replace field that Pi's page no longer lists.
  • Root cause: durationMs (agent-loop.ts executePreparedToolCall, createToolResultMessage, emitToolExecutionEnd; extensions/types.ts ToolRenderContext and ToolExecutionEndEvent; nested-tool-calls.ts; renderers/bash.ts): - agent loop records execute() time in whole ms on a monotonic clock, excluding hooks, on the tool result message (between isError and timestamp), tool_execution_end (JSON/RPC and extensions, all SDKs) and RunToolCall outcomes; a call that did not run has none; a throwing tool still has one. - the tool render context carries durationMs (nil while partial, for a call that did not run and for legacy results); bash/PowerShell Took shows the recorded value live and after a session reload…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 70d51e54f4 (2026-10-07)
  • Tests: agent/agent_loop_upstream_test.go::TestAgentLoop_RecordsDurationOfThrowingTool, agent/agent_loop_upstream_test.go::TestAgentLoop_RecordsToolExecutionDurationExcludingHooks, agent/agent_loop_upstream_test.go::TestRunToolCallOutcomeCarriesDurationMs, agent/message_json_test.go::TestAgentMessageJSONToolResultDurationMs, agent/tool_result_fields_test.go::TestToolResultMessageFieldsMatchUpstream, coding/nested_tool_call_events_test.go::TestNestedToolCallEndEventForwardsOutcomeDurationMs, coding/session_tool_duration_test.go::TestSessionRecordsToolDurationOnEventsAndPersistedResult, internal/codingagent/interactive_resume_tools_test.go::TestInteractiveMode_ResumeShellToolsShowRecordedDuration and 8 more in docs/bug-ledger.json
  • Release note: Fixed the message-format page: it said PiG does not carry the nestedCalls record on tool result messages, which it has written since 1.0.4, and it now lists the field; the page also stops citing a replace field that Pi's page no longer lists.

BF-70d8a41

Keybinding conflict detection ignores user bindings for actions the table does not define, as Pi's…

  • Source: PiG's own defect
  • Symptom: Keybinding conflict detection ignores user bindings for actions the table does not define, as Pi's KeybindingsManager.rebuild does, so a typo'd action no longer appears in a conflict (oracles TestKeybindingsManagerMatchesPi, TestAppKeybindingsManagerMatchesPi).
  • Root cause: Matching already agreed everywhere. Fixed in both managers; reverting either fails the matching test (2 failures). Pi orders conflicts by the user's JSON key order, which Pig's map decoding does not keep, so conflicts compare as sorted sets.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 70d8a41e14 (2026-10-07)
  • Tests: internal/codingagent/app_keybindings_oracle_test.go::TestAppKeybindingsManagerMatchesPi, tui/keybindings_matches_oracle_test.go::TestKeybindingsManagerMatchesPi
  • Release note: Keybinding conflict detection ignores user bindings for actions the table does not define, as Pi's KeybindingsManager.rebuild does, so a typo'd action no longer appears in a conflict (oracles TestKeybindingsManagerMatchesPi, TestAppKeybindingsManagerMatchesPi).

BF-70e1841

/settings warnings submenu: a warning the settings never set shows as on and a toggle always reports the…

  • Source: PiG's own defect
  • Symptom: /settings warnings submenu: a warning the settings never set shows as on and a toggle always reports the warning as set (Pi's ?? true).
  • Root cause: Every callback with its arguments, the callback count after each key, and every frame's text are compared; Pi's callbacks reached include onThemePreview, onThemeChange, onModelThinkingLevelChange/Remove and 30 more. Pi has no PiG 'Mask secret input' row (D80), so the driver inserts that row and its callback into a temp copy of Pi's settings-selector.js at Pig's position. One real difference: Pi's WarningSettingsSubmenu shows '(this.state.anthropicExtraUsage ?? true)' and always writes the toggled value; Pig's submenu used the Go zero value, so a warning the settings never set showed as off and its first toggle reported {} (the Set flag was never set). It now defaults to on and marks the…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d1f258f335 (2026-10-06), 70e1841cd8 (2026-10-07), c8e3d86378 (2026-10-07)
  • Tests: internal/codingagent/settings_selector_oracle_test.go::TestSettingsSelectorMatchesPi, internal/codingagent/settings_submenu_oracle_test.go::TestSettingsSubmenusMatchPi, test/parity/scenarios/settings/08-settings-search-editing.toml, tui/settings_list_input_test.go::TestSettingsListAnyDelegatedKeyResetsTheSelectionToTheFirstMatch, tui/settings_list_input_test.go::TestSettingsListBoundActionsRunInUpstreamOrder, tui/settings_list_input_test.go::TestSettingsListEmptyAndSingletonNavigationBoundaries, tui/settings_list_input_test.go::TestSettingsListSearchDelegatesEditingKeysToTheInput, tui/settings_list_input_test.go::TestSettingsListSubmenuReceivesEveryKeyIncludingCancel and 2 more in docs/bug-ledger.json
  • Release note: /settings warnings submenu: a warning the settings never set shows as on and a toggle always reports the warning as set (Pi's ?? true).

BF-7177d5f

fix(tui): card of an unregistered tool is Pi's formatToolExecution

  • Source: PiG's own defect
  • Symptom: fix(tui): card of an unregistered tool is Pi's formatToolExecution
  • Root cause: tool-execution.ts updateDisplay draws a tool without a definition (hasRendererDefinition false, e.g. a hallucinated tool name) as one Text: toolTitle name, a blank line, JSON.stringify(args, null, 2) and the ANSI-stripped result on the next line. Pig drew a compact 'x:1' summary (with sorted keys, cut at 40/80 chars) and a blank row before the result. Found by running scripted replies through the real Pi and Pig (mock OpenAI-completions server, tmux, session files compared too). TestUnregisteredToolCardIsUpstreamFormatToolExecution is red without the change; the two component-only tests that used a definition-less card for a registered tool now give it a definition.…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 7177d5f92b (2026-10-11)
  • Tests: tui/summary_output_pad_test.go::TestToolAndBashBlocksFollowOutputPad, tui/tool_execution_test.go::TestToolExecutionStreamingFallbackUsesLeadingPreview, tui/tool_execution_test.go::TestUnregisteredToolCardIsUpstreamFormatToolExecution

BF-72e7ab2

A tool card whose app.tools.expand key is unbound shows an empty key in "..

  • Source: PiG's own defect
  • Symptom: A tool card whose app.tools.expand key is unbound shows an empty key in "...
  • Root cause: Pi's keyText answers "" for an action with no key bound; AppKeyText returned the fallback default whenever the installed resolver answered empty, so the tool card expand hint kept 'ctrl+o' after the user unbound app.tools.expand. The fallback now applies only when no resolver is installed, as its doc says. Reverting the fix fails them.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 72e7ab2dbf (2026-10-11), c4ad739eb8 (2026-10-11), fac470319e (2026-10-11)
  • Tests: coding/extension/host/inproc/registries_test.go::TestShortcuts_DiagnosticMessageMatchesUpstream, coding/extension/host/inproc/shortcuts_oracle_test.go::TestRunnerShortcutsMatchPi, internal/codingagent/builtin_tool_cards_oracle_test.go::TestBuiltinToolCardsMatchPi, internal/codingagent/extension_editor_test.go::TestExtensionEditorHandleInputMatchesPi, tui/extension_input_oracle_test.go::TestExtensionInputMatchesPi, tui/extension_selector_oracle_test.go::TestExtensionSelectorMatchesPi
  • Release note: A tool card whose app.tools.expand key is unbound shows an empty key in "... (N more lines, to expand)" as Pi does, instead of the default ctrl+o (tui/keybinding_hints.go AppKeyText falls back only when no resolver is installed; TestBuiltinToolCardsMatchPi gained rebound/multi-key/unbound expand probes, 5 red before).

BF-72e7ab2-2

TestMcpManagerViewMatchesPi renders the /mcp manager footers under rebound, multi-key and unbound…

  • Source: PiG's own defect
  • Symptom: TestMcpManagerViewMatchesPi renders the /mcp manager footers under rebound, multi-key and unbound tui.select.confirm/cancel/up/down (a quarter of its 120 probes, against the merged tui and app table); no difference; the hard-coded-hint mutation is red (a matching-path mutation is not observable in the rendered rows).
  • Root cause: Pi's keyText answers "" for an action with no key bound; AppKeyText returned the fallback default whenever the installed resolver answered empty, so the tool card expand hint kept 'ctrl+o' after the user unbound app.tools.expand. The fallback now applies only when no resolver is installed, as its doc says. Reverting the fix fails them.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 33fe10d58e (2026-10-11), 72e7ab2dbf (2026-10-11), c4ad739eb8 (2026-10-11), fac470319e (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/models_error_oracle_test.go::TestModelsErrorMessageMatchesPi, coding/extension/host/inproc/registries_test.go::TestShortcuts_DiagnosticMessageMatchesUpstream, coding/extension/host/inproc/shortcuts_oracle_test.go::TestRunnerShortcutsMatchPi, coding/mcpext/manager_view_oracle_test.go::TestMcpManagerViewMatchesPi and 8 more in docs/bug-ledger.json
  • Release note: TestMcpManagerViewMatchesPi renders the /mcp manager footers under rebound, multi-key and unbound tui.select.confirm/cancel/up/down (a quarter of its 120 probes, against the merged tui and app table); no difference; the hard-coded-hint mutation is red (a matching-path mutation is not observable in the rendered rows).

BF-72e7ab2-3

TestLlamaViewMatchesPi runs a quarter of its 700 scenarios under rebound, multi-key and unbound…

  • Source: PiG's own defect
  • Symptom: TestLlamaViewMatchesPi runs a quarter of its 700 scenarios under rebound, multi-key and unbound tui.select.* bindings (hint rows and the progress view's cancel key); no difference; mutations on the hint key, the stop hint and the progress cancel match are red.
  • Root cause: Pi's keyText answers "" for an action with no key bound; AppKeyText returned the fallback default whenever the installed resolver answered empty, so the tool card expand hint kept 'ctrl+o' after the user unbound app.tools.expand. The fallback now applies only when no resolver is installed, as its doc says. Reverting the fix fails them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 33fe10d58e (2026-10-11), 72e7ab2dbf (2026-10-11), c4ad739eb8 (2026-10-11), fac470319e (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/models_error_oracle_test.go::TestModelsErrorMessageMatchesPi, coding/extension/host/inproc/registries_test.go::TestShortcuts_DiagnosticMessageMatchesUpstream, coding/extension/host/inproc/shortcuts_oracle_test.go::TestRunnerShortcutsMatchPi, coding/mcpext/manager_view_oracle_test.go::TestMcpManagerViewMatchesPi and 9 more in docs/bug-ledger.json
  • Release note: TestLlamaViewMatchesPi runs a quarter of its 700 scenarios under rebound, multi-key and unbound tui.select.* bindings (hint rows and the progress view's cancel key); no difference; mutations on the hint key, the stop hint and the progress cancel match are red.

BF-72e7ab2-4

TestCodemodeCardsMatchPi and TestMcpToolCardsMatchPi repeat their collapsed probes under rebound…

  • Source: PiG's own defect
  • Symptom: TestCodemodeCardsMatchPi and TestMcpToolCardsMatchPi repeat their collapsed probes under rebound, multi-key and unbound app.tools.expand (the expand hint of the codemode and MCP cards); no difference; hard-coded-key mutations are red.
  • Root cause: Pi's keyText answers "" for an action with no key bound; AppKeyText returned the fallback default whenever the installed resolver answered empty, so the tool card expand hint kept 'ctrl+o' after the user unbound app.tools.expand. The fallback now applies only when no resolver is installed, as its doc says. Reverting the fix fails them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 33fe10d58e (2026-10-11), 72e7ab2dbf (2026-10-11), c4ad739eb8 (2026-10-11), fac470319e (2026-10-11)
  • Tests: ai/models_error_oracle_test.go::TestModelsErrorMessageMatchesPi, coding/extension/host/inproc/registries_test.go::TestShortcuts_DiagnosticMessageMatchesUpstream, coding/extension/host/inproc/shortcuts_oracle_test.go::TestRunnerShortcutsMatchPi, coding/mcpext/manager_view_oracle_test.go::TestMcpManagerViewMatchesPi, internal/codingagent/builtin_tool_cards_oracle_test.go::TestBuiltinToolCardsMatchPi, internal/codingagent/extension_editor_test.go::TestExtensionEditorHandleInputMatchesPi, internal/codingagent/llama/ui_oracle_test.go::TestLlamaViewMatchesPi, internal/codingagent/mcp_cards_oracle_test.go::TestMcpToolCardsMatchPi and 2 more in docs/bug-ledger.json
  • Release note: TestCodemodeCardsMatchPi and TestMcpToolCardsMatchPi repeat their collapsed probes under rebound, multi-key and unbound app.tools.expand (the expand hint of the codemode and MCP cards); no difference; hard-coded-key mutations are red.

BF-72e7ab2-5

TestBashExecutionComponentMatchesPi probes the collapsed expand hint under rebound, multi-key and unbound…

  • Source: PiG's own defect
  • Symptom: TestBashExecutionComponentMatchesPi probes the collapsed expand hint under rebound, multi-key and unbound app.tools.expand with the merged tui and app table; no difference; the fixed-key mutation is red.
  • Root cause: Pi's keyText answers "" for an action with no key bound; AppKeyText returned the fallback default whenever the installed resolver answered empty, so the tool card expand hint kept 'ctrl+o' after the user unbound app.tools.expand. The fallback now applies only when no resolver is installed, as its doc says. Reverting the fix fails them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 33fe10d58e (2026-10-11), 72e7ab2dbf (2026-10-11), c4ad739eb8 (2026-10-11), fac470319e (2026-10-11)
  • Tests: ai/models_error_oracle_test.go::TestModelsErrorMessageMatchesPi, coding/extension/host/inproc/registries_test.go::TestShortcuts_DiagnosticMessageMatchesUpstream, coding/extension/host/inproc/shortcuts_oracle_test.go::TestRunnerShortcutsMatchPi, coding/mcpext/manager_view_oracle_test.go::TestMcpManagerViewMatchesPi, internal/codingagent/bash_execution_oracle_test.go::TestBashExecutionComponentMatchesPi, internal/codingagent/builtin_tool_cards_oracle_test.go::TestBuiltinToolCardsMatchPi, internal/codingagent/extension_editor_test.go::TestExtensionEditorHandleInputMatchesPi, internal/codingagent/llama/ui_oracle_test.go::TestLlamaViewMatchesPi and 3 more in docs/bug-ledger.json
  • Release note: TestBashExecutionComponentMatchesPi probes the collapsed expand hint under rebound, multi-key and unbound app.tools.expand with the merged tui and app table; no difference; the fixed-key mutation is red.

BF-72e7ab2-6

TestSummaryAndSkillMessageComponentsMatchPi probes the compaction, branch summary and skill messages under…

  • Source: PiG's own defect
  • Symptom: TestSummaryAndSkillMessageComponentsMatchPi probes the compaction, branch summary and skill messages under rebound, multi-key and unbound app.tools.expand; no difference; fixed-key mutations in each component are red.
  • Root cause: Pi's keyText answers "" for an action with no key bound; AppKeyText returned the fallback default whenever the installed resolver answered empty, so the tool card expand hint kept 'ctrl+o' after the user unbound app.tools.expand. The fallback now applies only when no resolver is installed, as its doc says. Reverting the fix fails them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 33fe10d58e (2026-10-11), 72e7ab2dbf (2026-10-11), c4ad739eb8 (2026-10-11), fac470319e (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/models_error_oracle_test.go::TestModelsErrorMessageMatchesPi, coding/extension/host/inproc/registries_test.go::TestShortcuts_DiagnosticMessageMatchesUpstream, coding/extension/host/inproc/shortcuts_oracle_test.go::TestRunnerShortcutsMatchPi, coding/mcpext/manager_view_oracle_test.go::TestMcpManagerViewMatchesPi and 12 more in docs/bug-ledger.json
  • Release note: TestSummaryAndSkillMessageComponentsMatchPi probes the compaction, branch summary and skill messages under rebound, multi-key and unbound app.tools.expand; no difference; fixed-key mutations in each component are red.

BF-731474b

coding.DefineTool returns the tool definition it is given, as Pi's defineTool does

  • Source: PiG's own defect
  • Symptom: coding.DefineTool returns the tool definition it is given, as Pi's defineTool does.
  • Root cause: Designed-out rows: 247 to 215.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 731474b0eb (2026-10-10)
  • Tests: coding/define_tool_test.go::TestDefineToolReturnsTheDefinitionUnchanged
  • Release note: coding.DefineTool returns the tool definition it is given, as Pi's defineTool does.

BF-731474b-2

codemode.CodemodeJsonSchema names the raw JSON of a tool's JSON Schema document, as Pi's…

  • Source: PiG's own defect
  • Symptom: codemode.CodemodeJsonSchema names the raw JSON of a tool's JSON Schema document, as Pi's CodemodeJsonSchema does.
  • Root cause: Designed-out rows: 247 to 215.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 731474b0eb (2026-10-10)
  • Tests: coding/define_tool_test.go::TestDefineToolReturnsTheDefinitionUnchanged
  • Release note: codemode.CodemodeJsonSchema names the raw JSON of a tool's JSON Schema document, as Pi's CodemodeJsonSchema does.

BF-7375fe4

After pig piglet update installs a new release of a pulled Piglet Binary, pig piglet list shows the…

  • Source: PiG's own defect
  • Symptom: Each release of a Piglet without a source was listed as its own Piglet and show printed only the first.
  • Root cause: After pig piglet update installed a new release of a pulled Piglet Binary, each installed release became its own binary-only Piglet: pig piglet list printed the name twice and pig piglet show <name> printed only the first release, not the one the update installed. Records without a matching source now join the one binary-only Piglet of their name, and the list names a target that several releases share once.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 265b6a9022 (2026-10-07), 7375fe4edc (2026-10-07)
  • Tests: coding/piglet/prune_test.go::TestPigletPruneRemovesOldBuildsOnly, coding/piglet/resolve_test.go::TestListGroupsBinaryOnlyRecordsOfOnePiglet, coding/piglet/signature/verifycache_test.go::TestCheckCached_ChangedIdentityOrPolicyRehashes, coding/piglet/signature/verifycache_test.go::TestCheckCached_EntryForAnotherPathIsNotTrusted, coding/piglet/signature/verifycache_test.go::TestCheckCached_FailedVerificationIsNeverRemembered, coding/piglet/signature/verifycache_test.go::TestCheckCached_FileChangedDuringTheHashIsNotRemembered, coding/piglet/signature/verifycache_test.go::TestCheckCached_KeepsOneEntryPerPathAndEvictsTheOldest, coding/piglet/signature/verifycache_test.go::TestCheckCached_RememberFailureKeepsTheResult and 7 more in docs/bug-ledger.json
  • Release note: After pig piglet update installs a new release of a pulled Piglet Binary, pig piglet list shows the Piglet once and pig piglet show <name> lists every installed release, including the one the update installed. Before, each release of a Piglet without a source was listed as its own Piglet and show printed only the first.

BF-73a09eb

fix: the ANSI renderer that replaces a frontend surface draws on the options terminal, not the surface's…

  • Source: PiG's own defect
  • Symptom: fix: the ANSI renderer that replaces a frontend surface draws on the options terminal, not the surface's terminal
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 73a09ebde9 (2026-10-09)
  • Tests: internal/codingagent/interactive_frontend_program_status_test.go::TestProcessTerminalShowsTheProgramStatusOnceNoSessionDraws

BF-73a6c8c

OpenAI-compatible chat completions requests keep the tool-call ids of the same model's earlier calls as they…

  • Source: PiG's own defect
  • Symptom: Every id in the request was rewritten, including the model's own.
  • Root cause: openai-completions.ts convertMessages gives normalizeToolCallId to transformMessages, which applies it to a tool call of another model (transform-messages.ts:136-139) and maps its result to the new id. PiG instead rewrote every id in the converted request messages, so a same-model call with a '|' id or, on provider openai, an id over 40 characters was changed where Pi sends it as is. convertTranscript now passes the normalizer to transformMessages and the post-pass is gone.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 73a6c8c24a (2026-10-10)
  • Tests: ai/openai_completions_tool_call_id_test.go::TestCompletionsNormalizesOnlyTheToolCallIDsOfOtherModels, ai/openai_completions_tool_call_id_test.go::TestCompletionsTruncatesAForeignPlainToolCallIDOnlyOnTheOpenAIProvider, ai/openai_test.go::TestConvertMessagesNormalizesToolCallIDsConsistently
  • Release note: OpenAI-compatible chat completions requests keep the tool-call ids of the same model's earlier calls as they were sent. As in Pi, only a tool call of another model is normalized: a |-separated id is shortened, and on the openai provider an id over 40 characters is truncated. Before, every id in the request was rewritten, including the model's own.

BF-73ef1ea

Reduced context-limit request failures by estimating input at 3.5 characters per token instead of 4 when…

  • Source: PiG's own defect
  • Symptom: Reduced context-limit request failures by estimating input at 3.5 characters per token instead of 4 when calculating output limits.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 73ef1ea5d7 (2026-10-08)
  • Tests: ai/estimate_content_test.go::TestEstimateTextAndImageContentTokensLikeUpstream, coding/session_tool_modifiers_port_test.go::TestInvalidToolModifierOptionsPort, coding/session_tool_modifiers_port_test.go::TestToolModifierOptionsChangeTheDefaultSelectionPort, coding/session_tool_modifiers_port_test.go::TestToolModifierOptionsSurviveReloadPort, internal/codingagent/tool_modifiers_test.go::TestApplyToolModifiersMatchesUpstream, internal/codingagent/tool_modifiers_test.go::TestGetToolListErrorMatchesUpstream
  • Release note: Reduced context-limit request failures by estimating input at 3.5 characters per token instead of 4 when calculating output limits.

BF-7472171

An extension dialog (select, confirm, input, editor) that opens while another is shown now replaces…

  • Source: PiG's own defect
  • Symptom: Previously the second dialog failed with "another extension dialog is already open" or waited for the first to close.
  • Root cause: The UI bridge held the terminal focus token for the whole of a ui.select/confirm/input/editor call, so a second dialog from a subprocess extension waited until the first closed and the interactive displacement () never ran. A dialog now waits only while a focused custom component holds the terminal and holds no focus itself.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 74721716a1 (2026-10-13), ef0d4cb621 (2026-10-13)
  • Tests: coding/extension/host/subprocess/ui_bridge_test.go::TestInteractiveDialogsDoNotWaitForEachOther, test/parity/scenarios/extensions-runtime/83-extension-dialog-displacement.toml
  • Release note: An extension dialog (select, confirm, input, editor) that opens while another is shown now replaces it, as in Pi. This includes dialogs from subprocess extensions. The replaced dialog stays pending until its signal aborts it. Previously the second dialog failed with "another extension dialog is already open" or waited for the first to close.

BF-7489735

pig -e <directory> now loads a Node extension whose entry file is index.mjs and that has no…

  • Source: PiG's own defect
  • Symptom: pig -e <directory> now loads a Node extension whose entry file is index.mjs and that has no package.json, index.ts or index.js, as Pi does.
  • Root cause: A directory with no package manifest and no index.ts or index.js is itself the extension path in Pi (package-manager.ts:1379-1384), and the loader's jiti import of it loads index.mjs. pig failed with "cannot resolve node extension entrypoint". resolveNodeEntrypoint and hasNodeSource now fall back to extsource.NodeDirectoryImport. Regression: TestResolveNodeEntrypointImportsAnIndexMjsDirectoryAsJiti (fails with the fallback removed).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0f6fdc2093 (2026-10-07), 7489735c3f (2026-10-09)
  • Tests: coding/cli/package_update_workers_test.go::TestPackageUpdateChecksBoundWorkers, coding/extension/host/subprocess/builder_node_entry_test.go::TestResolveNodeEntrypointImportsAnIndexMjsDirectoryAsJiti
  • Release note: pig -e <directory> now loads a Node extension whose entry file is index.mjs and that has no package.json, index.ts or index.js, as Pi does.

BF-74bc69b

fix(rpc): every Session replacement aborts the compactions of its batch, and an abort before the request…

  • Source: PiG's own defect
  • Symptom: fix(rpc): every Session replacement aborts the compactions of its batch, and an abort before the request answers with the abort error
  • Root cause:
    • the replacement aborts the batch's compact commands when it is dispatched and when it starts; a compact does not start before the batch drained; the abort reaches a compaction before it claims the Session (coding.WithCompactionAbort) - an abort before the summary request rejects it with the abort error itself, as pi-ai does (compaction.ErrRequestAborted); an abort during the request still answers Compaction cancelled - Pi's TypeError for the first of two aborted compacts and its answer order are recorded in D148 as defects not reproduced
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 74bc69bea4 (2026-10-10)
  • Tests: coding/cli/rpc_replacement_order_test.go::TestRPCReplacementAbortsTheCompactionOfItsBatch, coding/cli/rpc_replacement_order_test.go::TestRPCReplacementKeepsTheNothingToCompactAnswer, coding/session_test.go::TestCompactCancelsSynchronouslyFromStartEvent

BF-74e801f

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: Both shapes and the test generator now follow the two encoder structs; TestMemberScanAcceptsMessagesPiWrites gains openai-completions' responseId, responseModel, rawStopReason, errorMessage line (openai-completions.ts:564-586).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 74e801f982 (2026-10-10)
  • Tests: agent/message_shape_test.go::TestMemberScanAcceptsMessagesPiWrites, agent/message_shape_test.go::TestMemberScanAcceptsOnlyTextTheFieldsEncodeTo

BF-74f806c

fix(agent): end the tool flow at custom user messages after /tree navigation (#203)

  • Source: PiG's own defect
  • Symptom: fix(agent): end the tool flow at custom user messages after /tree navigation (#203)
  • Root cause: Custom messages that become user messages (branch and compaction summaries) now end an open tool flow, and system messages that arrive while tool calls are pending are held until after the results, as Pi does. Fixes an Anthropic HTTP 400 for an orphaned tool_result after /tree navigation.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 74f806c799 (2026-10-10, #203)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, coding/session_tree_wire_oracle_test.go::TestNavigateTreeEveryPointSendsValidToolFlow

BF-74fb98c

Markdown list items lex their text line by line like Pi: an HTML block starts at any item line, and an item…

  • Source: PiG's own defect
  • Symptom: Markdown list items lex their text line by line like Pi: an HTML block starts at any item line, and an item line indented four or more spaces past the content joins the text before it (so a heading, quote, rule, fence, table or bullet there stays text) and leaves an empty row before the text that follows.
  • Root cause: Pi lexes a list item's text with top false: no paragraph rule, so an HTML block (including a custom tag, comment or pre) starts at any item line indented up to three spaces, and a line indented four or more past the item's content is indented code that blockTokens joins to the text before it with four spaces removed, plus a newline that leaves an empty row before the text that follows. Pig kept the indentation of an html line, and re-lexed a joined line as a heading, quote, rule, fence, table, bullet or setext underline.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 74fb98c362 (2026-10-11)
  • Tests: tui/markdown_list_item_lines_oracle_test.go::TestMarkdownListItemContinuationLinesMatchPi
  • Release note: Markdown list items lex their text line by line like Pi: an HTML block starts at any item line, and an item line indented four or more spaces past the content joins the text before it (so a heading, quote, rule, fence, table or bullet there stays text) and leaves an empty row before the text that follows.

BF-75426aa

The /login and /logout provider picker and the /tree selector show the terminal cursor in their search…

  • Source: PiG's own defect
  • Symptom: The /login and /logout provider picker and the /tree selector show the terminal cursor in their search and label inputs only while they hold focus, as Pi does.
  • Root cause: OAuthSelectorComponent.SetFocused propagates to the search input and TreeSelectorComponent.SetFocused to the active label input, which inherits the flag when it opens (oauth-selector.ts:63, tree-selector.ts:1349,1410). The search input no longer claims focus at construction. The editor-slot runners for /login and /tree now focus the selector and restore the previous focus, as showSelector does; the cursor marker follows focus. SetFocused takes the container lock so it does not race a render.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ed05099398 (2026-10-06), 75426aa52d (2026-10-07)
  • Tests: ai/pi_messages_event_type_test.go::TestPiMessagesEventTypeConstantsMatchTheWireLiterals, coding/extension/session_boundary_draft_type_test.go::TestSessionBoundaryDraftTypeSelectsTheUpstreamMember, internal/codingagent/interactive_resume_thinking_test.go::TestInteractiveModeSetHiddenThinkingLabelReachesExistingAndLaterBlocks, internal/codingagent/reload_trust_models_test.go::TestInitNoticesFollowUpstreamOrder, internal/codingagent/selector_focus_test.go::TestEditorSlotOAuthSelectorTakesAndReturnsFocus, internal/codingagent/selector_focus_test.go::TestEditorSlotTreeSelectorTakesAndReturnsFocus, tui/assistant_message_block_label_test.go::TestAssistantMessageComponentSetHiddenThinkingLabel, tui/assistant_message_block_test.go::TestAssistantMessageBlock_SetHideThinkingBlock and 10 more in docs/bug-ledger.json
  • Release note: The /login and /logout provider picker and the /tree selector show the terminal cursor in their search and label inputs only while they hold focus, as Pi does. The picker's search input claimed focus even when it had none.

BF-75426aa-2

A Node extension's tool_call handler can edit event.input in place and the tool runs with the edit, as in…

  • Source: PiG's own defect
  • Symptom: A Node extension's tool_call handler can edit event.input in place and the tool runs with the edit, as in Pi: the host rewrites the call's input in place, and assigning a new object to event.input is ignored, as in Pi.
  • Root cause: OAuthSelectorComponent.SetFocused propagates to the search input and TreeSelectorComponent.SetFocused to the active label input, which inherits the flag when it opens (oauth-selector.ts:63, tree-selector.ts:1349,1410). The search input no longer claims focus at construction. The editor-slot runners for /login and /tree now focus the selector and restore the previous focus, as showSelector does; the cursor marker follows focus. SetFocused takes the container lock so it does not race a render.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ed05099398 (2026-10-06), 75426aa52d (2026-10-07)
  • Tests: ai/pi_messages_event_type_test.go::TestPiMessagesEventTypeConstantsMatchTheWireLiterals, coding/extension/session_boundary_draft_type_test.go::TestSessionBoundaryDraftTypeSelectsTheUpstreamMember, internal/codingagent/interactive_resume_thinking_test.go::TestInteractiveModeSetHiddenThinkingLabelReachesExistingAndLaterBlocks, internal/codingagent/reload_trust_models_test.go::TestInitNoticesFollowUpstreamOrder, internal/codingagent/selector_focus_test.go::TestEditorSlotOAuthSelectorTakesAndReturnsFocus, internal/codingagent/selector_focus_test.go::TestEditorSlotTreeSelectorTakesAndReturnsFocus, tui/assistant_message_block_label_test.go::TestAssistantMessageComponentSetHiddenThinkingLabel, tui/assistant_message_block_test.go::TestAssistantMessageBlock_SetHideThinkingBlock and 10 more in docs/bug-ledger.json
  • Release note: A Node extension's tool_call handler can edit event.input in place and the tool runs with the edit, as in Pi: the host rewrites the call's input in place, and assigning a new object to event.input is ignored, as in Pi. The Go, Python and Rust SDKs follow the same rule, and a tool_call reply in an unexpected shape now blocks the call (#165).

BF-756cac8

A streaming run no longer hangs, rarely and mostly under load, when a response turn resumes just as the drain…

  • Source: PiG's own defect
  • Symptom: A streaming run no longer hangs, rarely and mostly under load, when a response turn resumes just as the drain that granted it ends: a pausing coroutine put back a drain flag the ending drain had already cleared, which left every goroutine asleep with a reaction still queued.
  • Root cause: settle saved the executor's draining flag at entry and wrote it back when the body went on to pause. For a body that a serving goroutine claimed (needsDriver) the flag belongs to the drain that granted it, live on another goroutine and about to end; when that end landed in settle's unlocked reaction window, the write-back set draining again with no loop on any stack. serve and startDrainLocked then refused to drain, and the queued reactions never ran (a lost wakeup seen as a hang of TestGoogleVertexProviderStreamMatchesPi under -race).
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 756cac8180 (2026-10-14), a07977e2b9 (2026-10-14)
  • Tests: ai/continuation_settle_drain_test.go::TestContinuationSettleDoesNotResurrectEndedDrain, ai/continuation_settle_drain_test.go::TestContinuationSettleDoesNotRunSecondLoopForTurnGrantedFromSettle, ai/google_vertex_stream_oracle_test.go::TestGoogleVertexProviderStreamMatchesPi
  • Release note: A streaming run no longer hangs, rarely and mostly under load, when a response turn resumes just as the drain that granted it ends: a pausing coroutine put back a drain flag the ending drain had already cleared, which left every goroutine asleep with a reaction still queued.

BF-75c6ccb

fix(codingagent): context edits leave system messages unchanged

  • Source: PiG's own defect
  • Symptom: fix(codingagent): context edits leave system messages unchanged
  • Root cause: 400 random entry trees against Pi's buildSessionContext (TestBuildSessionContextMatchesPi): projectContextEntry maps only user/assistant/toolResult/custom messages; replaceContextContent also rewrote a system message.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 75c6ccb80e (2026-10-10)
  • Tests: internal/codingagent/session_context_oracle_test.go::TestBuildSessionContextMatchesPi

BF-7633906

On arm64, tool_search and searchTools() scores use the same natural logarithm as Pi running on Node for…

  • Source: PiG's own defect
  • Symptom: The logarithm in the BM25 weights differed from Node's in the last bit for about 1 in 500 arguments, which could change scores and break ties in a different order.
  • Root cause: Math.log is V8's fdlibm port in C++, not JavaScript arithmetic. Node's arm64 builds compile it with clang, whose default -ffp-contract=on fuses a product into the addition or subtraction of the same C expression (never across statements). Node 24.19 on darwin-arm64 matches that variant bit for bit over TestLogMatchesNode's 360022 inputs.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 7633906d03 (2026-10-13)
  • Tests: internal/jsmath/log_oracle_test.go::TestLogMatchesNode
  • Release note: On arm64, tool_search and searchTools() scores use the same natural logarithm as Pi running on Node for arm64, whose V8 math library fuses each multiply-add in one step. Before, the logarithm in the BM25 weights differed from Node's in the last bit for about 1 in 500 arguments, which could change scores and break ties in a different order.

BF-7662334

An extension that writes a large amount to stderr no longer grows its diagnostic log without limit

  • Source: PiG's own defect
  • Symptom: An extension that writes a large amount to stderr no longer grows its diagnostic log without limit.
  • Root cause: Host wire (go-vs-rust-harness #10): an outbound frame is encoded into a pooled buffer holding the length prefix and written with one socket write; Send allocates nothing in steady state (was 1 object, 20 us -> 8.6 us). Every extension process's stderr goes through a log that keeps the first 1 MiB and the last 64 KiB and counts the bytes it dropped, so a flood neither blocks the pipe nor fills the disk.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 766233435b (2026-10-09)
  • Tests: coding/extension/host/subprocess/conn_framing_test.go::TestConnAllocationBudgets, coding/extension/host/subprocess/conn_framing_test.go::TestLargeFrameChunksArriveIntact, coding/extension/host/subprocess/conn_framing_test.go::TestSendAllocatesNothingInSteadyState, coding/extension/host/subprocess/conn_framing_test.go::TestSmallFrameIsOneSocketWrite, coding/extension/host/subprocess/stderr_bound_test.go::TestFloodingChildNeitherBlocksNorFillsTheDisk, coding/extension/host/subprocess/stderr_bound_test.go::TestHelperFloodStderr, coding/extension/host/subprocess/stderr_bound_test.go::TestStderrLogKeepsHeadAndTail, coding/extension/host/subprocess/stderr_bound_test.go::TestStderrLogUnderTheBoundIsVerbatim and 4 more in docs/bug-ledger.json
  • Release note: An extension that writes a large amount to stderr no longer grows its diagnostic log without limit. The log keeps the first 1 MiB and the last 64 KiB of the output and says how many bytes it dropped, so a startup failure and a crash line both survive a flood.

BF-773a868

RPC mode no longer hangs when a Node extension command calls pi.sendMessage(..., { triggerTurn: true })…

  • Source: PiG's own defect
  • Symptom: RPC mode no longer hangs when a Node extension command calls pi.sendMessage(..., { triggerTurn: true }) while a before_agent_start handler is subscribed: the handler runs inside the call, as in Pi, and the run starts after the command's response.
  • Root cause: TestRPCCommandTurnStartOrderMatchesPi/async-followup hung (agent_settled timed out). An earlier change made a triggering pi.sendMessage a synchronous bridge call, so before_agent_start starts nested in that call's wait; its synchronous prefix ran (setStatus was written), but the handler's suspension was never reported, because the rest of it waits on the call's own answer. The host waited for the suspension, the runtime for the answer: a deadlock. A request started nested in a synchronous call now reports "blocked" (host_call) once its synchronous prefix returns, unless its handler reported the suspension already (session_info_changed, thinking_level_select).
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 773a868c84 (2026-10-13)
  • Tests: coding/cli/rpc_command_send_order_test.go::TestRPCCommandTurnStartOrderMatchesPi, coding/cli/rpc_ui_integration_test.go::TestRPCModeExtensionUIRoundTrip, coding/cli/session_wire_lifecycle_test.go::TestModeWireLifecycle, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml and 2 more in docs/bug-ledger.json
  • Release note: RPC mode no longer hangs when a Node extension command calls pi.sendMessage(..., { triggerTurn: true }) while a before_agent_start handler is subscribed: the handler runs inside the call, as in Pi, and the run starts after the command's response.

BF-773a868-2

RPC mode no longer hangs when a Node extension command calls pi.appendEntry and then pi.setSessionName…

  • Source: PiG's own defect
  • Symptom: RPC mode no longer hangs when a Node extension command calls pi.appendEntry and then pi.setSessionName while a session_info_changed handler is subscribed.
  • Root cause: TestRPCCommandTurnStartOrderMatchesPi/async-followup hung (agent_settled timed out). An earlier change made a triggering pi.sendMessage a synchronous bridge call, so before_agent_start starts nested in that call's wait; its synchronous prefix ran (setStatus was written), but the handler's suspension was never reported, because the rest of it waits on the call's own answer. The host waited for the suspension, the runtime for the answer: a deadlock. A request started nested in a synchronous call now reports "blocked" (host_call) once its synchronous prefix returns, unless its handler reported the suspension already (session_info_changed, thinking_level_select).
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 773a868c84 (2026-10-13)
  • Tests: coding/cli/rpc_command_send_order_test.go::TestRPCCommandTurnStartOrderMatchesPi, coding/cli/rpc_ui_integration_test.go::TestRPCModeExtensionUIRoundTrip, coding/cli/session_wire_lifecycle_test.go::TestModeWireLifecycle
  • Release note: RPC mode no longer hangs when a Node extension command calls pi.appendEntry and then pi.setSessionName while a session_info_changed handler is subscribed.

BF-7768bea

fix(ai): tool-call arguments are written as JSON.stringify writes them, and keep a lone surrogate

  • Source: PiG's own defect
  • Symptom: fix(ai): tool-call arguments are written as JSON.stringify writes them, and keep a lone surrogate
  • Root cause: ToolCall.ArgumentsJSON, which every provider request (OpenAI-compatible, Anthropic, Mistral, Google), hook and the tree use for the arguments text, fell back to encoding/json when no member order was recorded, that is for every call whose objects were already in sorted order: it escaped <, >, &, U+2028 and U+2029, sorted integer-like keys as text, and turned a lone surrogate into three U+FFFD. It now walks the arguments the way the ordered path does. Decoding the arguments (SetArgumentsJSON, a streamed call, ParseJSONWithRepair, the clone an assistant message takes when it is observed) used encoding/json, so a model's lone surrogate escape became U+FFFD while the rest of the wire kept it…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 7768bea9d9 (2026-10-10)
  • Tests: agent/lone_surrogate_message_test.go::TestAgentMessageRoundTripKeepsALoneSurrogate, ai/streaming_json_oracle_test.go::TestStreamingJSONMatchesPiOnSeededInput, ai/streaming_json_resume_oracle_test.go::TestStreamingArgumentsParserMatchesPiAtEveryChunk, ai/tool_call_arguments_test.go::TestArgumentsJSONIsJSONStringifyWithoutARecordedOrder, ai/tool_call_arguments_test.go::TestIsWTF8, ai/tool_call_arguments_test.go::TestOpenAIRequestReplaysArgumentsAsJSONStringify, ai/tool_call_arguments_test.go::TestStreamedArgumentsKeepALoneSurrogate, internal/codingagent/compaction/estimate_oracle_test.go::TestEstimateTokensMatchesPiOracle

BF-779318a

OpenAI-compatible streams where one chunk carries reasoning and content (LiteLLM and similar proxies) now…

  • Source: PiG's own defect
  • Symptom: OpenAI-compatible streams where one chunk carries reasoning and content (LiteLLM and similar proxies) now place the thinking block before the text.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 779318a6ea (2026-10-14, #10926)
  • Tests: ai/openai_completions_member_type_oracle_test.go::TestOpenAICompletionsMistypedMembersMatchPi, ai/openai_reasoning_pending_test.go::TestParseSSE_ReasoningDetailsApplyBeforeNonIterableToolCalls, ai/openai_reasoning_pending_test.go::TestParseSSE_ThinkingBeforeTextWhenOneDeltaCarriesBoth, ai/openai_tool_choice_stream_upstream_test.go::TestOpenAICompletionsToolChoiceStreamUpstream
  • Release note: OpenAI-compatible streams where one chunk carries reasoning and content (LiteLLM and similar proxies) now place the thinking block before the text. Carried forward from Pi 63035734 (earendil-works/pi#10926), past the 1.2.0+3daa2028 pin.

BF-78c4c6c

pig piglet add now names the offending bundled path and where it came from (for example `Package herdr…

  • Source: PiG's own defect
  • Symptom: pig piglet add now names the offending bundled path and where it came from (for example Package herdr origin "local:../../components/herdr") and says it leaves the added subdirectory, instead of a bare "must be a portable relative path" error.
  • Root cause: Adding a Piglet from a monorepo subdirectory whose Packages point outside it failed with 'Piglet closure path must be a portable relative path'. The error now names the path, the origin it came from, and why it is rejected.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 78c4c6c647 (2026-10-13)
  • Tests: coding/piglet/monorepo_closure_test.go::TestBundledPigletClosureErrorNamesPathAndOrigin
  • Release note: pig piglet add now names the offending bundled path and where it came from (for example Package herdr origin "local:../../components/herdr") and says it leaves the added subdirectory, instead of a bare "must be a portable relative path" error.

BF-78f2d55

pig mcp login --help described --timeout as only the wait for the browser; it limits the whole sign-in…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: pig mcp login --help described --timeout as only the wait for the browser; it limits the whole sign-in, as Pi 148622bc now says.
  • Root cause: Ports Pi packages/coding-agent/src/extensions/mcp/cli.ts HELP: the --timeout line reads "Time limit for the whole sign-in (default: 300)". The Go docs (docs/site/docs/mcp.md, internal/pigdocs/content/mcp.md) already describe --timeout as limiting the whole sign-in.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 78f2d55ded (2026-10-12)
  • Tests: coding/cli/mcp_command_test.go::TestPigMcpHelpIsPisHelpWithPiGsIdentity, coding/mcpext/cli_oracle_test.go::TestMcpCommandMatchesPi, coding/mcpext/cli_test.go::TestMcpCommandHelpIsPisHelpWithPiGsIdentity
  • Release note: pig mcp login --help described --timeout as only the wait for the browser; it limits the whole sign-in, as Pi 148622bc now says.

BF-79eadcf

Long sessions no longer slow down each streamed frame in the main screen: a frame redraws from the first line…

  • Source: PiG's own defect
  • Symptom: Long sessions no longer slow down each streamed frame in the main screen: a frame redraws from the first line that changed instead of re-rendering and comparing the whole transcript, with the same terminal output.
  • Root cause: The main screen asks the root for the lines kept since the revision it drew last. Line resets and the differential scan start at the first line that can differ: the earlier of the kept prefix and both frames' cursor rows. The per-line reuse of earlier reset results stays, but only while the previous input array is intact: it was composed with overlays, or the root has rendered at most once since. Overlay frames keep nothing, and a frame after RestoreRenderState scans everything. Mouse dispatch reads child heights from the window.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 79eadcf623 (2026-10-10)
  • Tests: tui/complexity_scaling_norace_test.go::TestScalingMainScreenFrames, tui/container_render_cache_test.go::TestMainScreenCursorExtractionDoesNotMutateBorrowedContainerLines, tui/windowed_render_test.go::TestWindowedFramesEqualWholeFrames
  • Release note: Long sessions no longer slow down each streamed frame in the main screen: a frame redraws from the first line that changed instead of re-rendering and comparing the whole transcript, with the same terminal output.

BF-79f9a9e

fix(agent): convert a live partial with one clone and keep events at an unchanged publication (TU-T7 rest)

  • Source: PiG's own defect
  • Symptom: fix(agent): convert a live partial with one clone and keep events at an unchanged publication (TU-T7 rest)
  • Root cause: shallowAssistantMessageFrom takes the fields of the shallow copy it builds from the capture instead of observing the capture a second time, so a message_update clones the partial once.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 79f9a9eeb8 (2026-10-13)
  • Tests: ai/assistant_published_clone_test.go::TestObservedStreamingToolCallEqualsFullClone

BF-7a4a8e8

fix duplicate jsstring import in tui/assistant_message_block.go

  • Source: PiG's own defect
  • Symptom: fix duplicate jsstring import in tui/assistant_message_block.go
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 7a4a8e8749 (2026-10-11)
  • Tests: None recorded.

BF-7b0f5d7

A native extension provider's filterAllModels receives the models its getAllModels (or getModels)…

  • Source: PiG's own defect
  • Symptom: A native extension provider's filterAllModels receives the models its getAllModels (or getModels) returned, with their members in the order the provider wrote them, instead of copies with members sorted by name.
  • Root cause: models.ts:731-734 getAllAvailable passes filterAllModels provider.getAllModels?.() ?? provider.getModels(), the provider's own objects. refreshNativeProvider rebuilt them from ProviderModelConfig through a map[string]json.RawMessage, so a subprocess extension's filterAllModels saw every model's members sorted by name. The list the provider returned now goes through as it is; a provider with neither getter still has its registered configs read as models, with provider after the config's members as { ...definition, provider } (provider-composer.ts:297).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 7b0f5d7da8 (2026-10-13)
  • Tests: internal/codingagent/native_provider_filter_order_test.go::TestNativeProviderFilterAllModelsSeesTheProvidersOwnModels
  • Release note: A native extension provider's filterAllModels receives the models its getAllModels (or getModels) returned, with their members in the order the provider wrote them, instead of copies with members sorted by name.

BF-7b59fe2

pig piglet build --format binary from a Pig source directory that is not a Git checkout (for example…

  • Source: PiG's own defect
  • Symptom: pig piglet build --format binary from a Pig source directory that is not a Git checkout (for example PIG_SOURCE_ROOT pointing at an extracted archive) now says the directory must be a Git checkout of PiG, names it, and suggests cloning PiG or unsetting PIG_SOURCE_ROOT, instead of failing with resolve Pig source revision: exit status 128.
  • Root cause: A Piglet Binary build from a plain copy of the PiG source failed with 'resolve Pig source revision: exit status 128'. The error now names the directory, says it must be a Git checkout and how to fix it; other git failures keep git's stderr line.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 375de4a98c (2026-10-12), 35006c8baf (2026-10-13), 7b59fe2605 (2026-10-13)
  • Tests: coding/cli/strip_providers_binary_test.go::TestStripBedrockConverseStreamBuildOmitsAWSSDK, coding/pigletbuild/source_fetch_test.go::TestSourceRevisionOutsideGitCheckoutNamesDirectory, internal/codingagent/program_status_reporter_oracle_test.go::TestProgramStatusReporterRandomSequencesMatchPi, third_party/awssdk/internal/endpoints/v2/lazy_test.go::TestLazyRegexpMatchesLikeRegexp
  • Release note: pig piglet build --format binary from a Pig source directory that is not a Git checkout (for example PIG_SOURCE_ROOT pointing at an extracted archive) now says the directory must be a Git checkout of PiG, names it, and suggests cloning PiG or unsetting PIG_SOURCE_ROOT, instead of failing with resolve Pig source revision: exit status 128. Other git failures now include git's error line.

BF-7b92429

find and grep trim like JavaScript: FindTool trimmed each fd output line with strings.TrimSpace where…

  • Source: PiG's own defect
  • Symptom: find and grep trim like JavaScript: FindTool trimmed each fd output line with strings.TrimSpace where find.ts:270 uses String.prototype.trim, so a file whose name ends with U+FEFF lost nothing in Pi but a name ending in U+0085 lost it in PiG (and the reverse).
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 7b92429a25 (2026-10-11), a2755058da (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, coding/discover_extensions_oracle_test.go::TestDiscoverExtensionPathsMatchPi, internal/codingagent/armin_test.go::TestArminFramesMatchPinnedPi, internal/codingagent/custom_message_components_oracle_test.go::TestCustomMessageAndEntryComponentsMatchPi, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries and 9 more in docs/bug-ledger.json
  • Release note: find and grep trim like JavaScript: FindTool trimmed each fd output line with strings.TrimSpace where find.ts:270 uses String.prototype.trim, so a file whose name ends with U+FEFF lost nothing in Pi but a name ending in U+0085 lost it in PiG (and the reverse). find's lines and stderr message and grep's stderr message and blank-line check now use jsstring.Trim (find.ts:252,270; grep.ts:220,252). New live-Pi oracle TestFindTrimsOutputLinesLikeJavaScript (internal/codingagent/tools/find_trim_names_oracle_test.go: names beginning or ending with U+FEFF, U+0085, U+00A0 or a space, and names that are only U+FEFF or U+0085); red before the fix (3 of 7 directories), green after; ./internal/codingagent/tools -race green. The stderr and ripgrep blank-line trims are not reachable by the oracle (they need fd or rg to fail or emit a blank JSON line) and are changed for the same rule only.

BF-7ba3211

A resumed session no longer holds its whole file in memory

  • Source: PiG's own defect
  • Symptom: A resumed session no longer holds its whole file in memory.
  • Root cause: The range is now 225-285 MB.
  • User impact: Behavior differed from Pi
  • Platform: macOS
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 7ba3211bdc (2026-10-11)
  • Tests: None recorded.
  • Release note: A resumed session no longer holds its whole file in memory. PiG keeps a small index of every entry and decodes only what the active context needs (the latest compaction's kept messages and everything after them); older entries are read back from the session file when a command such as /tree, /fork or export needs them. On a real 314 MB session, an interactive resume measured 150-190 MB after loading and 225-285 MB after opening /tree (two profiling runs on macOS), where PiG 0.4.1 took 1.2-1.3 GB and Pi 1.1.0 about 700 MB. History outside the active context is not kept decoded, so a long-open session file does not accumulate it in memory.

BF-7c59949

fix(codingagent): session selector keeps Pi's out-of-range selected index

  • Source: PiG's own defect
  • Symptom: fix(codingagent): session selector keeps Pi's out-of-range selected index
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 7c599493e4 (2026-10-10)
  • Tests: internal/codingagent/session_selector_oracle_test.go::TestSessionSelectorInputMatchesPi

BF-7cca5ee

A Node extension can default-import TypeBox (import Type from "typebox", and likewise typebox/value…

  • Source: PiG's own defect
  • Symptom: A Node extension can default-import TypeBox (import Type from "typebox", and likewise typebox/value, typebox/compile and @sinclair/typebox*), as it can in Pi.
  • Root cause:
    • runner.ts returns to partial: Pi renders before_agent_start's event.systemPrompt and ctx.getSystemPrompt() from the shared options on every read (runner.ts:1421-1438); Pig hands each handler the pre-edit string. Probed on the pinned Pi runner. - TestNodeRuntimeVirtualModulesMatchPiNamespaces read Pi's namespaces through Pig's loader hooks and compared Pig with itself; it now reads them in a hook-free process. - That comparison found the TypeBox shims had no default export; vendor-typebox.sh re-exports it, and TestTypeBoxDefaultImportsLoadInANodeExtension loads such an extension through the host.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 7cca5ee58a (2026-10-06)
  • Tests: coding/extension/host/subprocess/node_loader_test.go::TestNodeRuntimeVirtualModulesMatchPiNamespaces, coding/extension/host/subprocess/upstream_tool_schema_test.go::TestTypeBoxDefaultImportsLoadInANodeExtension
  • Release note: A Node extension can default-import TypeBox (import Type from "typebox", and likewise typebox/value, typebox/compile and @sinclair/typebox*), as it can in Pi. The import failed to link in an .mjs extension and returned the wrong object in a .ts extension.

BF-7d0621f

fix(extension): keep Pi's three-parameter EmitBeforeAgentStart (prompt, images, options); a custom run's…

  • Source: PiG's own defect
  • Symptom: fix(extension): keep Pi's three-parameter EmitBeforeAgentStart (prompt, images, options); a custom run's trigger travels on ctx
  • Root cause: Clears the S4 signature-mismatch gaps (ExtensionRunner.emitBeforeAgentStart). Ledger regenerated; interface-gaps: 0 gaps.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 7d0621f995 (2026-10-09)
  • Tests: None recorded.

BF-7dc9b09

-e <local Package> now contributes its prompts, skills and themes, loaded before the project, user and…

  • Source: PiG's own defect
  • Symptom: -e <local Package> now contributes its prompts, skills and themes, loaded before the project, user and Package resources, so they win same-name collisions as in Pi.
  • Root cause: A -e local Package contributed only extensions. Pi's resource loader resolves the -e sources as temporary Packages and merges their prompts, skills and themes before the project, user and Package resources (cliEnabledPrompts/Skills/Themes), so they win same-name collisions and survive --no-prompt-templates, --no-skills and --no-themes.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 7dc9b09ef7 (2026-10-09), da99854d1b (2026-10-09)
  • Tests: coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, test/parity/scenarios/extensions-runtime/76-cli-package-resources.toml
  • Release note: -e <local Package> now contributes its prompts, skills and themes, loaded before the project, user and Package resources, so they win same-name collisions as in Pi. They survive --no-prompt-templates, --no-skills and --no-themes. A directory with an extensions, skills, prompts or themes directory is a Package and no longer fails to load as an extension.

BF-7dc9b09-2

--prompt-template and --theme paths are now merged after the project, user and Package resources, as in…

  • Source: PiG's own defect
  • Symptom: --prompt-template and --theme paths are now merged after the project, user and Package resources, as in Pi, so a same-name resource there wins over them.
  • Root cause: A -e local Package contributed only extensions. Pi's resource loader resolves the -e sources as temporary Packages and merges their prompts, skills and themes before the project, user and Package resources (cliEnabledPrompts/Skills/Themes), so they win same-name collisions and survive --no-prompt-templates, --no-skills and --no-themes.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 7dc9b09ef7 (2026-10-09)
  • Tests: test/parity/scenarios/extensions-runtime/76-cli-package-resources.toml
  • Release note: --prompt-template and --theme paths are now merged after the project, user and Package resources, as in Pi, so a same-name resource there wins over them.

BF-7e09ec0

fix(codingagent): the idle collection runs one forced GC cycle (D172)

  • Source: PiG's own defect
  • Symptom: fix(codingagent): the idle collection runs one forced GC cycle (D172)
  • Root cause: debug.FreeOSMemory forces a collection itself, so the runtime.GC() before it was a second full cycle that returned nothing more.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 7e09ec03c8 (2026-10-13)
  • Tests: None recorded.

BF-7e1a05c

Resource resolution follows Pi's DefaultPackageManager.resolve in two cases: built-in extensions come after…

  • Source: PiG's own defect
  • Symptom: Resource resolution follows Pi's DefaultPackageManager.resolve in two cases: built-in extensions come after file and package extensions, and a Package listed twice in the same project settings uses its last entry (and its filters), keeping the first position.
  • Root cause: TestLoadMcpConfigMatchesPiOnRandomFiles loads 1500 random global and project mcp.json files through Pi's loadMcpConfig and PiG's LoadMcpConfig: 187 differed, among them every file with a numeric server name, because Object.entries lists array-index keys first and orderedjson.Object kept file order. Object.Set now inserts an array-index key at its ascending place before the other keys (Keys, MarshalJSON, Clone follow), with TestObjectEnumeratesArrayIndexKeysFirst. Dependent packages (codingagent, extension, mcpext, mcp, packagecontent) pass; the failures left in tools and the node-backed subprocess tests are the fd/rg and node-shim environment ones.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 12cd60837e (2026-10-09), 24232ef878 (2026-10-09), 2b3403045b (2026-10-09), 3def2a7bd8 (2026-10-10), 7e1a05ccfd (2026-10-10), 919575a79c (2026-10-10), f3b4045e8a (2026-10-10)
  • Tests: coding/mcpext/config_load_oracle_test.go::TestLoadMcpConfigMatchesPiOnRandomFiles, internal/codingagent/paths_oracle_test.go::TestPathsMatchPi, internal/codingagent/session_file_bytes_oracle_test.go::TestSessionFileBytesMatchPi, internal/codingagent/settings_file_bytes_oracle_test.go::TestSettingsFileBytesMatchPi, internal/codingagent/settings_load_oracle_test.go::TestSettingsLoadMatchesPiOnCorruptedFiles, internal/ignorerules/ignore_oracle_test.go::TestIgnoredMatchesPiIgnorePackage, internal/ignorerules/ignorerules_test.go::TestIgnoreRulesFollowTheIgnorePackage, internal/jsonparse/json_parse_oracle_test.go::TestValidateMatchesNodeJSONParse and 2 more in docs/bug-ledger.json
  • Release note: Resource resolution follows Pi's DefaultPackageManager.resolve in two cases: built-in extensions come after file and package extensions, and a Package listed twice in the same project settings uses its last entry (and its filters), keeping the first position.

BF-7ea21f3

RPC output writes summed usage (a tool result's usage with nested model calls, a compaction summary's usage)…

  • Source: PiG's own defect
  • Symptom: RPC output writes summed usage (a tool result's usage with nested model calls, a compaction summary's usage) with cacheWrite1h and reasoning before totalTokens, as Pi's combineUsage creates them, instead of after cost.
  • Root cause: Tests: - TestRPCCombinedUsageKeepsTheLiteralOrder. It also checks that a decoded Anthropic usage keeps the after-cost order. - TestRPCMessageUpdateStreamingVariantBytesMatchPi now decodes the same usage Pi's side holds instead of building a Go literal, which cannot say where the splits were created. - TestAssistantLateMembersFollowPiCreationOrder gains "anthropic cacheWrite1h from message_start only". The existing Anthropic case could not catch a missing splitsAfterCost at message_start, because its message_delta set the flag again. Removing the message_start assignment (anthropic.go) now fails this case. Pi creates cacheWrite1h there with || 0 (anthropic-messages.ts:684) on every stream.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), 2309604d80 (2026-10-10), 7ea21f3e44 (2026-10-10), c10cde302d (2026-10-10), eeac307095 (2026-10-10), f29e906cb3 (2026-10-10)
  • Tests: agent/assistant_member_order_oracle_test.go::TestAssistantLateMembersFollowPiCreationOrder, agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses and 11 more in docs/bug-ledger.json
  • Release note: RPC output writes summed usage (a tool result's usage with nested model calls, a compaction summary's usage) with cacheWrite1h and reasoning before totalTokens, as Pi's combineUsage creates them, instead of after cost.

BF-7f3a71c

fix(durable): the reference Harness wraps a plain Models once too

  • Source: PiG's own defect
  • Symptom: fix(durable): the reference Harness wraps a plain Models once too
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 7f3a71c014 (2026-10-21)
  • Tests: None recorded.

BF-7f441d7

MCP OAuth URLs are WHATWG URLs, as in Pi

  • Source: PiG's own defect
  • Symptom: MCP OAuth URLs are WHATWG URLs, as in Pi.
  • Root cause: Tests: - TestOAuthURLsMatchPi runs Pi's own packages/mcp/src/oauth source from the upstream mirror on 21 cases. On the old code 15 of the first 20 differed: fragments in authorize (6), token (3) and challenge (3) URLs, the opaque base (1) and the null origins (2). - TestDiscoveryRequestsTheURLWithoutItsFragment guards the new request path. It was not red before the change, since *url.URL also dropped the fragment.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 7f441d7364 (2026-10-10), 827b3cffa4 (2026-10-10), ced3a9c4d4 (2026-10-10)
  • Tests: coding/cli/rpc_model_type_binary_test.go::TestRPCModelObjectsCarryTheRegistryModelType, coding/cli/rpc_model_type_test.go::TestRPCBuiltinChatModelCarriesType, coding/cli/rpc_model_type_test.go::TestRPCModelProjectionCarriesModelType, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode, internal/testenv/spawn_test.go::TestCleanupStopsARunningChild, internal/testenv/spawn_test.go::TestOutputRefusesAStreamTheCallerSet, internal/testenv/spawn_test.go::TestStopEndsAChildThatIgnoresSIGTERM, internal/testenv/spawn_test.go::TestWaitDoesNotBlockOnStdoutHeldByADescendant and 5 more in docs/bug-ledger.json
  • Release note: MCP OAuth URLs are WHATWG URLs, as in Pi. An empty fragment (#) or a malformed fragment escape now stays in the authorization URL that is shown and opened, and in the insecure-endpoint error. The authorization parameters replace repeated names, and an opaque authorization server URL fails with Invalid URL. The protected-resource check compares origins, which are null for custom schemes.

BF-7fc4ef4

fix(coding): shut down the extension host of a DefaultResourceLoader an extension constructed when its last…

  • Source: PiG's own defect
  • Symptom: fix(coding): shut down the extension host of a DefaultResourceLoader an extension constructed when its last holder lets go
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 7fc4ef463d (2026-10-21)
  • Tests: None recorded.

BF-8001cc5

SettingsManager collection getters (getPackages, getExtensionPaths, getSkillPaths, getPromptTemplatePaths…

  • Source: PiG's own defect
  • Symptom: SettingsManager collection getters (getPackages, getExtensionPaths, getSkillPaths, getPromptTemplatePaths, getThemePaths, getAllModelThinkingLevels, getThinkingBudgets, getTerminalCapabilityOverrides, getWarnings, getHttpIdleTimeoutMs, getWebSocketConnectTimeoutMs, getExternalEditorCommand): new live-Pi oracle TestSettingsManagerCollectionGettersMatchPi (2,500 layered global/project file pairs; throws compared by message).
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), e8cc072521 (2026-10-08), 433d232815 (2026-10-09), 8001cc51af (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/streaming_json_resume_oracle_test.go::TestStreamingArgumentsParserMatchesPiAtEveryChunk and 6 more in docs/bug-ledger.json
  • Release note: SettingsManager collection getters (getPackages, getExtensionPaths, getSkillPaths, getPromptTemplatePaths, getThemePaths, getAllModelThinkingLevels, getThinkingBudgets, getTerminalCapabilityOverrides, getWarnings, getHttpIdleTimeoutMs, getWebSocketConnectTimeoutMs, getExternalEditorCommand): new live-Pi oracle TestSettingsManagerCollectionGettersMatchPi (2,500 layered global/project file pairs; throws compared by message). Two real fixes: (1) an authored "httpIdleTimeoutMs": null was dropped (Pi keeps it, overrides the other layer, and getHttpIdleTimeoutMs throws "Invalid httpIdleTimeoutMs setting: null"; websocketConnectTimeoutMs already did this); (2) a project "externalEditor": "" did not override a global editor (Pi's merge keeps the empty string, so the editor falls back to VISUAL, EDITOR, nano); both now survive load, merge and encode. Regression TestSettingsEncodeKeepsAnAuthoredNullHTTPIdleTimeoutAndEmptyExternalEditor. 11 mutations red. Not compared: wrong-typed array members, fractional numbers, a thrown value that is neither number nor string; getWarnings is compared as the consumer reads it (anthropicExtraUsage !== false).

BF-8001cc5-2

SettingsManager.getCompactionSettings (+ getCompactionReserveTokens, getCompactionKeepRecentTokens)…

  • Source: PiG's own defect
  • Symptom: SettingsManager.getCompactionSettings (+ getCompactionReserveTokens, getCompactionKeepRecentTokens), getShellPath, getSessionDir, getThemeSetting, getTheme, getModelThinkingLevel: new live-Pi oracle TestSettingsManagerResolvedGettersMatchPi (3,000 layered global/project file pairs; model override beats ordinary setting beats default; invalid authored token values and unsafe integers compared by thrown message; path settings through normalizePath with home expansion and file URLs; a theme containing "/" reads as unset).
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), e8cc072521 (2026-10-08), 433d232815 (2026-10-09), 8001cc51af (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/streaming_json_resume_oracle_test.go::TestStreamingArgumentsParserMatchesPiAtEveryChunk and 7 more in docs/bug-ledger.json
  • Release note: SettingsManager.getCompactionSettings (+ getCompactionReserveTokens, getCompactionKeepRecentTokens), getShellPath, getSessionDir, getThemeSetting, getTheme, getModelThinkingLevel: new live-Pi oracle TestSettingsManagerResolvedGettersMatchPi (3,000 layered global/project file pairs; model override beats ordinary setting beats default; invalid authored token values and unsafe integers compared by thrown message; path settings through normalizePath with home expansion and file URLs; a theme containing "/" reads as unset). No bug found. 9 mutations red (override ignored, either validation removed, override key, object message, theme slash rule, normalizePath bypass, model-level key). Not compared: wrong-typed values, empty strings.

BF-801cadd

fix(codingagent): session files decode invalid UTF-8 as Node does

  • Source: PiG's own defect
  • Symptom: fix(codingagent): session files decode invalid UTF-8 as Node does
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 801cadd5a9 (2026-10-10)
  • Tests: internal/codingagent/session_load_oracle_test.go::TestLoadEntriesFromFileMatchesPiOnCorruptedFiles

BF-8048c85

Tool-call header argument preview no longer splits a character: truncateArg (tui/tool_execution.go) cut…

  • Source: PiG's own defect
  • Symptom: Tool-call header argument preview no longer splits a character: truncateArg (tui/tool_execution.go) cut to max-1 bytes, so a non-ASCII argument longer than the budget ended in a half character that the JSON quoting turned into a visible U+FFFD before the ellipsis (text:"ééé…é�…"); it now backs up to the start of a character.
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 8048c851e3 (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain, test/longsession/session_oracle_test.go::TestSessionManagerMatchesPi, test/parity/scenarios/rpc/58-rpc-session-wrong-typed-members.toml and 1 more in docs/bug-ledger.json
  • Release note: Tool-call header argument preview no longer splits a character: truncateArg (tui/tool_execution.go) cut to max-1 bytes, so a non-ASCII argument longer than the budget ended in a half character that the JSON quoting turned into a visible U+FFFD before the ellipsis (text:"ééé…é�…"); it now backs up to the start of a character. Regression TestFormatToolArgsTruncatesAtACharacterBoundary (red before: é, 日 and 🙂 at four byte offsets; both mutations, no back-up and cutting at max, red); ./tui -race green (1,139 s). Found by scanning production code for byte len() comparisons against limits. HeaderForTool's fallback (FormatToolArgs: name path:"x") is PiG's own format for a tool with no builtin formatter and no definition, where Pi renders formatToolCallWithArgs; that mismatch is the open "tool card with no definition" ruling request in questions/lg-help-4.md.

BF-8070fcc

OpenAI Codex and Azure OpenAI requests parse their URL as fetch does again: an upper-case scheme, a space or…

  • Source: PiG's own defect
  • Symptom: OpenAI Codex and Azure OpenAI requests parse their URL as fetch does again: an upper-case scheme, a space or a dot segment in a configured base URL reaches the server as the serialized URL, and the fragment is not sent.
  • Root cause: An earlier change routed Codex and Azure through newFetchRequest, which hands its input to nodeurl.RequestURL as an already-serialized href. Codex's resolveCodexUrl text is not serialized, so a base URL like HTTP://host went out with scheme HTTPS-cased ('fetch failed'), a space went out raw, and an unparsable path sent its fragment. undici's Request parses the input first: newFetchRequest now does (TypeError 'Failed to parse URL from'), drops the fragment for the global fetch, and refuses credentials with fetch's TypeError. The openai SDK callers (completions, responses incl. Azure, openrouter-images) report any of these as 'Connection error.'; Codex, which calls fetch itself…
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 8070fcc38b (2026-10-10)
  • Tests: ai/fetch_request_url_test.go::TestCodexAndAzureRequestTargetIsParsedLikeFetch, ai/fetch_request_url_test.go::TestCodexRequestTargetDropsTheFragmentOfAnOpaquePath
  • Release note: OpenAI Codex and Azure OpenAI requests parse their URL as fetch does again: an upper-case scheme, a space or a dot segment in a configured base URL reaches the server as the serialized URL, and the fragment is not sent. Codex reports a base URL with credentials with fetch's own message.

BF-8085dc0

A legacy oauth.json or settings.json that starts with a byte order mark migrates to auth.json, as in Pi

  • Source: PiG's own defect
  • Symptom: A legacy oauth.json or settings.json that starts with a byte order mark migrates to auth.json, as in Pi.
  • Root cause: migrateAuthToAuthJson parses JSON.parse(stripBom(...)) for both files (migrations.ts:36,51). Pig parsed the raw bytes, so a BOM-prefixed file failed to parse and its credentials were not migrated. Regression: TestMigrateAuthToAuthJSONStripsTheBOM (red before).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2e003bf45b (2026-10-06), 410d7332a8 (2026-10-06), 8085dc062b (2026-10-06), 8d23cfcee3 (2026-10-06), fbd3640da9 (2026-10-06)
  • Tests: ai/auth_credential_payload_test.go::TestCredentialCustomJSONRetainsUTF16Payload, ai/auth_stringify_test.go::TestAuthStorageWritesJSONStringifyText, internal/codingagent/migrations_auth_order_test.go::TestMigrateAuthToAuthJSONStripsTheBOM, internal/codingagent/migrations_auth_order_test.go::TestMigrateAuthToAuthJSONWritesFilesInPiKeyOrder, internal/codingagent/settings_key_order_test.go::TestSaveSettingsPatchKeepsNestedKeyOrder, internal/codingagent/settings_key_order_test.go::TestSaveSettingsPatchKeepsTheFileKeyOrder, internal/codingagent/settings_key_order_test.go::TestSaveSettingsPatchReplacesInPlace, internal/codingagent/settings_key_order_test.go::TestSaveSettingsPatchWritesJSONStringifyText and 1 more in docs/bug-ledger.json
  • Release note: A legacy oauth.json or settings.json that starts with a byte order mark migrates to auth.json, as in Pi.

BF-80970da

The Go SDK page now says that leaving AgentDir empty in CreateAgentSessionServicesOptions makes a program…

  • Source: PiG's own defect
  • Symptom: The Go SDK page now says that leaving AgentDir empty in CreateAgentSessionServicesOptions makes a program read PIG_CODING_AGENT_DIR the way the pig command does.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 80970da292 (2026-10-11)
  • Tests: None recorded.
  • Release note: The Go SDK page now says that leaving AgentDir empty in CreateAgentSessionServicesOptions makes a program read PIG_CODING_AGENT_DIR the way the pig command does.

BF-80b912a

A failed install-receipt write during pig update or install, such as one rejected because the update CA…

  • Source: PiG's own defect
  • Symptom: A failed install-receipt write during pig update or install, such as one rejected because the update CA file is not owner-only, no longer keeps its staged .install-receipt-* temp file open.
  • Root cause: A failed install-receipt write during pig update or install, such as one rejected because the update CA file is not owner-only, no longer keeps its staged .install-receipt-* temp file open.
  • User impact: Credential or permission exposure
  • Platform: Windows
  • Fixed in: Unreleased
  • Fix commits: 80b912a3e2 (2026-10-24)
  • Tests: None recorded.
  • Release note: A failed install-receipt write during pig update or install, such as one rejected because the update CA file is not owner-only, no longer keeps its staged .install-receipt-* temp file open. The handle is closed before the temp file is removed, so no stray file is left on Windows.

BF-80bb522

Compacting a history longer than the model's context window no longer fails with the provider's "prompt is…

  • Source: PiG's own defect
  • Symptom: Compacting a history longer than the model's context window no longer fails with the provider's "prompt is too long" error: the history is summarized in window-sized chunks, each chunk updating the summary of the one before, a split turn's over-window prefix joins those chunks, and a chunk the provider still refuses is halved (Pi 1.2.0 sends the whole history in one request and fails; D167).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 80bb5229b5 (2026-10-12)
  • Tests: None recorded.
  • Release note: Compacting a history longer than the model's context window no longer fails with the provider's "prompt is too long" error: the history is summarized in window-sized chunks, each chunk updating the summary of the one before, a split turn's over-window prefix joins those chunks, and a chunk the provider still refuses is halved (Pi 1.2.0 sends the whole history in one request and fails; D167).

BF-80bb522-2

Gemini 2.x models (Google Generative AI and Vertex) no longer reject a conversation in which a parallel tool…

  • Source: A Pi defect that PiG fixed first (divergence D115)
  • Symptom: Gemini 2.x models (Google Generative AI and Vertex) no longer reject a conversation in which a parallel tool call other than the last returned an image.
  • Root cause: Gemini answers the function calls of one model turn only when the user turn directly after it holds a function response for every call ("Please ensure that the number of function response parts is equal to the number of function call parts of the function call turn", Google function-calling documentation; the same file's comment: "Cloud Code Assist API requires all function responses to be in a single user turn"). A parallel tool batch with an image in any result but the last gets HTTP 400 on every later turn of the session.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: none recorded
  • Removed when Pi fixes it: Pi keeps the function responses of one model turn in one user turn (Pi's routing test changes), at which point delete the deferral and the two rewrites.
  • Commit that wrote the release note (the fix commit is not identified): 82a06d586d (2026-10-09), 85152a825d (2026-10-10), 80bb5229b5 (2026-10-12)
  • Tests: ai/google_contract_0861_test.go::TestGeminiToolResultImageOrderingMatchesUpstream, ai/google_generative_ai_payload_oracle_test.go::TestGoogleGenerativeAIPayloadMatchesPi, ai/google_generative_ai_payload_oracle_test.go::TestGoogleVertexPayloadMatchesPi, ai/google_image_tool_results_d115_test.go::TestGoogleImageToolResultOfASingleCallMatchesPiD115, ai/google_image_tool_results_d115_test.go::TestGoogleImageToolResultsKeepFunctionResponsesInOneTurnD115, test/pipeline-diff/diff_test.go::TestAIPipelineMatchesPi, test/pipeline-diff/diff_test.go::TestPipelineMatchesPi
  • Release note: Gemini 2.x models (Google Generative AI and Vertex) no longer reject a conversation in which a parallel tool call other than the last returned an image. PiG keeps all function responses of one model turn in a single user turn and sends the image turns after them. Pi 1.2.0 splits that turn (D115).

BF-80bb522-3

Amazon Bedrock Converse requests merge adjacent messages that have the same role

  • Source: A Pi defect that PiG fixed first (divergence D116)
  • Symptom: Amazon Bedrock Converse requests merge adjacent messages that have the same role.
  • Root cause: the Converse API requires the roles of a conversation to alternate and rejects two consecutive messages of one role with a ValidationException (HTTP 400). The shape is common: aborting a turn while tools run and then sending a new prompt gives assistant(toolUse), user(toolResult...), user(prompt), and every later turn of the session then fails.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: none recorded
  • Removed when Pi fixes it: Pi merges or otherwise alternates adjacent same-role messages in its Converse converter.
  • Commit that wrote the release note (the fix commit is not identified): a00c6eda58 (2026-10-10), ee4d7a20b0 (2026-10-10), 80bb5229b5 (2026-10-12)
  • Tests: ai/bedrock_alternation_d116_test.go::TestBedrockMergesAdjacentSameRoleMessagesD116, ai/bedrock_convert_upstream_test.go::TestBedrockUpstreamConvertMessages, ai/bedrock_payload_oracle_test.go::TestBedrockConversePayloadMatchesPi, ai/bedrock_test.go::TestBedrockConvertMessages_SkipsEmptyAssistant
  • Release note: Amazon Bedrock Converse requests merge adjacent messages that have the same role. A prompt sent after an abort during tool execution, or after a dropped assistant turn, no longer makes every later turn fail with a role-alternation error. Pi 1.2.0 sends two consecutive user messages (D116).

BF-80c797d

Text that holds ill-formed UTF-8 is written with one U+FFFD for each ill-formed sequence, as Node decodes it…

  • Source: PiG's own defect
  • Symptom: Text that holds ill-formed UTF-8 is written with one U+FFFD for each ill-formed sequence, as Node decodes it, instead of one per byte (Durable core records and the new internal/jsparity writers).
  • Root cause: Lead ruling 2026-10-11T00:27Z: the caller's insertion order of requestMetadata reaches the wire (parity, no D-number). Pi passes options.requestMetadata to the AWS SDK, which writes the object in its own-key order (bedrock-converse-stream.ts; ConverseStreamRequest$).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 80c797da93 (2026-10-10), afa1dd5185 (2026-10-10)
  • Tests: ai/bedrock_request_body_oracle_test.go::TestBedrockRequestBodyMatchesPi, ai/bedrock_wire_metadata_order_test.go::TestBedrockStringMapEnumeratesLikeAJavaScriptObject, ai/google_request_body_oracle_test.go::TestGoogleRequestBodyMatchesPi, coding/extension/host/subprocess/native_stream_options_union_test.go::TestNativeProviderOptionsKeepRawValuesAndNumericPresence, coding/extension/host/subprocess/request_metadata_order_test.go::TestCompleteRequestKeepsRequestMetadataOrder, coding/extension/host/subprocess/request_metadata_order_test.go::TestModelStreamRequestKeepsRequestMetadataOrder, coding/extension/host/subprocess/request_metadata_order_test.go::TestProviderOptionsWireKeepsRequestMetadataOrder, internal/codingagent/native_thinking_options_test.go::TestSubprocessRequestMetadataKeepsKeyOrder and 4 more in docs/bug-ledger.json
  • Release note: Text that holds ill-formed UTF-8 is written with one U+FFFD for each ill-formed sequence, as Node decodes it, instead of one per byte (Durable core records and the new internal/jsparity writers).

BF-81e584b

anthropic-messages payload now matches Pi for four cases found by the new live-Pi oracle…

  • Source: PiG's own defect
  • Symptom: anthropic-messages payload now matches Pi for four cases found by the new live-Pi oracle TestAnthropicMessagesCompatAndOptionsPayloadMatchesPi (ai/anthropic.go, anthropic-messages.ts:1132, 949-990, 1154-1164; 1,500 probes over supportsMidConvoEffort/supportsMidConvoSystemMessages/supportsMidConvoToolChanges/allowedFallbackModels/forceAdaptiveThinking, interleavedThinking, thinkingBudgets, env cache retention, object tool choices, odd metadata, model maxTokens, thinkingLevelMap, and mid-conversation system messages with sections and tool changes): (1) the initial system message is the transcript's first message as given, so a mid-conversation system message after a dropped errored/aborted assistant turn is no longer taken for the prompt; (2) a managed-effort request without forceAdaptiveThinking takes the effort high (streamSimple derives it only for adaptive models); (3) an unmapped xhigh/max level is high (mapThinkingLevelToEffort has no such case); (4) a managed-effort simple request still gets the budget-adjusted max_tokens. openai-codex-responses now picks the instruction role of a mid-conversation system message from the selected model's reasoning flag instead of always developer; the bedrock, google and responses payload oracles now draw mid-conversation system messages too.
  • Root cause: Real fixes: anthropic.go buildParams initial system message from the untransformed transcript; managed effort 'high' unless forceAdaptiveThinking; unmapped xhigh/max -> high; managed simple max_tokens adjustment. openai_responses.go: codex instruction role from the selected model's reasoning flag.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 81e584bdba (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/anthropic_effort_strict_test.go::TestAnthropicManagedEffortMapping, ai/anthropic_messages_compat_options_oracle_test.go::TestAnthropicMessagesCompatAndOptionsPayloadMatchesPi, ai/bedrock_payload_oracle_test.go::TestBedrockConversePayloadMatchesPi, ai/openai_completions_compat_options_oracle_test.go::TestOpenAICompletionsCompatAndCacheOptionsPayloadMatchesPi and 4 more in docs/bug-ledger.json
  • Release note: anthropic-messages payload now matches Pi for four cases found by the new live-Pi oracle TestAnthropicMessagesCompatAndOptionsPayloadMatchesPi (ai/anthropic.go, anthropic-messages.ts:1132, 949-990, 1154-1164; 1,500 probes over supportsMidConvoEffort/supportsMidConvoSystemMessages/supportsMidConvoToolChanges/allowedFallbackModels/forceAdaptiveThinking, interleavedThinking, thinkingBudgets, env cache retention, object tool choices, odd metadata, model maxTokens, thinkingLevelMap, and mid-conversation system messages with sections and tool changes): (1) the initial system message is the transcript's first message as given, so a mid-conversation system message after a dropped errored/aborted assistant turn is no longer taken for the prompt; (2) a managed-effort request without forceAdaptiveThinking takes the effort high (streamSimple derives it only for adaptive models); (3) an unmapped xhigh/max level is high (mapThinkingLevelToEffort has no such case); (4) a managed-effort simple request still gets the budget-adjusted max_tokens. openai-codex-responses now picks the instruction role of a mid-conversation system message from the selected model's reasoning flag instead of always developer; the bedrock, google and responses payload oracles now draw mid-conversation system messages too. Mutation-checked: 10 mutants red (initial from transformed list, effort default, managed max_tokens, xhigh case, fallbacks model/beta, interleaved beta, mid-convo effort beta, native tool changes anchor).

BF-81e584b-2

Live-Pi oracle TestMistralMessagesPayloadMatchesPi (ai/mistral_messages_payload_oracle_test.go…

  • Source: PiG's own defect
  • Symptom: Live-Pi oracle TestMistralMessagesPayloadMatchesPi (ai/mistral_messages_payload_oracle_test.go, mistral-conversations.ts toChatMessages and options; 1,200 random transcripts with thinking, errored/aborted turns, foreign tool calls, tool results with images and mid-conversation system messages, thinking level maps, sampling options, tool choices): the earlier Mistral payload oracles drew one user message, so the message conversion was only checked by two hand-written bodies.
  • Root cause: Real fixes: anthropic.go buildParams initial system message from the untransformed transcript; managed effort 'high' unless forceAdaptiveThinking; unmapped xhigh/max -> high; managed simple max_tokens adjustment. openai_responses.go: codex instruction role from the selected model's reasoning flag.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 81e584bdba (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/anthropic_effort_strict_test.go::TestAnthropicManagedEffortMapping, ai/anthropic_messages_compat_options_oracle_test.go::TestAnthropicMessagesCompatAndOptionsPayloadMatchesPi, ai/bedrock_payload_oracle_test.go::TestBedrockConversePayloadMatchesPi, ai/mistral_messages_payload_oracle_test.go::TestMistralMessagesPayloadMatchesPi and 5 more in docs/bug-ledger.json
  • Release note: Live-Pi oracle TestMistralMessagesPayloadMatchesPi (ai/mistral_messages_payload_oracle_test.go, mistral-conversations.ts toChatMessages and options; 1,200 random transcripts with thinking, errored/aborted turns, foreign tool calls, tool results with images and mid-conversation system messages, thinking level maps, sampling options, tool choices): the earlier Mistral payload oracles drew one user message, so the message conversion was only checked by two hand-written bodies. No payload difference. Mutation-checked: dropping the initial-system slice, the tool-result error text and the image data-URL prefix go red; sanitizeSurrogates on the system prompt is not drawn (generated text has no lone surrogate).

BF-8241433

A Mistral stream chunk with a member of an unexpected type is read as Pi's JavaScript reads it instead of…

  • Source: PiG's own defect
  • Symptom: A Mistral stream chunk with a member of an unexpected type is read as Pi's JavaScript reads it instead of ending the turn with a JSON decode error: a finish reason that is not a string ends the turn with Provider stopped with: <its string form>, a content item's non-string text ends it with Pi's text.replace is not a function, a content item that is not an object is skipped, a tool_calls value that is not an array ends it with toolCalls is not iterable (a string with Pi's error for its first character), a usage that is not an object reads as zeros, a prompt count sent as a string counts as its number, and a cached-token count that is not a number reads as 0.
  • Root cause: processResponsesStream reads each member with its own expression (openai-responses-shared.ts:463-800). PiG ended the turn with "openai-responses: invalid SSE JSON" for a mistyped event member and silently skipped an output_item.added/.done whose item did not decode (the two AI-08 stream-parse-continue hits), where Pi: - appends String(delta) with += on every delta event, "null"/"undefined" for a null/absent delta; - joins a done message's content.map((c) => c.type === "output_text" ? c.text : c.refusal).join("") and a done reasoning item's summary/content texts with String() for each value and "" for null/undefined, and throws item.content?.map is not a function (or summary) for a…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5078f73100 (2026-10-11), 5932542c21 (2026-10-11), 71e9d13eb9 (2026-10-11), 82414338e7 (2026-10-11), 8eb1202528 (2026-10-11), b6afa7be1a (2026-10-11)
  • Tests: ai/anthropic_event_member_type_oracle_test.go::TestAnthropicEventMistypedMembersMatchPi, ai/google_chunk_member_type_oracle_test.go::TestGoogleChunkMistypedMembersMatchPi, ai/mistral_chunk_member_type_oracle_test.go::TestMistralChunkMistypedMembersMatchPi, ai/openai_responses_member_type_oracle_test.go::TestOpenAIResponsesMistypedMembersMatchPi
  • Release note: A Mistral stream chunk with a member of an unexpected type is read as Pi's JavaScript reads it instead of ending the turn with a JSON decode error: a finish reason that is not a string ends the turn with Provider stopped with: <its string form>, a content item's non-string text ends it with Pi's text.replace is not a function, a content item that is not an object is skipped, a tool_calls value that is not an array ends it with toolCalls is not iterable (a string with Pi's error for its first character), a usage that is not an object reads as zeros, a prompt count sent as a string counts as its number, and a cached-token count that is not a number reads as 0.

BF-82d1808

Subprocess extensions no longer slow down each streamed token in long sessions: the state sent before each…

  • Source: PiG's own defect
  • Symptom: Subprocess extensions no longer slow down each streamed token in long sessions: the state sent before each event reuses the context usage estimate and the session name until the session changes.
  • Root cause: The host pushes a state snapshot to a subprocess extension before every event it dispatches (subprocess host.go pushStateTo), and the snapshot carries getContextUsage and getSessionName. Both walked the whole log: ContextUsage projected the branch and getSessionName scanned every entry backwards, so each event, including one message_update per streamed delta, cost O(history): 2 MB and 4.3 ms per event at 500 exchanges. Pi computes context usage only when an extension calls ctx.getContextUsage() (agent-session.ts getContextUsage).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 10aebd5c05 (2026-10-09), 82d1808c64 (2026-10-10)
  • Tests: ai/complexity_scaling_norace_test.go::TestScalingProviderRequestPerTurn, ai/complexity_scaling_norace_test.go::TestScalingProviderStreamPerDelta, coding/cli/complexity_scaling_norace_test.go::TestScalingExtensionEventStatePush, coding/complexity_scaling_norace_test.go::TestScalingSessionTurn, coding/extension/host/subprocess/complexity_scaling_norace_test.go::TestScalingMessageUpdatePayload, coding/session_context_usage_reuse_test.go::TestContextUsageReusesTheEstimateUntilTheLogOrWindowChanges, internal/codingagent/compaction/complexity_scaling_norace_test.go::TestScalingCompactionAndContextEstimate, internal/codingagent/complexity_scaling_norace_test.go::TestScalingSessionLogOperations and 7 more in docs/bug-ledger.json
  • Release note: Subprocess extensions no longer slow down each streamed token in long sessions: the state sent before each event reuses the context usage estimate and the session name until the session changes.

BF-83d5488

pig install <dir> of a local directory with no package manifest and no extensions/, skills/, prompts/…

  • Source: PiG's own defect
  • Symptom: Previously Pig recorded the Package and loaded nothing from it.
  • Root cause: Pi resolves every local source, configured Package or -e, through resolveLocalExtensionSource (package-manager.ts:1296-1299, 1377-1384): when collectPackageResources reports no filter, no "pi" manifest and no conventional resource directory (:2214-2262), the directory itself is one enabled extension.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0f6fdc2093 (2026-10-07), 382156cdb7 (2026-10-07), 66bb7180b3 (2026-10-07), 8723a419d7 (2026-10-07), a79de03e7f (2026-10-07), 7489735c3f (2026-10-09), 83d54886a6 (2026-10-09)
  • Tests: coding/cli/package_local_directory_extension_test.go::TestInstalledLocalDirectoryWithoutPackageResourcesLoadsAsAnExtension, coding/cli/package_update_workers_test.go::TestPackageUpdateChecksBoundWorkers, coding/extension/host/subprocess/builder_node_entry_test.go::TestResolveNodeEntrypointImportsAnIndexMjsDirectoryAsJiti, coding/resource_loader_extend_test.go::TestResourceLoaderInterfaceExtendsAndReloads, internal/codingagent/interactive_test.go::TestHasAssistantUsageAfterCompaction, internal/codingagent/tools/bash_spawn_hook_test.go::TestBashSpawnHookTypeIsSharedByPowerShell, internal/packagemanager/local_file_source_test.go::TestLocalDirectoryPackageSourceThatDescribesResourcesIsNotItselfAnExtension, internal/packagemanager/local_file_source_test.go::TestLocalDirectoryPackageSourceWithoutResourcesIsItselfAnExtension and 4 more in docs/bug-ledger.json
  • Release note: pig install <dir> of a local directory with no package manifest and no extensions/, skills/, prompts/ or themes/ directory now loads the directory itself as an extension, as pi install does. Previously Pig recorded the Package and loaded nothing from it.

BF-83fea11

On Windows, a refused connection reads as Node's connect ECONNREFUSED and is not retried, as in Pi; it was…

  • Source: PiG's own defect
  • Symptom: On Windows, a refused connection reads as Node's connect ECONNREFUSED and is not retried, as in Pi; it was reported as a retryable fetch failed.
  • Root cause: nodeNetErrorMessage accepted only the connect syscall, but Go names the Windows call connectex, so on Windows a refused connect fell through to 'fetch failed' and a retryable Bedrock error where Node reports 'connect ECONNREFUSED 127.0.0.1:N', not retryable (TestNodeNetErrorMessageIsNodesText, TestBedrockRefusedConnectIsNodesErrorLikePi on the release PR's Windows native-2 job).
  • User impact: An operation failed or was rejected
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: 83fea11dbf (2026-10-15)
  • Tests: ai/bedrock_error_metadata_upstream_test.go::TestBedrockRefusedConnectIsNodesErrorLikePi, ai/node_net_error_test.go::TestNodeNetErrorMessageIsNodesText, ai/oauth_page_corpus_test.go::TestOAuthPageCorpusMatchesThePinnedPackage
  • Release note: On Windows, a refused connection reads as Node's connect ECONNREFUSED and is not retried, as in Pi; it was reported as a retryable fetch failed.

BF-844d215

Repairing a partial tool-call arguments text follows partial-json's obj["__proto__"] = value: a…

  • Source: PiG's own defect
  • Symptom: Repairing a partial tool-call arguments text follows partial-json's obj["__proto__"] = value: a "__proto__": null member leaves the object without a prototype, so a later "__proto__" member is an ordinary member, as in Pi (it was dropped).
  • Root cause: partial-json builds objects with obj[key] = value. A "proto" member runs Object.prototype's setter: null makes the prototype null, an object or array replaces it, any other value is ignored, and none creates a property. Once the prototype is null there is no setter, so a later "proto" member is an ordinary member. parseMembers dropped every "proto" member.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 844d2155d8 (2026-10-11)
  • Tests: ai/streaming_json_oracle_test.go::TestStreamingJSONMatchesPiOnSeededInput, ai/streaming_json_resume_test.go::TestStreamingArgumentsParserMatchesWholeTextParser
  • Release note: Repairing a partial tool-call arguments text follows partial-json's obj["__proto__"] = value: a "__proto__": null member leaves the object without a prototype, so a later "__proto__" member is an ordinary member, as in Pi (it was dropped).

BF-848836a

fix(frontmatter): header starts one UTF-16 unit after the fence

  • Source: PiG's own defect
  • Symptom: fix(frontmatter): header starts one UTF-16 unit after the fence
  • Root cause: Pi slices the YAML header with normalized.slice(4, endIndex); Go cut at byte 4, which splits a multi-byte character after '---'. Adds a random-document differential (6000 documents plus nine fixed rows, 8+ red before).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 848836a581 (2026-10-10)
  • Tests: internal/codingagent/frontmatter/pi_random_oracle_test.go::TestParseRandomDocumentsMatchesPi

BF-852652e

PiG no longer names documentation that does not exist

  • Source: PiG's own defect
  • Symptom: They named <config root>/docs files that were never written.
  • Root cause: Pi names the docs of its installed package, which exist wherever Pi runs. PiG writes its bundle under the config root at startup (D22) and ignored a failed write, so a config root this user cannot write and that held no bundle of the running build left the system prompt, login help and the codemode description naming files that were never written.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 852652e3ae (2026-10-09), da99854d1b (2026-10-09)
  • Tests: coding/cli/piglet_verify_cache_startup_test.go::TestSignedPigletBinaryRunsWhenItsVerifyCacheCannotBeWritten, coding/cli/readonly_config_root_binary_test.go::TestReadOnlyConfigRootStartsQuietlyAndNamesOnlyDocsThatExist, coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, internal/codingagent/auth_guidance_test.go::TestNoTextNamesADocsBundleStartupCouldNotWrite
  • Release note: PiG no longer names documentation that does not exist. When the config root (PIG_HOME) cannot be written and holds no documentation bundle of the running build, the system prompt has no docs section, and login help and the codemode description name no docs page. Before, they named <config root>/docs files that were never written. pig docs show still prints the embedded pages.

BF-852f029

Durable document, conversation-view and task-graph states report failing state listeners to the Session's…

  • Source: PiG's own defect
  • Symptom: Durable document, conversation-view and task-graph states report failing state listeners to the Session's report, as Pi does, instead of dropping them.
  • Root cause: Ports packages/chord/src/state.ts at Pi (moved from services/state.ts) into the public chord package: - NewReplicatedState is replicatedState(initial) (state.ts:404-419, MutableReplicatedStateImpl :240-290): Change and ChangeArray draft through delta.Change, Replace adopts an alias-free root, a deeply equal replacement publishes nothing, a change or replace from a change callback is rejected (state.ts:256, :281). - The mutable state has no exact-op subscription any more, so a listener failure never reaches Change or Replace: StateSubscriber reports it through reportErrorAsync (state.ts:146-169, :394-398), the Go uncaught reporter, after the revision is committed, and delivery to later…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 852f029c26 (2026-10-12)
  • Tests: chord/json_test.go::TestIsJsonValueChecksStrictJSONWithoutNormalizing, chord/replicated_state_delivery_upstream_test.go::TestAttachedReplicatedStateListenerErrors, chord/replicated_state_delivery_upstream_test.go::TestMutableReplicatedStateReportsRejectedCallbacksThroughItsDefaultReporter, chord/replicated_state_delivery_upstream_test.go::TestReplicatedStatePublicDelivery, chord/replicated_state_upstream_test.go::TestAuthoritativeReplicatedStateSources, chord/replicated_state_upstream_test.go::TestTransactionalReplicatedState, durable/session/session_watches_test.go::TestSessionDocumentWatches, internal/goroutineid/goroutineid_test.go::TestCurrentIdentifiesTheCallingGoroutine
  • Release note: Durable document, conversation-view and task-graph states report failing state listeners to the Session's report, as Pi does, instead of dropping them.

BF-85618d9

fix(rpc): a failed switch_session answers before the replacement ahead of it; prompt compaction checks…

  • Source: PiG's own defect
  • Symptom: fix(rpc): a failed switch_session answers before the replacement ahead of it; prompt compaction checks resolve in order
  • Root cause: (2) The prompt compaction check shares one helper between CheckPromptCompaction and BeginPromptCompactionCheck; the overflow guard reset and the replayed overflow response are pinned. (3) A check that follows a compaction still running decides behind it off the loop, so the prompts of one batch resolve in order (the Session lock race made p2 answer first in 1 of 3 runs); pinned by TestRPCPromptsWithDueCompactionsAnswerInRequestOrder (red when the chain is cut). rpcResolvedChannel no longer steals rpcAwaitInOrder's doc comment.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 85618d9a25 (2026-10-10)
  • Tests: coding/cli/rpc_prompt_order_test.go::TestRPCPromptsWithDueCompactionsAnswerInRequestOrder, coding/cli/rpc_replacement_order_test.go::TestRPCEarlyReplacementErrorFollowsTheCompactionStart, coding/cli/rpc_replacement_order_test.go::TestRPCFailedSwitchSessionAnswersBeforeTheReplacementAheadOfIt, coding/session_prompt_compaction_check_test.go::TestBeginPromptCompactionCheckReturnsOnlyADueCompaction, coding/session_prompt_compaction_check_test.go::TestPromptCompactionCheckReplaysAnOverflowedResponse

BF-85908ae

A /tree branch summary the provider refuses as too long is retried with the newest half of the request…

  • Source: A Pi defect that PiG fixed first (divergence D167)
  • Symptom: A /tree branch summary the provider refuses as too long is retried with the newest half of the request, never less than the newest message, until it is accepted (D167).
  • Root cause: D167.md, the pigdocs row, the changelog and the CompactUsing marker now state it, and the evidence lists its tests. The pigdocs D167 row moves from 'Additive features' to 'Behavior differences': D167 changes a Pi behaviour.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: none recorded
  • Removed when Pi fixes it: Pi summarizes over-window histories itself (chunking or trimming the summary input).
  • Fix commits: 78e2b7c5fc (2026-10-11), 85908ae8a9 (2026-10-11), c8be31c504 (2026-10-11), f412b3de4e (2026-10-11), 54b89d33cd (2026-10-13)
  • Tests: internal/codingagent/compaction/generate_summary_oracle_test.go::TestGenerateSummaryMatchesPi, internal/codingagent/compaction/over_window_history_test.go::TestGenerateBranchSummaryHalvesARequestTheProviderRefusesAsTooLong, internal/codingagent/compaction/over_window_history_test.go::TestGenerateBranchSummaryOfABranchThatFitsIsOneRequest, internal/codingagent/compaction/over_window_history_test.go::TestGenerateBranchSummaryOfOneRefusedMessageKeepsTheProvidersError, internal/codingagent/compaction/over_window_history_test.go::TestGenerateSummaryHalvesAChunkTheProviderRefusesAsTooLong, internal/codingagent/compaction/over_window_history_test.go::TestGenerateSummaryOfAHistoryLongerThanTheWindowIsChunked, internal/codingagent/compaction/over_window_history_test.go::TestGenerateSummaryOfAHistoryThatFitsTheWindowIsOneRequest, internal/codingagent/compaction/over_window_history_test.go::TestGenerateSummaryOfOneMessageLongerThanTheWindowKeepsTheProvidersError and 6 more in docs/bug-ledger.json
  • Release note: A /tree branch summary the provider refuses as too long is retried with the newest half of the request, never less than the newest message, until it is accepted (D167).

BF-85a7b01

A prompt now fails with Pi's error when the Session's history holds a system message whose content is a…

  • Source: PiG's own defect
  • Symptom: PiG read such a member as absent and sent the request.
  • Root cause: AgentSession.prompt runs _preparePromptAndToolLoadout after before_agent_start; it applies the run's tool loadout and then replays the transcript's system messages with getCurrentSystemMessage (agent-session.ts:1738-1748). The replay renders each system message's content with contentText and then applies every toolsRemoved and toolsAdded list (pi-ai utils/transcript.ts getCurrentSystemMessage, getCurrentTools; utils/text.ts contentText). Pi parses a session without validation, so a system message with a numeric content makes the prompt fail with "content.filter is not a function", and one with toolsAdded 5 with "number 5 is not iterable (cannot read property Symbol(Symbol.iterator))"; the…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 85a7b010a8 (2026-10-10)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, agent/system_replay_error_test.go::TestCurrentSystemMessageErrorMatchesPi, internal/codingagent/interactive_prompt_validation_test.go::TestInteractivePromptValidationPrecedesBeforeAgentStart, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain and 3 more in docs/bug-ledger.json
  • Release note: A prompt now fails with Pi's error when the Session's history holds a system message whose content is a number, a boolean or an object (content.filter is not a function), whose content array holds null, or whose toolsAdded or toolsRemoved is a number, a boolean, an object or an array that holds null (for example number 5 is not iterable (cannot read property Symbol(Symbol.iterator))). Before, PiG read such a member as absent and sent the request. In RPC mode the prompt response reports the error, and in interactive mode the error is shown; no run starts.

BF-85c98be

durable/interop/bench.mjs and sqlhost-bench seed|sample report the process's own peak RSS on Linux…

  • Source: PiG's own defect
  • Symptom: durable/interop/bench.mjs and sqlhost-bench seed|sample report the process's own peak RSS on Linux (VmHWM), as bench/durableperf already did. getrusage's maxRSS lagged it by 6 to 20 MiB, and it took on the launcher's peak when that was larger: Go, Python and Node all spawn with vfork, so the child's maxRSS starts from the parent's.
  • Root cause: durable/interop/bench.mjs read process.resourceUsage().maxRSS and sqlhost-bench getrusage's ru_maxrss. On Linux that value carries the RSS high-water mark of the address space the process was exec'd from, and every launcher of these samples spawns with vfork: with a 512 MiB parent, a bench.mjs sample whose own VmHWM is 110.5-111.6 MiB reports 512.8-521.5 MiB from Go (exec.Command, also through taskset), Python (subprocess.run) and Node (spawnSync).
  • User impact: Behavior differed from Pi
  • Platform: Linux
  • Fixed in: 0.5.0
  • Fix commits: 85c98bed2b (2026-10-13)
  • Tests: None recorded.
  • Release note: durable/interop/bench.mjs and sqlhost-bench seed|sample report the process's own peak RSS on Linux (VmHWM), as bench/durableperf already did. getrusage's maxRSS lagged it by 6 to 20 MiB, and it took on the launcher's peak when that was larger: Go, Python and Node all spawn with vfork, so the child's maxRSS starts from the parent's. peakRss in the pi and pig-native lines of earlier Linux runs is low by that lag.

BF-85ca0f5

Rebuilding the transcript (resume, /tree, display settings) now places cache-miss notices as Pi does in a…

  • Source: PiG's own defect
  • Symptom: Rebuilding the transcript (resume, /tree, display settings) now places cache-miss notices as Pi does in a session with branches: each response is compared with the request before it in the session file, as the live notice and /session do.
  • Root cause: renderSessionItems calls collectCacheMisses(sessionManager.getEntries()), which scans the whole file in file order. PiG scanned only the rendered branch, so a response on a second branch missed the notice that the live path and /session report.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 85ca0f547b (2026-10-13)
  • Tests: internal/codingagent/interactive_cache_miss_entries_test.go::TestRebuiltCacheMissNoticesScanEveryEntryInFileOrder
  • Release note: Rebuilding the transcript (resume, /tree, display settings) now places cache-miss notices as Pi does in a session with branches: each response is compared with the request before it in the session file, as the live notice and /session do. PiG compared only within the shown branch.

BF-860776a

A Google provider built from a model id without catalog metadata now sends its own earlier thinking blocks as…

  • Source: PiG's own defect
  • Symptom: The missing API made every earlier turn look like another model and the signature was dropped (found by the provider-wire parity scenario against the pinned Pi).
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), 860776a9f5 (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/google_same_model_signature_test.go::TestGoogleProviderKeepsItsOwnThinkingSignaturesWithoutCatalogMetadata and 3 more in docs/bug-ledger.json
  • Release note: A Google provider built from a model id without catalog metadata now sends its own earlier thinking blocks as thoughts with their signatures, as Pi does for the same provider, API and model. Before, the missing API made every earlier turn look like another model and the signature was dropped (found by the provider-wire parity scenario against the pinned Pi).

BF-86364b4

fix(minimatch): case-insensitive patterns compare as a JavaScript RegExp does

  • Source: PiG's own defect
  • Symptom: fix(minimatch): case-insensitive patterns compare as a JavaScript RegExp does
  • Root cause: regexp2 lower-cases both sides, so a pattern letter or non-ASCII literal also matched characters that lower-case to it (İ to i, the Kelvin sign to k, the Angstrom sign to å) where JS /i compares toUpperCase and keeps a character whose upper case would become ASCII. ASCII letters now spell their two cases and non-ASCII literals and Canonicalize classes are case-sensitive groups.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 86364b4411 (2026-10-10)
  • Tests: internal/minimatch/minimatch_oracle_test.go::TestMatchAgainstNodeMinimatch

BF-866c7f9

fix(rpc): two prompts of one batch answer in request order

  • Source: PiG's own defect
  • Symptom: fix(rpc): two prompts of one batch answer in request order
  • Root cause: Pi's check resumes without I/O unless it compacts, so the policy half now runs on the loop (Session.BeginPromptCompactionCheck) and only a due compaction leaves it, chained through rpcAwaitInOrder.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 866c7f9fb3 (2026-10-10)
  • Tests: coding/cli/rpc_prompt_order_test.go::TestRPCPromptsOfOneBatchAnswerInRequestOrder, coding/session_prompt_compaction_check_test.go::TestBeginPromptCompactionCheckReturnsOnlyADueCompaction

BF-869cef8

A pi-messages stream event that is a number, string, boolean or array is skipped, as Pi skips an event of…

  • Source: PiG's own defect
  • Symptom: A pi-messages stream event that is a number, string, boolean or array is skipped, as Pi skips an event of unknown type, instead of ending the reply with a JSON decode error.
  • Root cause: It does not: the event decode into PiMessagesEvent failed with "json: cannot unmarshal number into Go value of type ai.PiMessagesEvent", and the stream ended in an error with no reply. Pi's readPiMessagesEvents drops only a falsy event (pi-messages.ts:290-292). createEventConverter returns { ...event, partial } for a truthy number, string, boolean or array, as for any event without a known type (:193-272), and the reply that follows streams. The agent loop ignores the typeless event (agent-loop.ts:415-445).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 77790f284b (2026-10-11), 869cef866a (2026-10-11), e720ec0d0a (2026-10-11)
  • Tests: ai/bedrock_eventstream_integer_float_test.go::TestBedrockStreamAcceptsIntegerValuedFloats, ai/bedrock_eventstream_integer_float_test.go::TestBedrockStreamEventReadsIntegerValuedFloats, ai/openai_stream_oracle_test.go::TestOpenAICompletionsProviderStreamMatchesPi, ai/pi_messages_non_object_event_test.go::TestPiMessagesNonObjectEventIsSkippedLikeAnUnknownEvent, internal/jsjson/normalize_test.go::TestNormalizeIntegers
  • Release note: A pi-messages stream event that is a number, string, boolean or array is skipped, as Pi skips an event of unknown type, instead of ending the reply with a JSON decode error.

BF-86c411a

fix(builtin): codemode and tool_search declare their parameters in TypeBox key order

  • Source: PiG's own defect
  • Symptom: fix(builtin): codemode and tool_search declare their parameters in TypeBox key order
  • Root cause: Probed through RPC get_messages with every built-in tool: all nine parameter objects are now byte-equal to Pi. No divergence (byte parity).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 86c411a481 (2026-10-10)
  • Tests: coding/extension/builtin/codemode/schema_order_test.go::TestInputSchemaKeepsTypeBoxKeyOrder, coding/extension/builtin/toolsearch/schema_order_test.go::TestInputSchemaKeepsTypeBoxKeyOrder

BF-871b48d

pig install <source> --validate-only now validates git and npm sources that are not installed yet

  • Source: PiG's own defect
  • Symptom: pig install <source> --validate-only now validates git and npm sources that are not installed yet.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.3
  • Fix commits: 871b48d401 (2026-10-22)
  • Tests: coding/cli/extension_validate_command_test.go::TestInstallValidateOnlyValidatesInstalledGitSource, coding/cli/extension_validate_command_test.go::TestInstallValidateOnlyValidatesUninstalledGitSourceInScratchDir, coding/cli/extension_validate_command_test.go::TestInstallValidateOnlyValidatesUninstalledNPMSourceInScratchDir
  • Release note: pig install <source> --validate-only now validates git and npm sources that are not installed yet. It fetches them into a temporary directory that is removed afterwards, and no longer reports stat …/git/<host>/<owner>/<repo>: no such file or directory. Installed packages and settings are not touched.

BF-87a6ac5

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: UnmarshalKeepingSurrogates is generic over its destination, so the call sites decode into their own concrete types. The perf-lint entry for FromJsonValue's typed decode keeps its count and follows the callee's new name. unmarshalOrdered is generic for the same reason.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 87a6ac512c (2026-10-25)
  • Tests: None recorded.

BF-88177e8

fix(cli): type the validate-only scratch-fetch marker as D28 and extend the record

  • Source: PiG's own defect
  • Symptom: fix(cli): type the validate-only scratch-fetch marker as D28 and extend the record
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 88177e8e3e (2026-10-22)
  • Tests: None recorded.

BF-8894a13

A double-quoted YAML string in a skill, prompt template or parseFrontmatter header now reads a…

  • Source: PiG's own defect
  • Symptom: Each half became U+FFFD.
  • Root cause: A 6,000-message differential against Pi's own serializeConversation (node + dist) now has 0 differences in Go and Python. Before the fix it had 53 in Go and 33 in Python. The conformance row cannot see this, because the harness decodes reports with encoding/json, which turns every lone surrogate into U+FFFD.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 15cb4f2db8 (2026-10-11), 50f526d69e (2026-10-11), 8894a13f34 (2026-10-11), a178939d6a (2026-10-11), edf4304a1c (2026-10-11)
  • Tests: extensions/sdk-py/tests/test_serialize_conversation_surrogates.py, extensions/sdk/serialize_conversation_surrogate_test.go::TestSerializeConversationKeepsUnpairedSurrogates, internal/codingagent/frontmatter/ordered_test.go::TestParseOrderedListsKeysInInsertionOrder, internal/yaml12/surrogate_escape_test.go::TestDoubleQuotedSurrogateEscapesJoinIntoOneCharacter, test/extension-conformance/host_parser_helpers_test.go::TestConformance_HostParserHelpersAgreeAcrossSDKs, test/extension-conformance/session_skill_helpers_test.go::TestConformance_SessionAndSkillHelpersAgreeAcrossSDKs
  • Release note: A double-quoted YAML string in a skill, prompt template or parseFrontmatter header now reads a surrogate-pair escape such as "\uD83D\uDE00" as the one character it encodes, as Pi does. Before, each half became U+FFFD.

BF-8894a13-2

The liveness conformance harness answers the host-parser helper calls a factory makes before the register…

  • Source: PiG's own defect
  • Symptom: The liveness conformance harness answers the host-parser helper calls a factory makes before the register frame, so the recording of the five SDK cells is unchanged.
  • Root cause: The yaml library builds a double-quoted scalar from UTF-16 units (String.fromCodePoint per escape), so "\uD83D\uDE00" is U+1F600: Pi's parseFrontmatter reads a: "\uD83D\uDE00" as {"a":"😀"}. yaml12 turned each surrogate escape into U+FFFD on its own, so skills, prompt templates and the new frontmatter.parse host call gave two U+FFFD. doubleQuotedValue now holds a high-surrogate escape until the next thing the scalar receives and joins it with a low-surrogate escape; a surrogate left unpaired stays U+FFFD (a Go string cannot hold it).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 15cb4f2db8 (2026-10-11), 8894a13f34 (2026-10-11), edf4304a1c (2026-10-11), f51d8b4bea (2026-10-11)
  • Tests: internal/codingagent/frontmatter/ordered_test.go::TestParseOrderedListsKeysInInsertionOrder, internal/yaml12/surrogate_escape_test.go::TestDoubleQuotedSurrogateEscapesJoinIntoOneCharacter, test/extension-conformance/compaction_usage_test.go::TestConformance_CompactionUsageHelpersAgreeAcrossSDKs, test/extension-conformance/host_parser_helpers_test.go::TestConformance_HostParserHelpersAgreeAcrossSDKs
  • Release note: The liveness conformance harness answers the host-parser helper calls a factory makes before the register frame, so the recording of the five SDK cells is unchanged.

BF-893ff97

A read call whose offset or limit arrives as a string shows the right line range (:25-37, not…

  • Source: PiG's own defect
  • Symptom: A read call whose offset or limit arrives as a string shows the right line range (:25-37, not :25-2512) in the TUI and in exported HTML (Pi #9887).
  • Root cause: read.ts formatReadLineRange and export template.js add raw args with JS +; offset 25 / limit "13" rendered :25-2512.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 893ff9708b (2026-10-09)
  • Tests: None recorded.
  • Release note: A read call whose offset or limit arrives as a string shows the right line range (:25-37, not :25-2512) in the TUI and in exported HTML (Pi #9887).

BF-89d167f

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: EACCES) and crashes when it must write auth.json or sessions; it has no SDK stage or Binary verify cache. PiG's two additions stay quiet on EROFS/EACCES/EPERM and still warn otherwise.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 89d167fe18 (2026-10-09)
  • Tests: coding/cli/readonly_config_root_test.go::TestReadOnlyConfigRootError

BF-8a18a6b

A Node extension no longer keeps reading an entry the host refused

  • Source: PiG's own defect
  • Symptom: A Node extension no longer keeps reading an entry the host refused.
  • Root cause: Fixes #264
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 8a18a6bb2d (2026-10-21, #272)
  • Tests: coding/extension/host/subprocess/runtime_node_refused_append_test.go::TestNodeExtensionReadsExactlyTheSessionTheHostHolds, coding/extension/host/subprocess/runtime_node_refused_append_test.go::TestNodeRuntimeStopsShowingAnAppendTheHostRefused, test/extension-conformance/session_refused_append_test.go::TestSessionMirrorHoldsOnlyWhatTheHostAppendedAcrossSDKs
  • Release note: A Node extension no longer keeps reading an entry the host refused. When the host answers pi.appendEntry (or ctx.sessionManager.appendCustomEntry) with an error, getEntries(), getBranch(), getLeafId(), getEntry() and the other session reads stop showing that entry, also after later calls the host synchronizes; before, the entry stayed in the extension's session reads although the host's log never held it. Only the refused entry is taken back: appends still in flight stay, and an entry the host appended is read with the parent the host linked it to, where the extension could show the refused entry or an older leaf instead. A call that was cancelled or lost with its connection has no answer, so its entry stays until the host's log settles it.

BF-8a25539

llama-cpp-classify answers match Pi bit for bit: the softmax uses jsmath.Exp, V8's fdlibm Math.exp…

  • Source: PiG's own defect
  • Symptom: llama-cpp-classify answers match Pi bit for bit: the softmax uses jsmath.Exp, V8's fdlibm Math.exp (Go's math.Exp differs in the last bit for some arguments on amd64).
  • Root cause: request-body-lone-surrogate, completions-empty-sanitized-text, llama-cpp-classify-pi-parity, openai-decisions-input-order and openrouter-images-edge-responses get the sha of the commit that added them (TestChangelogFragmentsFollowTheRules). llama-cpp-classify-pi-parity: 'so the server receives what it receives from Pi' for the forbidden phrase 'same requests' (TestPublicClaimsMatchTheEvidence).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 8a25539ce3 (2026-10-13)
  • Tests: test/docs-drift/changelog_fragments_test.go::TestChangelogFragmentsFollowTheRules, test/docs-drift/public_claims_test.go::TestPublicClaimsMatchTheEvidence
  • Release note: llama-cpp-classify answers match Pi bit for bit: the softmax uses jsmath.Exp, V8's fdlibm Math.exp (Go's math.Exp differs in the last bit for some arguments on amd64). Its requests are written with their members in Pi's order, and a failed lookup no longer cancels the sibling requests of the same Promise.all, so the server receives what it receives from Pi.

BF-8a88c34

On Windows, a custom theme edited in a rapid burst of writes no longer stays on a stale version

  • Source: PiG's own defect
  • Symptom: On Windows, a custom theme edited in a rapid burst of writes no longer stays on a stale version.
  • Root cause: An overflow of the native change buffer loses events but leaves the watch armed. The run loop closed the native watcher on every error, so a burst of writes that overflowed the Windows ReadDirectoryChangesW buffer ended the watch and the final write was never reloaded. An overflow now schedules a reload, as Pi does for a change without a filename, and keeps watching; any other error still closes the watcher.
  • User impact: Lost or altered data
  • Platform: Windows
  • Fixed in: Unreleased
  • Fix commits: 8a88c34d51 (2026-10-26)
  • Tests: tui/theme_watcher_upstream_test.go::TestThemeWatcherOtherErrorsCloseNativeWatcher, tui/theme_watcher_upstream_test.go::TestThemeWatcherOverflowReloadsAndKeepsWatching
  • Release note: On Windows, a custom theme edited in a rapid burst of writes no longer stays on a stale version. A burst that overflowed the native change buffer made the watcher treat the overflow as a failure and close its watch, so writes after the overflow were never seen and the theme kept an older version of the file (seen as accent="#123456" want="#000064" in Windows CI, only while the machine was loaded). An overflow now reloads the theme file, as Pi reloads on a change without a filename, and the watcher keeps watching; any other watch failure still closes it, as in Pi.

BF-8a9bb40

fix(ai): Anthropic retry-delay message and Codex error body without an error member

  • Source: PiG's own defect
  • Symptom: fix(ai): Anthropic retry-delay message and Codex error body without an error member
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 8a9bb40f7b (2026-10-10)
  • Tests: None recorded.

BF-8ab5a89

A long session no longer holds each tool result twice

  • Source: PiG's own defect
  • Symptom: A long session no longer holds each tool result twice.
  • Root cause: Pi's session holds the message object the agent appended (session-manager.ts appendMessage pushes it into fileEntries), so a tool result is in memory once. appendMessageEntry kept the appended message only when its record decoded DeepEqual to it, and a tool result never does: its record decodes Details to their JSON (orderedjson.Value), while the tool gave a typed value (ReadDetails, BashDetails, EditToolDetails, ...) or an extension a map. So every tool result was held twice, as the agent's message and as the record's decode, text included.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 06c408c21d (2026-10-11), 8ab5a89874 (2026-10-11), eab3caf769 (2026-10-11)
  • Tests: internal/codingagent/session_store_test.go::TestAppendedMessageIsHeldOnceWhenItsRecordDecodesToIt, internal/codingagent/session_store_test.go::TestAppendedToolResultWithTypedDetailsIsHeldOnce, internal/codingagent/tools/truncation_share_test.go::TestTruncatedResultDetailsShareTheReturnedText
  • Release note: A long session no longer holds each tool result twice. The session now keeps the tool result the agent produced instead of a second copy decoded from its record, and a truncated read, ls, find, grep or bash result keeps its kept output once instead of again in its truncation details. In a 75-prompt session of file reads (about 4.2M tokens of context), memory at the end drops from about 245 MB to about 170 MB.

BF-8ab86c8

RPC get_state sent right after new_session, fork, clone, switch_session or export_html now…

  • Source: PiG's own defect
  • Symptom: RPC get_state sent right after new_session, fork, clone, switch_session or export_html now answers first from the old Session, as in Pi; PiG still answers the replacement when stdin ends, which Pi does not (D146).
  • Root cause: One ordering mechanism, rpcAwaitInOrder on the executor's event-loop task queue, serves both awaited operations that Pi resumes in request order: - set_model's auth checks run concurrently and settle in input order. Two pipelined set_models used to apply in completion order, so the final model was wrong in 1 of 12 runs. - Session replacements chain: each starts after the previous one applied and answers after it, so the last one requested decides the final Session. Concurrent replacements raced in Runner.BindCommandActions and hung at stdin end about one run in two. - A fork or clone requested while an earlier replacement is pending reads its entry from the Session that was current when it…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 8ab86c8219 (2026-10-10)
  • Tests: coding/cli/rpc_replacement_order_test.go::TestRPCCloneAfterNewSessionOfOneBatchUsesTheRequestingSession, coding/cli/rpc_replacement_order_test.go::TestRPCReplacementStartsAfterItsBatchWasDispatched, coding/cli/rpc_replacement_order_test.go::TestRPCReplacementsOfOneBatchRunInRequestOrder, coding/cli/rpc_replacement_order_test.go::TestRPCSessionReplacementAnswersAfterALaterCommandOfItsBatch, coding/cli/rpc_replacement_order_test.go::TestRPCSessionReplacementsOfOneBatchSettleInDispatchOrder, coding/cli/rpc_resume_integration_test.go::TestRPCModeSessionReplacementAndExport, coding/cli/rpc_set_model_pipeline_test.go::TestRPCPipelinedSetModelsApplyInInputOrder, test/parity/scenarios/rpc/55-rpc-replacement-concurrent-get-state.toml
  • Release note: RPC get_state sent right after new_session, fork, clone, switch_session or export_html now answers first from the old Session, as in Pi; PiG still answers the replacement when stdin ends, which Pi does not (D146).

BF-8b75dc0

Config selector (pig config): every key that reaches the search input re-filters and resets the selection…

  • Source: PiG's own defect
  • Symptom: Config selector (pig config): every key that reaches the search input re-filters and resets the selection to the first item even when the query is unchanged, Tab no longer edits the search when project mode is unavailable, and the search is a callback-mode Input so Enter no longer freezes it, all as in Pi's ResourceList.handleInput (tui/config_selector_oracle_test.go, 648 probes against pinned Pi).
  • Root cause: Every callback with its arguments, the callback count after each key, and every frame's text are compared; Pi's callbacks reached include onThemePreview, onThemeChange, onModelThinkingLevelChange/Remove and 30 more. Pi has no PiG 'Mask secret input' row (D80), so the driver inserts that row and its callback into a temp copy of Pi's settings-selector.js at Pig's position. One real difference: Pi's WarningSettingsSubmenu shows '(this.state.anthropicExtraUsage ?? true)' and always writes the toggled value; Pig's submenu used the Go zero value, so a warning the settings never set showed as off and its first toggle reported {} (the Set flag was never set). It now defaults to on and marks the…
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d1f258f335 (2026-10-06), 082355f166 (2026-10-07), 0aba908b97 (2026-10-07), 0ae3fcf17c (2026-10-07), 4058f34548 (2026-10-07), 4d041349a7 (2026-10-07), 56a0400b9c (2026-10-07), 5b0e90985b (2026-10-07), 623553262c (2026-10-07), 70e1841cd8 (2026-10-07), 8b75dc031c (2026-10-07), a7be906e71 (2026-10-07), c8a90a61db (2026-10-07), c8e3d86378 (2026-10-07), ccaade27af (2026-10-07), f2a2b3409b (2026-10-07)
  • Tests: internal/codingagent/extension_dialog_remapping_test.go::TestExtensionDialogRemappingThroughOwnerLoop, internal/codingagent/hidden_thinking_label_test.go::TestExtensionHiddenThinkingLabelReachesAssistantBlocks, internal/codingagent/remote_overlay_production_review_test.go::TestRemoteOverlayProductionRejectsCachedTerminalWidth, internal/codingagent/scoped_models_oracle_test.go::TestScopedModelsSelectorMatchesPi, internal/codingagent/session_selector_oracle_test.go::TestSessionSelectorInputMatchesPi, internal/codingagent/settings_selector_oracle_test.go::TestSettingsSelectorMatchesPi, internal/codingagent/settings_submenu_oracle_test.go::TestSettingsSubmenusMatchPi, internal/codingagent/thinking_selector_oracle_test.go::TestThinkingSelectorInputMatchesPi and 29 more in docs/bug-ledger.json
  • Release note: Config selector (pig config): every key that reaches the search input re-filters and resets the selection to the first item even when the query is unchanged, Tab no longer edits the search when project mode is unavailable, and the search is a callback-mode Input so Enter no longer freezes it, all as in Pi's ResourceList.handleInput (tui/config_selector_oracle_test.go, 648 probes against pinned Pi).

BF-8b8f379

A pi-ai call made while a module loads is answered in every extension host: SDK sessions, the extension…

  • Source: PiG's own defect
  • Symptom: A pi-ai call made while a module loads is answered in every extension host: SDK sessions, the extension validation, the authentication inspection and the login preview no longer hang on it, and any extension call with no handler now gets an error result instead of silence.
  • Root cause: Host.runCall dropped a call that no case took when the Host had neither a UI bridge nor a call hook, so a module that awaited a pi-ai call at load waited for ever in an SDK session (DiscoverAndLoadExtensions, DefaultResourceLoader), the extension validation and the authentication inspection. A call no handler takes is now an error result. A Host with no bridge streams a load-time pi-ai call through SetEarlyModelStream, installed by the coding package from the same EarlyModelStream the CLI Host's bridge uses, and the login preview's own bridge installs it too.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 8b8f379483 (2026-10-11), e9e9cf8816 (2026-10-11)
  • Tests: coding/cli/load_time_completion_loaders_test.go::TestAuthContributionInspectionAnswersLoadTimeCompletions, coding/cli/load_time_completion_loaders_test.go::TestAuthContributionLoadByNameAnswersLoadTimeCompletions, coding/cli/load_time_completion_loaders_test.go::TestExtensionLoginPreviewAnswersLoadTimeCompletions, coding/cli/load_time_completion_loaders_test.go::TestExtensionValidationAnswersLoadTimeCompletions, coding/extension/host/subprocess/runtime_node_model_runtime_test.go::TestHostAnswersACallItHasNoHandlerFor, coding/extension/host/subprocess/runtime_node_model_runtime_test.go::TestNodeLoadTimeCompletionAnswersBeforeRegistration, coding/load_time_completion_test.go::TestDefaultResourceLoaderAnswersLoadTimeCompletions, coding/load_time_completion_test.go::TestDiscoverAndLoadExtensionsAnswersLoadTimeCompletions and 1 more in docs/bug-ledger.json
  • Release note: A pi-ai call made while a module loads is answered in every extension host: SDK sessions, the extension validation, the authentication inspection and the login preview no longer hang on it, and any extension call with no handler now gets an error result instead of silence.

BF-8b9e467

PIG_PROFILE=metrics reports the exact cumulative allocation counts

  • Source: PiG's own defect
  • Symptom: This fix it missed allocations still held in the runtime's per-processor caches, so a record could be as much as half a megabyte low, depending on whether a garbage collection happened to run shortly before exit.
  • Root cause: PIG_PROFILE=metrics read /gc/heap/allocs through runtime/metrics, which does not flush the per-P mcaches. A span still cached keeps its allocations out of the counter until a refill or a collection, so the record undercounted by up to ~500 KB depending on whether a GC happened to run shortly before exit. That made process.rpc_startup.bytes bimodal: 2.79 MB on a quiet host and 3.38 MB under load. The high value is the real one. CollectMetrics now flushes with ReadMemStats before it reads the counters (TestCollectMetricsCountsAllocations InCachedSpans; it was red 4/5 before).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 8b9e467f20 (2026-10-12)
  • Tests: ai/registry_index_test.go::TestRegistryIndexesMatchPerModelAppendInCatalogOrder, internal/profiling/metrics_test.go::TestCollectMetricsCountsAllocationsInCachedSpans
  • Release note: PIG_PROFILE=metrics reports the exact cumulative allocation counts. Before this fix it missed allocations still held in the runtime's per-processor caches, so a record could be as much as half a megabyte low, depending on whether a garbage collection happened to run shortly before exit.

BF-8bc7c8f

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 8bc7c8f3f0 (2026-10-10)
  • Tests: mcp/oauth/callback_server_options_test.go::TestOAuthCallbackServerNamesTheRedirectMixUpAndReturnsTheContextCause

BF-8c3f5a1

fix(tui): a finished block's hidden thinking run keeps no streaming memos

  • Source: PiG's own defect
  • Symptom: fix(tui): a finished block's hidden thinking run keeps no streaming memos
  • Root cause: An earlier change keeps a finished Markdown's streaming memos until its next render when its text was not drawn since the last delta. A thinking run hidden while it streams (Ctrl+T or a click) shows the hidden-thinking label instead, so its Markdown never renders again and kept the memos for the session.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 8c3f5a1959 (2026-10-13)
  • Tests: tui/assistant_message_hidden_thinking_memos_test.go::TestFinishedBlockKeepsNoMemosForAHiddenThinkingRun

BF-8c5a7a4

The Go, Python and Rust extension SDKs' serializeConversation (SerializeConversation…

  • Source: PiG's own defect
  • Symptom: The Go, Python and Rust extension SDKs' serializeConversation (SerializeConversation, serialize_conversation) omits thinking blocks, as Pi 1.2.0's does and the Go host already did: an assistant message prints only its visible text and tool calls, with no [Assistant thinking] line.
  • Root cause: The Go host (internal/codingagent/compaction) already omits it; the Go, Python and Rust SDKs still printed it.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 8c5a7a4e32 (2026-10-13)
  • Tests: None recorded.
  • Release note: The Go, Python and Rust extension SDKs' serializeConversation (SerializeConversation, serialize_conversation) omits thinking blocks, as Pi 1.2.0's does and the Go host already did: an assistant message prints only its visible text and tool calls, with no [Assistant thinking] line.

BF-8c7bacf

delta.AssertValidOp checks path safety as Pi's assertValidOp does: it rejects a negative, fractional…

  • Source: PiG's own defect
  • Symptom: It accepted them, and only ApplyImmutable rejected them while applying.
  • Root cause:
    • AssertValidOp never checked path safety: it accepted [-1], [1.5], [true], [null] and nested segments that Pi's assertPathArg rejects. - Its messages carried an "invalid delta operation: " prefix and wrong names ("t payload" for Pi's "t shape", "invalid ... path" for "path is empty"). - ApplyImmutable rejected a numeric segment on an object; Pi sets that key: applyImmutable({a:1}, [["s",[1],2]]) is {"1":2,"a":1}. - The public path_error test expected ["a","b","c"] where Pi reports the unresolvable container ["a","b"].
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1d27f9097e (2026-10-07), 1f339c7ef2 (2026-10-07), 229e0b3983 (2026-10-07), 88d190453c (2026-10-07), 1f5a782c0a (2026-10-08), 2f86645d6b (2026-10-08), 33c35623be (2026-10-08), 453b3b354c (2026-10-08), 5355335e3a (2026-10-08), 5dd595fb87 (2026-10-08), 6ca5fde3a9 (2026-10-08), 6cd85b3523 (2026-10-08), 9d34ce6e4f (2026-10-08), d4e4a82ed9 (2026-10-08), f735020719 (2026-10-08), 8c7bacff5b (2026-10-09), 9b505ead89 (2026-10-09), cb15f9d6b6 (2026-10-09)
  • Tests: chord/delta/apply_immutable_upstream_test.go::TestCheckedImmutableOperationApplication, chord/delta/apply_immutable_upstream_test.go::TestImmutableApplicationComplexity, chord/delta/apply_numeric_key_test.go::TestApplyImmutableSetsANumericSegmentOnAnObjectAsAKey, chord/delta/apply_pi_oracle_test.go::TestApplyResolvesArraySegmentsInUpstreamOrder, chord/delta/apply_pi_oracle_test.go::TestTruncateInsideASurrogatePairKeepsTheLowHalf, chord/delta/delta_pi_differential_test.go::TestDeltaMatchesPiOnRandomCases, chord/delta/delta_upstream_test.go::TestDeltaArrayIndexSafety, chord/delta/delta_upstream_test.go::TestDeltaCanonicalStrings and 55 more in docs/bug-ledger.json
  • Release note: delta.AssertValidOp checks path safety as Pi's assertValidOp does: it rejects a negative, fractional, boolean, null or nested path segment, and __proto__, constructor or prototype. Before, it accepted them, and only ApplyImmutable rejected them while applying. Its messages are Pi's (path is empty, t shape).

BF-8c7bacf-2

delta.ApplyImmutable sets a numeric path segment on an object as that key, as Pi does

  • Source: PiG's own defect
  • Symptom: It reported unresolvable path.
  • Root cause:
    • AssertValidOp never checked path safety: it accepted [-1], [1.5], [true], [null] and nested segments that Pi's assertPathArg rejects. - Its messages carried an "invalid delta operation: " prefix and wrong names ("t payload" for Pi's "t shape", "invalid ... path" for "path is empty"). - ApplyImmutable rejected a numeric segment on an object; Pi sets that key: applyImmutable({a:1}, [["s",[1],2]]) is {"1":2,"a":1}. - The public path_error test expected ["a","b","c"] where Pi reports the unresolvable container ["a","b"].
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5355335e3a (2026-10-08), 9d34ce6e4f (2026-10-08), 8c7bacff5b (2026-10-09), 9b505ead89 (2026-10-09), cb15f9d6b6 (2026-10-09)
  • Tests: chord/delta/apply_immutable_upstream_test.go::TestCheckedImmutableOperationApplication, chord/delta/apply_immutable_upstream_test.go::TestImmutableApplicationComplexity, chord/delta/apply_numeric_key_test.go::TestApplyImmutableSetsANumericSegmentOnAnObjectAsAKey, chord/delta/apply_pi_oracle_test.go::TestApplyResolvesArraySegmentsInUpstreamOrder, chord/delta/apply_pi_oracle_test.go::TestTruncateInsideASurrogatePairKeepsTheLowHalf, chord/delta/delta_pi_differential_test.go::TestDeltaMatchesPiOnRandomCases, chord/delta/delta_upstream_test.go::TestDeltaArrayIndexSafety, chord/delta/delta_upstream_test.go::TestDeltaCanonicalStrings and 28 more in docs/bug-ledger.json
  • Release note: delta.ApplyImmutable sets a numeric path segment on an object as that key, as Pi does. Before, it reported unresolvable path.

BF-8c7bacf-3

PathError and UnsafePathError messages print paths as JSON.stringify does: <, >, &, U+2028 and…

  • Source: PiG's own defect
  • Symptom: PathError and UnsafePathError messages print paths as JSON.stringify does: <, >, &, U+2028 and U+2029 stay unescaped and -0 prints as 0.
  • Root cause:
    • AssertValidOp never checked path safety: it accepted [-1], [1.5], [true], [null] and nested segments that Pi's assertPathArg rejects. - Its messages carried an "invalid delta operation: " prefix and wrong names ("t payload" for Pi's "t shape", "invalid ... path" for "path is empty"). - ApplyImmutable rejected a numeric segment on an object; Pi sets that key: applyImmutable({a:1}, [["s",[1],2]]) is {"1":2,"a":1}. - The public path_error test expected ["a","b","c"] where Pi reports the unresolvable container ["a","b"].
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2f86645d6b (2026-10-08), 8c7bacff5b (2026-10-09)
  • Tests: chord/delta/apply_immutable_upstream_test.go::TestCheckedImmutableOperationApplication, chord/delta/apply_immutable_upstream_test.go::TestImmutableApplicationComplexity, chord/delta/apply_numeric_key_test.go::TestApplyImmutableSetsANumericSegmentOnAnObjectAsAKey, chord/delta/apply_pi_oracle_test.go::TestApplyResolvesArraySegmentsInUpstreamOrder, chord/delta/apply_pi_oracle_test.go::TestTruncateInsideASurrogatePairKeepsTheLowHalf, chord/delta/delta_pi_differential_test.go::TestDeltaMatchesPiOnRandomCases, chord/delta/delta_upstream_test.go::TestDeltaArrayIndexSafety, chord/delta/delta_upstream_test.go::TestDeltaCanonicalStrings and 18 more in docs/bug-ledger.json
  • Release note: PathError and UnsafePathError messages print paths as JSON.stringify does: <, >, &, U+2028 and U+2029 stay unescaped and -0 prints as 0.

BF-8d008eb

fix(interactive): paint the requested first frame before the color wait returns

  • Source: PiG's own defect
  • Symptom: fix(interactive): paint the requested first frame before the color wait returns
  • Root cause: The first frame has nothing to throttle against, so TUI.start's request now reaches the owner loop at once (RequestImmediateRender), and the startup color wait paints a frame already handed to the owner loop before it returns. A terminal that answers at once no longer leaves the first frame waiting for the rest of Init.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 8d008ebed6 (2026-10-13)
  • Tests: None recorded.

BF-8d23cfc

Saving a setting keeps the key order of the existing settings.json, at the top level and inside a patched…

  • Source: PiG's own defect
  • Symptom: A replaced setting stays where it was and a new setting is added at the end; the file used to be rewritten with its top-level keys sorted.
  • Root cause: Pi persists { ...currentFileSettings } with the modified fields assigned onto it and JSON.stringify(merged, null, 2) (settings-manager.ts:715-732), so the file's keys keep their order. saveSettingsPatch re-encoded a Go map and sorted the top-level keys. Regression: TestSaveSettingsPatchKeepsTheFileKeyOrder, TestSaveSettingsPatchReplacesInPlace.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 8d23cfcee3 (2026-10-06)
  • Tests: internal/codingagent/settings_key_order_test.go::TestSaveSettingsPatchKeepsTheFileKeyOrder, internal/codingagent/settings_key_order_test.go::TestSaveSettingsPatchReplacesInPlace
  • Release note: Saving a setting keeps the key order of the existing settings.json, at the top level and inside a patched object, as Pi does. A replaced setting stays where it was and a new setting is added at the end; the file used to be rewritten with its top-level keys sorted.

BF-8eb1202

An OpenAI Chat Completions chunk with a member of an unexpected type (a number for delta.content…

  • Source: PiG's own defect
  • Symptom: An OpenAI Chat Completions chunk with a member of an unexpected type (a number for delta.content, delta.tool_calls or function.arguments, a non-object choices or delta, a non-string delta.role or tool call type, a falsy custom, a tool call element that is not an object) is read as Pi's JavaScript reads it: the member is ignored, tool-call arguments append their string form, a tool_calls string makes one empty tool call per character, any other tool_calls value that is not an array ends the turn with choice.delta.tool_calls is not iterable, non-empty array content (or an object with a positive length) appends its string form, a truthy finish_reason that is not a string ends the turn with Provider finish_reason: <its string form>, a usage that is not an object resets the counts to 0, a falsy count or a details member that is not an object reads as 0, and a prompt count that is not a number counts as its Number() when that is an integer.
  • Root cause: Pi reads event.message.id and event.message.usage.input_tokens (anthropic-messages.ts:666, :679), event.content_block.type (:690), event.delta.type (:737) and event.delta.stop_reason (:818) without a guard, so a null or absent member throws Cannot read properties of null (reading 'x') (or of undefined) and the turn ends with that error; the response id and model are recorded before the usage read throws. PiG's typed decode read each as an empty value and streamed on.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 5078f73100 (2026-10-11), 5932542c21 (2026-10-11), 8eb1202528 (2026-10-11), e1abc6c615 (2026-10-11)
  • Tests: ai/anthropic_event_member_type_oracle_test.go::TestAnthropicEventMistypedMembersMatchPi, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-switch.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-start.toml
  • Release note: An OpenAI Chat Completions chunk with a member of an unexpected type (a number for delta.content, delta.tool_calls or function.arguments, a non-object choices or delta, a non-string delta.role or tool call type, a falsy custom, a tool call element that is not an object) is read as Pi's JavaScript reads it: the member is ignored, tool-call arguments append their string form, a tool_calls string makes one empty tool call per character, any other tool_calls value that is not an array ends the turn with choice.delta.tool_calls is not iterable, non-empty array content (or an object with a positive length) appends its string form, a truthy finish_reason that is not a string ends the turn with Provider finish_reason: <its string form>, a usage that is not an object resets the counts to 0, a falsy count or a details member that is not an object reads as 0, and a prompt count that is not a number counts as its Number() when that is an integer. An Anthropic content_block_delta with a missing or non-numeric index matches no block and its text, thinking, partial-JSON and signature members append their string form. An Anthropic message_start, content_block_start or message_delta is read the same way: a usage, delta or content block that is not an object reads as empty, a falsy message_start count is 0, a block's text, thinking or signature that is not a string starts with its string form, a model that is not a string becomes the response model's string form, and a truthy stop_reason that is not a string ends the turn with Unhandled stop reason: <its string form>. A null or missing message, message usage, content block or delta ends the turn with Pi's Cannot read properties of null (or undefined) error instead of streaming on. Other mistyped members still end the turn with the decode error.

BF-8f45099

Compaction summaries, branch summaries and bug reports omit thinking blocks (Pi 15e7bdc1, Pi #9602): replayed…

  • Source: PiG's own defect
  • Symptom: Compaction summaries, branch summaries and bug reports omit thinking blocks (Pi 15e7bdc1, Pi #9602): replayed thinking is often not in the live context, so including it could make the summarization request larger than the context it summarizes and compaction fail on every attempt.
  • Root cause: Pi (fix(coding-agent,durable): omit thinking from compaction summaries): replayed thinking is often not in the live context (radius drops it, Anthropic drops blocks that do not match the request), so including it made a summarization request larger than the context it summarizes (a ~996K of 1M session produced 1.38M-token requests that failed on every attempt). serializeConversation writes only the visible text, tool calls and tool results. Compaction, branch summaries and bug reports all go through it. PiG's D141 (keep the first and last 1000 UTF-16 units of a thinking-only message) is retired: its remove-when ("Pi bounds thinking-only messages in compaction summaries") holds, and…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 8f450993c6 (2026-10-11)
  • Tests: coding/cli/rpc_compaction_thinking_only_test.go::TestRPCCompactionSurvivesThinkingOnlyMessagesTheRequestsOmit, internal/codingagent/compaction/serialization_oracle_test.go::TestSerializeConversationMatchesPiOracle, internal/codingagent/compaction/serialization_upstream_test.go::TestSerializeConversationUpstream, internal/codingagent/compaction/utils_test.go::TestSerializeConversation_AssistantThinkingIsOmitted, test/docs-drift/bug_ledger_test.go::TestBugLedgerTestsExist, test/docs-drift/changelog_fragments_test.go::TestChangelogFragmentsFollowTheRules, test/docs-drift/divergences_test.go::TestDivergenceLedgerEqualsItsRecordFiles, test/docs-drift/divergences_test.go::TestEveryActiveDivergenceIsInTheBundle and 1 more in docs/bug-ledger.json
  • Release note: Compaction summaries, branch summaries and bug reports omit thinking blocks (Pi 15e7bdc1, Pi #9602): replayed thinking is often not in the live context, so including it could make the summarization request larger than the context it summarizes and compaction fail on every attempt.

BF-8f88d43

On Windows, an extension's exec no longer returns no output for a command that printed, when the machine is…

  • Source: PiG's own defect
  • Symptom: On Windows, an extension's exec no longer returns no output for a command that printed, when the machine is too busy to run PiG's pipe reader within 100 ms of the command's exit.
  • Root cause: extension.ExecCommand ended the post-exit wait when the 100 ms grace of Pi's waitForChildProcess elapsed, because on every platform it told childwait that a descendant might still hold the output pipes. On Windows nothing else can answer (a pipe cannot be asked without waiting behind the reader that is blocked in reading it: PeekNamedPipe blocked behind ReadFile on windows-latest), so a reader that a busy machine had not run for the grace lost what the child wrote before it exited, as the bash tool's shell did on Unix. The bash tool's shell does not have this on Windows: it starts suspended and joins a job object, and an empty job tells Wait that no process holds the pipe.
  • User impact: Wrong output or ordering
  • Platform: Windows
  • Fixed in: Unreleased
  • Fix commits: 8f88d43fa5 (2026-10-24)
  • Tests: coding/extension/exec_drain_test.go::TestExecCommandKeepsTheOutputOfAChildThatExitedBeforeItsReadersRan
  • Release note: On Windows, an extension's exec no longer returns no output for a command that printed, when the machine is too busy to run PiG's pipe reader within 100 ms of the command's exit. The command now starts suspended and joins a job object before it can start another process, as the bash tool's shell already did, so PiG knows when no process holds the output pipes any more and reads them to the end instead of dropping what the command had written before it exited.

BF-905f3d0

A trap of the codemode module on a Worker no longer loses the output the script printed before it: the host…

  • Source: PiG's own defect
  • Symptom: A script that printed 100,000 one-character items used to trap the module on its heap and now completes (a batch of the same items took 1.06 s, against 1.56 s with the module holding them; the exchange count and the other timings are unchanged).
  • Root cause: The Go behavior backend found the module trapping at its heap cap on a script at the output limits. After the hand-written exchange JSON () the 16 MiB case passes at 64 MiB; 100,000 output items cost about 460 bytes each in the module and need the cap at 96 MiB (the whole sandbox.test.ts table, 52 behaviors, passes on the Worker host). The earlier message's sync.Pool explanation was not verified; the measured cause is the copies. exchangeSizeHint moves to the tinygo file (unused elsewhere).
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 7494507f32 (2026-10-10), 7cfe779980 (2026-10-10), 07a474224f (2026-10-11), 123e9549ea (2026-10-11), 6e1f6d5a4b (2026-10-11), 6f72a8fe03 (2026-10-11), 905f3d001f (2026-10-11), 982b1fccc4 (2026-10-11), a1ed417e3a (2026-10-11), aa9704868f (2026-10-11), aec01937f1 (2026-10-11), af95a496fb (2026-10-11), d40e75c067 (2026-10-11)
  • Tests: codemode/cloudflare/cloudflare_test.go::TestCreateInlineSandboxRunsWithTheEntrysModule, codemode/cloudflare/cloudflare_test.go::TestNewSandboxDurableObjectHasPisDefaults, codemode/cloudflare/cloudflare_test.go::TestTheObjectsLimitsCapEveryRequest, codemode/cloudflare/cloudflare_test.go::TestTheWorkerObjectHasTheSameDefaults, codemode/engine_exports_test.go::TestQJSExportsMatchTheWorkerHost, codemode/resources_linux_test.go::TestConcurrentExecutionsReturnMemoryAndDescriptors, codemode/serve_encode_test.go::TestARemoteServerThatHandsItsOutputToTheHostCountsIt, codemode/serve_encode_test.go::TestAppendExchangeCountsTheHostedOutput and 8 more in docs/bug-ledger.json
  • Release note: A trap of the codemode module on a Worker no longer loses the output the script printed before it: the host holds the output items, and the crash message is delivered behind them, as Pi delivers its buffered output with a crash. A script that printed 100,000 one-character items used to trap the module on its heap and now completes (a batch of the same items took 1.06 s, against 1.56 s with the module holding them; the exchange count and the other timings are unchanged).

BF-9088293

The Azure OpenAI base URL is parsed and serialized with the WHATWG URL parser, as Pi does: hosts are…

  • Source: PiG's own defect
  • Symptom: The Azure OpenAI base URL is parsed and serialized with the WHATWG URL parser, as Pi does: hosts are lowercased and converted from IDN form, any scheme Pi's new URL accepts is accepted (and one it rejects is rejected), and the /openai/v1 rewrite works on the normalized host and path.
  • Root cause: The Go function required '://' and a host, which Pi's new URL does not. It now parses with nodeurl.ParseURL, tests the Azure host on the normalized hostname, rewrites through WithPathname and WithSearch(""), and serializes with the new URL.Href.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 15a5c0b670 (2026-10-09), 2c04c1445a (2026-10-09), 31f4517fbe (2026-10-09), 7c4d874438 (2026-10-09), 90882933f6 (2026-10-09), c4d51d5c1a (2026-10-09), d80c1cd150 (2026-10-09)
  • Tests: ai/authorization_input_oracle_test.go::TestAuthorizationInputParsersMatchPi, ai/azure_config_oracle_test.go::TestAzureBaseURLNormalizationMatchesPi, ai/codex_account_id_oracle_test.go::TestCodexAccountIDMatchesPi, ai/githubcopilot_oracle_test.go::TestGitHubCopilotHelpersMatchPi, ai/radius_urls_oracle_test.go::TestRadiusURLsMatchPiOnSeededProbes, ai/trusted_url_oracle_test.go::TestTrustedVerificationURLsMatchPi, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode, internal/nodeurl/url_oracle_test.go::TestParseMatchesNodeURL
  • Release note: The Azure OpenAI base URL is parsed and serialized with the WHATWG URL parser, as Pi does: hosts are lowercased and converted from IDN form, any scheme Pi's new URL accepts is accepted (and one it rejects is rejected), and the /openai/v1 rewrite works on the normalized host and path.

BF-909581e

The terminal no longer stays in mouse-tracking, focus-reporting, Kitty keyboard, bracketed-paste or…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: The terminal no longer stays in mouse-tracking, focus-reporting, Kitty keyboard, bracketed-paste or alternate-screen mode when pig exits through a path that skips the normal teardown (a direct exit, an emergency exit, an off-loop crash, a panic unwinding through Main).
  • Root cause: Port Pi (#11170): Pi's process 'exit' handler writes the mode resets synchronously when stop() did not run. Go has no exit hook, so tui.RestoreTerminalOnExit is that handler and every exit path reachable while a TUI runs (coding/cli exitProcess; emergencyTerminalExit, handleInterruptSignal, uncaughtOffLoop) calls it before os.Exit. Order matches TuiAltScreen: end synchronized output, mouse off, autowrap on, cursor, progress clear, bracketed paste off, Kitty pop, modifyOtherKeys off, then alternate-screen exit and cursor show; cooked mode is restored as libuv does. Registered terminals and alt screens leave on normal stop, so nothing is written twice.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 909581ef0b (2026-10-20)
  • Tests: coding/cli/exit_restore_paths_test.go::TestEveryExitPathReachableWhileATUIRunsRestoresTheTerminal, tui/terminal_exit_restore_test.go::TestExitRestoreFixture, tui/terminal_exit_restore_test.go::TestRestoreTerminalOnExitMainScreenWritesOnceAndRestoresCookedMode, tui/terminal_exit_restore_test.go::TestTerminalRestoreOnProcessExit, tui/terminal_exit_restore_test.go::TestTerminalRestoreOnProcessExitDoesNotRepeatAfterStop
  • Release note: The terminal no longer stays in mouse-tracking, focus-reporting, Kitty keyboard, bracketed-paste or alternate-screen mode when pig exits through a path that skips the normal teardown (a direct exit, an emergency exit, an off-loop crash, a panic unwinding through Main). The modes are restored in Pi's order, once, and not again after a normal stop (Pi 9df819de, #11170).

BF-909a2e2

MCP OAuth metadata documents with a member whose name differs from a modeled one only in case (for example…

  • Source: PiG's own defect
  • Symptom: MCP OAuth metadata documents with a member whose name differs from a modeled one only in case (for example Scopes_Supported) are no longer rejected; the member is kept as an unmodeled member, as in Pi.
  • Root cause: Pi packages/mcp/src/oauth/types.ts:198-209 parseClientInformation spreads the whole response, and flow.ts:125-138 selectClientAuthMethod reads token_endpoint_auth_method from it. ParseClientInformation decoded the whole struct with encoding/json, which fails on the first ill-typed member and lost every typed member with it (a registration with "contacts":"bad" lost token_endpoint_auth_method, so the token request used client_secret_basic instead of the registered client_secret_post), and matched members without regard to case. Each member now decodes on its own under its exact name.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5654e71185 (2026-10-05), 909a2e20e6 (2026-10-05)
  • Tests: mcp/oauth/client_information_test.go::TestParseClientInformationKeepsTypedMembersBesideAnIllTypedOne, mcp/oauth/client_information_test.go::TestParseClientInformationReadsExactMemberNames, mcp/oauth/upstream_oracle_test.go::TestBuildAuthorizationServerDiscoveryURLsMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseAuthorizationServerMetadataMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseClientInformationMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseOAuthTokensMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseProtectedResourceMetadataMatchesPi, mcp/oauth/upstream_oracle_test.go::TestParseWWWAuthenticateMatchesPi and 3 more in docs/bug-ledger.json
  • Release note: MCP OAuth metadata documents with a member whose name differs from a modeled one only in case (for example Scopes_Supported) are no longer rejected; the member is kept as an unmodeled member, as in Pi.

BF-90c1f0f

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: TestBuiltinToolCardsMatchPi and TestBuiltinToolCardsRandomMatchPi compared the bash card's Elapsed/Took row, which both PiG (shellRenderCall and shellRenderResult) and Pi (renderers/bash.ts createShellRenderers) read from the wall clock between the start event and the result render. Under CPU load either side could cross a 50 ms rounding step the other did not, so the row flipped between 0.0s and 0.1s (or more).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 90c1f0f52c (2026-10-13)
  • Tests: internal/codingagent/builtin_tool_cards_oracle_test.go::TestBuiltinToolCardsMatchPi, internal/codingagent/builtin_tool_cards_oracle_test.go::TestBuiltinToolCardsRandomMatchPi

BF-9101bc8

Bedrock model matching follows Pi: separator runs in a model id or display name become a single - (so "Opus…

  • Source: PiG's own defect
  • Symptom: Bedrock model matching follows Pi: separator runs in a model id or display name become a single - (so "Opus 4.6" and "sonnet...5" match the adaptive-thinking and caching families), no-break spaces count as separators, and a model counts as Anthropic Claude only when its id carries an anthropic.claude or anthropic/claude prefix or its display name mentions claude.
  • Root cause: getModelMatchCandidates, supportsAdaptiveThinking, supportsNativeXhighEffort, supportsThinkingBlockBinding, isAnthropicClaudeModel and supportsPromptCaching are cut from Pi's source (testdata/bedrockmodel_match.mjs) and run on 4500 seeded model id/name pairs. 3035 differed (red before). Causes: (1) bedrockModelMatchCandidates trimmed values, skipped empty ones and de-duplicated, and replaced each of ' .:' separately where Pi rewrites every run of \s, '_', '.', ':' to one '-', so 'Opus 4.6', 'sonnet...5' and 'Haiku::5' never reached the 'opus-4-6'/'sonnet-5'/'haiku-5' families and no-break spaces or NEL were not separators; (2) isAnthropicClaudeBedrockModel tested 'claude' on the model…
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 7c4d874438 (2026-10-09), 9101bc880b (2026-10-09)
  • Tests: ai/bedrock_model_match_oracle_test.go::TestBedrockModelMatchingMatchesPi, ai/githubcopilot_oracle_test.go::TestGitHubCopilotHelpersMatchPi, ai/overflow_oracle_test.go::TestContextOverflowMatchesPiOnSeededMessages, internal/evals/plan_oracle_test.go::TestPlanMatchesPiOnSeededProbes
  • Release note: Bedrock model matching follows Pi: separator runs in a model id or display name become a single - (so "Opus 4.6" and "sonnet...5" match the adaptive-thinking and caching families), no-break spaces count as separators, and a model counts as Anthropic Claude only when its id carries an anthropic.claude or anthropic/claude prefix or its display name mentions claude.

BF-918f21c

formatThousands (tui/compaction_summary.go, compaction-summary-message.ts:40…

  • Source: PiG's own defect
  • Symptom: formatThousands (tui/compaction_summary.go, compaction-summary-message.ts:40 tokensBefore.toLocaleString()) wrote a negative count as -,123 (the separator rule counted the minus sign as a digit); it now keeps the sign out of the grouping.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 918f21c5e8 (2026-10-11)
  • Tests: tui/compaction_summary_thousands_test.go::TestFormatThousandsMatchesPi
  • Release note: formatThousands (tui/compaction_summary.go, compaction-summary-message.ts:40 tokensBefore.toLocaleString()) wrote a negative count as -,123 (the separator rule counted the minus sign as a digit); it now keeps the sign out of the grouping. New live-Pi test TestFormatThousandsMatchesPi (28 values, both signs, up to ±2^53; red with the sign handling off or the grouping shifted). A negative tokensBefore only comes from a hand-edited session file.

BF-919575a

A JSON syntax error reports what V8's JSON.parse reports in two cases it got wrong: a number or string…

  • Source: PiG's own defect
  • Symptom: A JSON syntax error reports what V8's JSON.parse reports in two cases it got wrong: a number or string where a true, false or null literal breaks off (t1, fals") is "Unexpected number" or "Unexpected string in JSON at position N", and a backslash followed by a character above U+00FF in a string is "Unexpected token" rather than "Bad escaped character".
  • Root cause: redactJsonValue is JSON.parse(JSON.stringify(value)); RedactBugReportJSON kept the number text it decoded with UseNumber, so -0, 1e21 and a 30-digit integer reached the report as -0, 1e21 and the digits where Pi writes 0, 1e+21 and 1.+29. TestBugReportRedactionMatchesPi compares 1500 URLs (schemes, nested schemes, userinfo, IPv6 and IDN hosts, dot segments, percent-encoding, repeated and mixed-case query keys, fragments, non-URLs) and 800 JSON values (sensitive keys in snake, kebab and camel case, nested containers, null members) with Pi's redactUrl and redactJsonValue; the URLs all matched, the numbers did not (red first). Numbers are now canonicalised through jsonstringify.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 52573667c1 (2026-10-09), 579f07df0e (2026-10-09), d07038cb99 (2026-10-09), 919575a79c (2026-10-10)
  • Tests: ai/classifier_answers_oracle_test.go::TestClassifierAnswersMatchPi, ai/system_one_shared_test.go::TestSystemOneSharedFailureMessages, internal/codingagent/bug_report_redact_oracle_test.go::TestBugReportRedactionMatchesPi, internal/codingagent/bug_report_redact_oracle_test.go::TestBugReportRedactionNormalizesNumbersLikeJSONStringify, internal/codingagent/session_file_bytes_oracle_test.go::TestSessionFileBytesMatchPi, internal/jsonparse/json_parse_oracle_test.go::TestValidateMatchesNodeJSONParse, internal/sessionentry/entry_test.go::TestBranchSummaryEntryKeepsTheMemberOrderOfDetails
  • Release note: A JSON syntax error reports what V8's JSON.parse reports in two cases it got wrong: a number or string where a true, false or null literal breaks off (t1, fals") is "Unexpected number" or "Unexpected string in JSON at position N", and a backslash followed by a character above U+00FF in a string is "Unexpected token" rather than "Bad escaped character".

BF-91e912a

Two PiG versions that share a config root (two installed versions, or PiG beside a Piglet Binary) no longer…

  • Source: PiG's own defect
  • Symptom: Two PiG versions that share a config root (two installed versions, or PiG beside a Piglet Binary) no longer restage each other's extension SDKs at every start or delete each other's extension builds.
  • Root cause: Two PiG builds that share a config root (two installed versions, or stock PiG beside a Piglet Binary) staged into the same state/pigsdk/sdk, sdk-py and sdk-rs, so every switch restaged all three and pruned every extension build made against the other build's SDK, and a Python extension of one build could import the other build's SDK.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), e8cc072521 (2026-10-08), 395c46cbf2 (2026-10-09), 852652e3ae (2026-10-09), da99854d1b (2026-10-09), 91e912aa90 (2026-10-10)
  • Tests: chord/delta/own_key_order_test.go::TestDiffRevisionsVisitsArrayIndexKeysFirstInNumericOrder, coding/cli/piglet_verify_cache_startup_test.go::TestSignedPigletBinaryRunsWhenItsVerifyCacheCannotBeWritten, coding/cli/readonly_config_root_binary_test.go::TestReadOnlyConfigRootStartsQuietlyAndNamesOnlyDocsThatExist, coding/cli/startup_sdk_test.go::TestStartupSDKStageIsQuietOnAReadOnlyConfigRoot, coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, coding/extension/pigsdk/pigsdk_stage_test.go::TestStagingLeavesAnotherBuildsStagingTrees, coding/extension/pigsdk/pigsdk_version_share_test.go::TestStagingEvictsLeastRecentlyUsedSDKsAndTheirBuilds, coding/extension/pigsdk/pigsdk_version_share_test.go::TestStagingKeepsAnotherBuildsSDKAndItsBuilds and 12 more in docs/bug-ledger.json
  • Release note: Two PiG versions that share a config root (two installed versions, or PiG beside a Piglet Binary) no longer restage each other's extension SDKs at every start or delete each other's extension builds. Each version stages its SDKs into its own directory under state/pigsdk, named for the SDK's content hash, and reads only that directory, so a Python extension also always runs against its own version's SDK. After staging, PiG keeps the 4 most recently used SDK directories per language and drops only the extension builds whose SDK is gone. pig reload --sdk-path prints the running version's directory, and pig diagnose shows it.

BF-91f9946

OpenRouter image generation reads the chat completions body as Pi does, checked against the pinned Pi in a…

  • Source: PiG's own defect
  • Symptom: OpenRouter image generation reads the chat completions body as Pi does, checked against the pinned Pi in a new oracle test: a body with no choices (OpenRouter sometimes answers 200 with an error object) fails with Pi's TypeError instead of returning an empty success, content, image and usage members of other types are skipped or fail with the same message, and an unparseable body reports the JSON syntax error.
  • Root cause: TestOpenRouterImagesEdgeResponsesMatchPi (new, 150 bodies, with its own Pi driver) showed that a 200 body without choices returned an empty success where Pi throws Cannot read properties of undefined (reading '0'), and that content, image and usage members of another type failed the decode where Pi skips them or throws a specific TypeError. The body is now walked untyped in Pi's order, so the id, usage and text read before a failing member stay on the result.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 91f99469d5 (2026-10-11)
  • Tests: ai/openrouter_images_edge_oracle_test.go::TestOpenRouterImagesEdgeResponsesMatchPi
  • Release note: OpenRouter image generation reads the chat completions body as Pi does, checked against the pinned Pi in a new oracle test: a body with no choices (OpenRouter sometimes answers 200 with an error object) fails with Pi's TypeError instead of returning an empty success, content, image and usage members of other types are skipped or fail with the same message, and an unparseable body reports the JSON syntax error.

BF-928e15c

A listener that fails while agent_start is dispatched now ends the run as in Pi (failure messages, no…

  • Source: PiG's own defect
  • Symptom: A listener that fails while agent_start is dispatched now ends the run as in Pi (failure messages, no turn_start or prompt), and the failure message carries the model's api (agent.ts handleRunFailure).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A listener that fails while agent_start is dispatched now ends the run as in Pi (failure messages, no turn_start or prompt), and the failure message carries the model's api (agent.ts handleRunFailure).

BF-928e15c-10

Constrained-sampling errors quote tool and property names as Pi does (raw text between double quotes, not…

  • Source: PiG's own defect
  • Symptom: Constrained-sampling errors quote tool and property names as Pi does (raw text between double quotes, not Go-escaped), and the strict JSON-schema conversion treats an explicit null properties/items and a type array holding non-string elements like Pi when deciding whether a union variant is an object or array.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Constrained-sampling errors quote tool and property names as Pi does (raw text between double quotes, not Go-escaped), and the strict JSON-schema conversion treats an explicit null properties/items and a type array holding non-string elements like Pi when deciding whether a union variant is an object or array.

BF-928e15c-11

GitHub Copilot model-catalog fetches now report a failed response as Pi does (`<status> <reason>: <untrimmed…

  • Source: PiG's own defect
  • Symptom: GitHub Copilot model-catalog fetches now report a failed response as Pi does (<status> <reason>: <untrimmed body>), report a malformed catalog as Invalid Copilot models response, and fail on a body that cannot be read, while a policy POST that is not rate limited no longer fails because its body cannot be read.
  • Root cause: GitHub Copilot model-catalog fetches now report a failed response as Pi does (<status> <reason>: <untrimmed body>), report a malformed catalog as Invalid Copilot models response, and fail on a body that cannot be read, while a policy POST that is not rate limited no longer fails because its body cannot be read.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: coding/extension/host/subprocess/runtime_node_sdk_bundle_test.go::TestNodeLibraryBundlesRegenerateExactly, coding/extension/host/subprocess/runtime_node_sdk_bundle_test.go::TestNodeSDKBundleRegeneratesExactly
  • Release note: GitHub Copilot model-catalog fetches now report a failed response as Pi does (<status> <reason>: <untrimmed body>), report a malformed catalog as Invalid Copilot models response, and fail on a body that cannot be read, while a policy POST that is not rate limited no longer fails because its body cannot be read.

BF-928e15c-12

GitHub Copilot login now reports failed HTTP responses as Pi does (<status> <reason>: <body>, not `HTTP…

  • Source: PiG's own defect
  • Symptom: GitHub Copilot login now reports failed HTTP responses as Pi does (<status> <reason>: <body>, not HTTP <status>: <trimmed body>), reads a device-flow error that is an empty string as a failed device flow, reads numbers past the double range as JavaScript does, and reads ill-formed UTF-8 or a truncated body as fetch does.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: GitHub Copilot login now reports failed HTTP responses as Pi does (<status> <reason>: <body>, not HTTP <status>: <trimmed body>), reads a device-flow error that is an empty string as a failed device flow, reads numbers past the double range as JavaScript does, and reads ill-formed UTF-8 or a truncated body as fetch does.

BF-928e15c-13

GitHub Copilot, ChatGPT and OpenAI Codex logins report what Pi reports when a server's response cannot be…

  • Source: PiG's own defect
  • Symptom: GitHub Copilot, ChatGPT and OpenAI Codex logins report what Pi reports when a server's response cannot be used: JavaScript's SyntaxError text for a body that is not JSON, terminated for a body that is cut off, the server's own reason phrase for an empty error body, and a Copilot device-flow error_description of any JSON type printed as JavaScript prints it (for example 5 or [object Object]).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: GitHub Copilot, ChatGPT and OpenAI Codex logins report what Pi reports when a server's response cannot be used: JavaScript's SyntaxError text for a body that is not JSON, terminated for a body that is cut off, the server's own reason phrase for an empty error body, and a Copilot device-flow error_description of any JSON type printed as JavaScript prints it (for example 5 or [object Object]).

BF-928e15c-14

GitHub Copilot login sends the browser to the verification URL exactly as Pi does (new URL(...).href: the…

  • Source: PiG's own defect
  • Symptom: GitHub Copilot login sends the browser to the verification URL exactly as Pi does (new URL(...).href: the default port dropped), and a device-flow failure appends a non-string error_description as Pi prints it.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: GitHub Copilot login sends the browser to the verification URL exactly as Pi does (new URL(...).href: the default port dropped), and a device-flow failure appends a non-string error_description as Pi prints it.

BF-928e15c-15

Device-code login polling now does its time arithmetic in floating-point milliseconds as Pi does, so a NaN…

  • Source: PiG's own defect
  • Symptom: Device-code login polling now does its time arithmetic in floating-point milliseconds as Pi does, so a NaN, infinite or very large interval or expiry waits as Node's setTimeout would instead of overflowing a Go duration.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Device-code login polling now does its time arithmetic in floating-point milliseconds as Pi does, so a NaN, infinite or very large interval or expiry waits as Node's setTimeout would instead of overflowing a Go duration.

BF-928e15c-16

A durable directory reader now refuses a maxEntries above Number.MAX_SAFE_INTEGER with an invalid…

  • Source: PiG's own defect
  • Symptom: A durable directory reader now refuses a maxEntries above Number.MAX_SAFE_INTEGER with an invalid FileError, as Pi's NodeDirReader.next does.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A durable directory reader now refuses a maxEntries above Number.MAX_SAFE_INTEGER with an invalid FileError, as Pi's NodeDirReader.next does.

BF-928e15c-17

Durable entries keep a present empty edits list ("edits":[]) when they are appended, stored and read…

  • Source: PiG's own defect
  • Symptom: Durable entries keep a present empty edits list ("edits":[]) when they are appended, stored and read back, as Pi does.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Durable entries keep a present empty edits list ("edits":[]) when they are appended, stored and read back, as Pi does. Pig previously dropped it.

BF-928e15c-18

Durable: a JSONL store whose task or submission record carries a status outside the closed set (a damaged…

  • Source: PiG's own defect
  • Symptom: Durable: a JSONL store whose task or submission record carries a status outside the closed set (a damaged file) now fails to open with "Invalid committed state at sequence N", as Pi's does, instead of loading the record under an unknown status.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Durable: a JSONL store whose task or submission record carries a status outside the closed set (a damaged file) now fails to open with "Invalid committed state at sequence N", as Pi's does, instead of loading the record under an unknown status.

BF-928e15c-19

Fixed Editor.SetText keeping the previous scroll offset: as in Pi, replacing the text puts the view back at…

  • Source: PiG's own defect
  • Symptom: Fixed Editor.SetText keeping the previous scroll offset: as in Pi, replacing the text puts the view back at the top, so a long editor that was scrolled no longer renders with the wrong rows and scroll indicators after setText.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Fixed Editor.SetText keeping the previous scroll offset: as in Pi, replacing the text puts the view back at the top, so a long editor that was scrolled no longer renders with the wrong rows and scroll indicators after setText.

BF-928e15c-2

A stored models catalog whose contextWindow or maxTokens is written as 128000.0 or 1.28E5 loads like…

  • Source: PiG's own defect
  • Symptom: A stored models catalog whose contextWindow or maxTokens is written as 128000.0 or 1.28E5 loads like Pi's JSON.parse reads it, instead of failing to decode the model.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A stored models catalog whose contextWindow or maxTokens is written as 128000.0 or 1.28E5 loads like Pi's JSON.parse reads it, instead of failing to decode the model.

BF-928e15c-20

Fixed frontmatter parsing when the opening --- is followed by a multi-byte character (for example U+0085 or…

  • Source: PiG's own defect
  • Symptom: Fixed frontmatter parsing when the opening --- is followed by a multi-byte character (for example U+0085 or U+2028) or an astral character instead of a line break: the header now starts after one UTF-16 code unit, as Pi slices it, rather than at byte 4.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Fixed frontmatter parsing when the opening --- is followed by a multi-byte character (for example U+0085 or U+2028) or an astral character instead of a line break: the header now starts after one UTF-16 code unit, as Pi slices it, rather than at byte 4.

BF-928e15c-21

Google streams replace an ill-formed UTF-8 sequence with one U+FFFD per maximal subpart (the WHATWG…

  • Source: PiG's own defect
  • Symptom: Google streams replace an ill-formed UTF-8 sequence with one U+FFFD per maximal subpart (the WHATWG TextDecoder rule) and drop a leading byte order mark, as Pi's genai SDK does; the decoder used before emitted one replacement character per bad byte.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Google streams replace an ill-formed UTF-8 sequence with one U+FFFD per maximal subpart (the WHATWG TextDecoder rule) and drop a leading byte order mark, as Pi's genai SDK does; the decoder used before emitted one replacement character per bad byte.

BF-928e15c-22

Case-insensitive matching of tool names (Claude Code tool casing), the anthropic-beta, reserved Bedrock and…

  • Source: PiG's own defect
  • Symptom: Case-insensitive matching of tool names (Claude Code tool casing), the anthropic-beta, reserved Bedrock and OpenCode session header names, Bedrock GovCloud regions and model ids, the Codex usage-limit error codes, the merging of model and caller header names and the Bedrock data-retention hint now lower-case with JavaScript rules, so İ is no longer treated as i.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Case-insensitive matching of tool names (Claude Code tool casing), the anthropic-beta, reserved Bedrock and OpenCode session header names, Bedrock GovCloud regions and model ids, the Codex usage-limit error codes, the merging of model and caller header names and the Bedrock data-retention hint now lower-case with JavaScript rules, so İ is no longer treated as i.

BF-928e15c-23

Kimi Code OAuth login and refresh now read the server's response the way Pi does: a body that is not a JSON…

  • Source: PiG's own defect
  • Symptom: Kimi Code OAuth login and refresh now read the server's response the way Pi does: a body that is not a JSON object or array counts as absent, a field of an unexpected JSON type is reported as invalid or missing (instead of a Go decode error), an error body is quoted as JSON.stringify writes it (member order, number and string forms), error descriptions keep an empty value, slow_down carries the server's interval, a leading byte order mark is ignored, the failure texts match Pi's (Invalid Kimi Code device authorization response, Kimi Code token refresh response missing fields: …, the status and body of a 5xx during login), and a refresh with an empty stored token sends the request as Pi does.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Kimi Code OAuth login and refresh now read the server's response the way Pi does: a body that is not a JSON object or array counts as absent, a field of an unexpected JSON type is reported as invalid or missing (instead of a Go decode error), an error body is quoted as JSON.stringify writes it (member order, number and string forms), error descriptions keep an empty value, slow_down carries the server's interval, a leading byte order mark is ignored, the failure texts match Pi's (Invalid Kimi Code device authorization response, Kimi Code token refresh response missing fields: …, the status and body of a 5xx during login), and a refresh with an empty stored token sends the request as Pi does.

BF-928e15c-24

Kimi Code and xAI OAuth decode a response body as fetch does: ill-formed UTF-8 becomes one U+FFFD per maximal…

  • Source: PiG's own defect
  • Symptom: Kimi Code and xAI OAuth decode a response body as fetch does: ill-formed UTF-8 becomes one U+FFFD per maximal ill-formed subpart (not one per byte, nor one per run), in quoted error bodies and in fields such as error_description; a body that fails to read (a connection closed mid-body) is never parsed, so a truncated token response is no longer accepted.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Kimi Code and xAI OAuth decode a response body as fetch does: ill-formed UTF-8 becomes one U+FFFD per maximal ill-formed subpart (not one per byte, nor one per run), in quoted error bodies and in fields such as error_description; a body that fails to read (a connection closed mid-body) is never parsed, so a truncated token response is no longer accepted.

BF-928e15c-25

A tool or command that throws an Error with an empty message reports an empty message in the Node runtime…

  • Source: PiG's own defect
  • Symptom: A tool or command that throws an Error with an empty message reports an empty message in the Node runtime, as upstream does (err instanceof Error ? err.message : String(err)); it reported Error.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A tool or command that throws an Error with an empty message reports an empty message in the Node runtime, as upstream does (err instanceof Error ? err.message : String(err)); it reported Error.

BF-928e15c-26

The Rust SDK parses every JSON number as the nearest double, as JSON.parse does; it could land one unit in…

  • Source: PiG's own defect
  • Symptom: The Rust SDK parses every JSON number as the nearest double, as JSON.parse does; it could land one unit in the last place away (123456789012345680000 was read as 123456789012345670000).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: The Rust SDK parses every JSON number as the nearest double, as JSON.parse does; it could land one unit in the last place away (123456789012345680000 was read as 123456789012345670000).

BF-928e15c-27

The Go SDK writes NaN and infinities as null (and negative zero as 0) in the frames it sends, as…

  • Source: PiG's own defect
  • Symptom: The Go SDK writes NaN and infinities as null (and negative zero as 0) in the frames it sends, as JSON.stringify and the Node, Rust and Python SDKs do; a Go tool that returned one used to fail with an encoding error.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: The Go SDK writes NaN and infinities as null (and negative zero as 0) in the frames it sends, as JSON.stringify and the Node, Rust and Python SDKs do; a Go tool that returned one used to fail with an encoding error.

BF-928e15c-28

The Node SDK's messageRole now returns "" for a message whose role is not a string, as the Go, Rust and…

  • Source: PiG's own defect
  • Symptom: The Node SDK's messageRole now returns "" for a message whose role is not a string, as the Go, Rust and Python SDKs do (it returned the value, e.g. 5).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: The Node SDK's messageRole now returns "" for a message whose role is not a string, as the Go, Rust and Python SDKs do (it returned the value, e.g. 5).

BF-928e15c-29

The Go and Rust SDKs now answer a tool result past the 128 MiB frame limit with a frame too large error, as…

  • Source: PiG's own defect
  • Symptom: The Go and Rust SDKs now answer a tool result past the 128 MiB frame limit with a frame too large error, as the Node and Python SDKs do; the call used to hang until the run was aborted.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: The Go and Rust SDKs now answer a tool result past the 128 MiB frame limit with a frame too large error, as the Node and Python SDKs do; the call used to hang until the run was aborted.

BF-928e15c-3

Provider HTTP error bodies are decoded like response.text(): ill-formed UTF-8 becomes one U+FFFD per…

  • Source: PiG's own defect
  • Symptom: Provider HTTP error bodies are decoded like response.text(): ill-formed UTF-8 becomes one U+FFFD per maximal subpart and a leading byte order mark is dropped, so an error message no longer carries raw invalid bytes (Anthropic, OpenAI completions and Responses, Azure, Google, Mistral, pi-messages, Codex).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Provider HTTP error bodies are decoded like response.text(): ill-formed UTF-8 becomes one U+FFFD per maximal subpart and a leading byte order mark is dropped, so an error message no longer carries raw invalid bytes (Anthropic, OpenAI completions and Responses, Azure, Google, Mistral, pi-messages, Codex).

BF-928e15c-30

The Python SDK sends NaN and infinities as null (as JSON.stringify does) instead of the bare words…

  • Source: PiG's own defect
  • Symptom: The Python SDK sends NaN and infinities as null (as JSON.stringify does) instead of the bare words NaN/Infinity, which the host refused and which ended the extension's connection.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: The Python SDK sends NaN and infinities as null (as JSON.stringify does) instead of the bare words NaN/Infinity, which the host refused and which ended the extension's connection.

BF-928e15c-31

The Rust SDK decodes tool arguments, notifications and host-call results nested past 128 levels (up to 4096)…

  • Source: PiG's own defect
  • Symptom: The Rust SDK decodes tool arguments, notifications and host-call results nested past 128 levels (up to 4096) instead of dropping its host connection.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: The Rust SDK decodes tool arguments, notifications and host-call results nested past 128 levels (up to 4096) instead of dropping its host connection.

BF-928e15c-32

The Rust SDK no longer drops its host connection when a frame (a tool argument, event payload or object key)…

  • Source: PiG's own defect
  • Symptom: The Rust SDK no longer drops its host connection when a frame (a tool argument, event payload or object key) holds an unpaired UTF-16 surrogate; it decodes the unit as U+FFFD, as Go does for a string.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: The Rust SDK no longer drops its host connection when a frame (a tool argument, event payload or object key) holds an unpaired UTF-16 surrogate; it decodes the unit as U+FFFD, as Go does for a string.

BF-928e15c-33

The login dialog renders its one input at every prompt position until a submit replaces them with the…

  • Source: PiG's own defect
  • Symptom: The login dialog renders its one input at every prompt position until a submit replaces them with the submitted text, and keeps it after a cancel, as Pi's LoginDialogComponent does; an unanswered earlier prompt no longer renders blank.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: The login dialog renders its one input at every prompt position until a submit replaces them with the submitted text, and keeps it after a cancel, as Pi's LoginDialogComponent does; an unanswered earlier prompt no longer renders blank.

BF-928e15c-34

Markdown: a rule written with the list's own marker (* * *) after a blank line ends the list with its blank…

  • Source: PiG's own defect
  • Symptom: Markdown: a rule written with the list's own marker (* * *) after a blank line ends the list with its blank row, and a line after a three-level nested list is a lazy line only where each enclosing item allows it, as in Pi.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Markdown: a rule written with the list's own marker (* * *) after a blank line ends the list with its blank row, and a line after a three-level nested list is a lazy line only where each enclosing item allows it, as in Pi.

BF-928e15c-35

Markdown: a list item line that is a rule (- a, a blank line, - - -; or - * * *) renders as a rule…

  • Source: PiG's own defect
  • Symptom: Markdown: a list item line that is a rule (- a, a blank line, - - -; or - * * *) renders as a rule inside the item instead of hanging the renderer.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Markdown: a list item line that is a rule (- a, a blank line, - - -; or - * * *) renders as a rule inside the item instead of hanging the renderer.

BF-928e15c-36

Markdown: an indented line that a table delimiter row (---) follows joins the paragraph before it with its…

  • Source: PiG's own defect
  • Symptom: Markdown: an indented line that a table delimiter row (---) follows joins the paragraph before it with its indent removed, instead of rendering as a separate code block, as in Pi.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Markdown: an indented line that a table delimiter row (---) follows joins the paragraph before it with its indent removed, instead of rendering as a separate code block, as in Pi.

BF-928e15c-37

Markdown: a text that ends in one line of spaces after an indented code block, an HTML block or a table no…

  • Source: PiG's own defect
  • Symptom: Markdown: a text that ends in one line of spaces after an indented code block, an HTML block or a table no longer renders the extra empty row, a fence right after an HTML comment gets no spacing row, and an indented line after an empty quoted line starts a code block in the quote, as in Pi.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Markdown: a text that ends in one line of spaces after an indented code block, an HTML block or a table no longer renders the extra empty row, a fence right after an HTML comment gets no spacing row, and an indented line after an empty quoted line starts a code block in the quote, as in Pi.

BF-928e15c-38

Markdown no longer adds a blank row after a quote or block when only a repeated link definition follows it…

  • Source: PiG's own defect
  • Symptom: Markdown no longer adds a blank row after a quote or block when only a repeated link definition follows it, and a reference before its definition no longer changes that.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Markdown no longer adds a blank row after a quote or block when only a repeated link definition follows it, and a reference before its definition no longer changes that.

BF-928e15c-39

Markdown: a line that continues a nested list item's paragraph (- a, - b, x) renders at the nested…

  • Source: PiG's own defect
  • Symptom: Markdown: a line that continues a nested list item's paragraph (- a, - b, x) renders at the nested item's text column, and a bullet with an empty first line followed by a nested list keeps its empty bullet row, as in Pi.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Markdown: a line that continues a nested list item's paragraph (- a, - b, x) renders at the nested item's text column, and a bullet with an empty first line followed by a nested list keeps its empty bullet row, as in Pi.

BF-928e15c-4

A Gemini (or Bedrock/native) response streamed in many small chunks no longer hangs the stream…

  • Source: PiG's own defect
  • Symptom: A Gemini (or Bedrock/native) response streamed in many small chunks no longer hangs the stream intermittently: the body reader goroutine no longer runs the completion that starts the next read.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A Gemini (or Bedrock/native) response streamed in many small chunks no longer hangs the stream intermittently: the body reader goroutine no longer runs the completion that starts the next read.

BF-928e15c-40

Markdown no longer adds an empty row after a table that is too narrow to draw, when the table is inside a…

  • Source: PiG's own defect
  • Symptom: Markdown no longer adds an empty row after a table that is too narrow to draw, when the table is inside a list item and another block follows it.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Markdown no longer adds an empty row after a table that is too narrow to draw, when the table is inside a list item and another block follows it.

BF-928e15c-41

Markdown: list items whose markers are indented by different amounts (0 to 3 spaces) form one list, so a…

  • Source: PiG's own defect
  • Symptom: Markdown: list items whose markers are indented by different amounts (0 to 3 spaces) form one list, so a loose list keeps its empty rows between them, as in Pi.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Markdown: list items whose markers are indented by different amounts (0 to 3 spaces) form one list, so a loose list keeps its empty rows between them, as in Pi.

BF-928e15c-42

Markdown rendering shows a horizontal rule (- - -, * * *) inside a nested list item as a rule, as Pi…

  • Source: PiG's own defect
  • Symptom: Markdown rendering shows a horizontal rule (- - -, * * *) inside a nested list item as a rule, as Pi does, instead of an empty bullet.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Markdown rendering shows a horizontal rule (- - -, * * *) inside a nested list item as a rule, as Pi does, instead of an empty bullet.

BF-928e15c-43

Markdown nested lists treat a less-indented continuation line the way Pi does: a line indented to a nested…

  • Source: PiG's own defect
  • Symptom: Markdown nested lists treat a less-indented continuation line the way Pi does: a line indented to a nested item's content relative to its parent stays in that item, and a --- | --- line no longer ends the item.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Markdown nested lists treat a less-indented continuation line the way Pi does: a line indented to a nested item's content relative to its parent stays in that item, and a --- | --- line no longer ends the item.

BF-928e15c-44

Markdown: a blockquote whose last quoted line holds only spaces or tabs (> q, >, x) now renders the…

  • Source: PiG's own defect
  • Symptom: Markdown: a blockquote whose last quoted line holds only spaces or tabs (> q, >, x) now renders the lazy line in the same paragraph, with the whitespace line kept as an empty row, as Pi does.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Markdown: a blockquote whose last quoted line holds only spaces or tabs (> q, >, x) now renders the lazy line in the same paragraph, with the whitespace line kept as an empty row, as Pi does.

BF-928e15c-45

Markdown task lists whose later item is a heading (- [ ] b over ===) keep and strip checkbox markers on…

  • Source: PiG's own defect
  • Symptom: Markdown task lists whose later item is a heading (- [ ] b over ===) keep and strip checkbox markers on the same items as Pi.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Markdown task lists whose later item is a heading (- [ ] b over ===) keep and strip checkbox markers on the same items as Pi.

BF-928e15c-46

MCP HTTP servers and the OAuth requests of a sign-in now report a connection that fails before any response…

  • Source: PiG's own defect
  • Symptom: MCP HTTP servers and the OAuth requests of a sign-in now report a connection that fails before any response as fetch failed, as Pi does (Node fetch rejection), instead of Go's Post "<url>": dial tcp ... text in /mcp, pi mcp list and pi mcp login.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: MCP HTTP servers and the OAuth requests of a sign-in now report a connection that fails before any response as fetch failed, as Pi does (Node fetch rejection), instead of Go's Post "<url>": dial tcp ... text in /mcp, pi mcp list and pi mcp login.

BF-928e15c-47

Meta OAuth login now reads the server's responses as Pi does: an invalid device-authorization response is…

  • Source: PiG's own defect
  • Symptom: Meta OAuth login now reads the server's responses as Pi does: an invalid device-authorization response is quoted as JSON.stringify writes it (an array body as the array, members in the server's order, numbers as JavaScript prints them), a number past the double range is an unusable interval instead of voiding the whole body, a leading byte order mark or ill-formed UTF-8 is read as fetch does, and a body that fails to read counts as null.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Meta OAuth login now reads the server's responses as Pi does: an invalid device-authorization response is quoted as JSON.stringify writes it (an array body as the array, members in the server's order, numbers as JavaScript prints them), a number past the double range is an unusable interval instead of voiding the whole body, a leading byte order mark or ill-formed UTF-8 is read as fetch does, and a body that fails to read counts as null.

BF-928e15c-48

Case-insensitive glob matching compares as a JavaScript regular expression does: İ no longer matches i…

  • Source: PiG's own defect
  • Symptom: Case-insensitive glob matching compares as a JavaScript regular expression does: İ no longer matches i, the Kelvin sign no longer matches k, and the Angstrom sign no longer matches å (the engine lower-cased both sides).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Case-insensitive glob matching compares as a JavaScript regular expression does: İ no longer matches i, the Kelvin sign no longer matches k, and the Angstrom sign no longer matches å (the engine lower-cased both sides).

BF-928e15c-49

OpenAI ChatGPT and Codex OAuth token failures with an empty body report the reason phrase the server sent…

  • Source: PiG's own defect
  • Symptom: OpenAI ChatGPT and Codex OAuth token failures with an empty body report the reason phrase the server sent (response.statusText), as Pi does, instead of the canonical text for the status code.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: OpenAI ChatGPT and Codex OAuth token failures with an empty body report the reason phrase the server sent (response.statusText), as Pi does, instead of the canonical text for the status code.

BF-928e15c-5

Sign in with ChatGPT now reads the token endpoint's responses as Pi does: a failed response quotes the body…

  • Source: PiG's own defect
  • Symptom: Sign in with ChatGPT now reads the token endpoint's responses as Pi does: a failed response quotes the body decoded as fetch does (an unreadable body falls back to the status text), a body that is not JSON or cannot be read fails the request instead of reporting that it is not an object, and a leading byte order mark or ill-formed UTF-8 is read as fetch does.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Sign in with ChatGPT now reads the token endpoint's responses as Pi does: a failed response quotes the body decoded as fetch does (an unreadable body falls back to the status text), a body that is not JSON or cannot be read fails the request instead of reporting that it is not an object, and a leading byte order mark or ill-formed UTF-8 is read as fetch does.

BF-928e15c-50

OpenRouter OAuth key exchange now ignores a leading byte order mark in the response body, as Pi's…

  • Source: PiG's own defect
  • Symptom: OpenRouter OAuth key exchange now ignores a leading byte order mark in the response body, as Pi's Response.json() does.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: OpenRouter OAuth key exchange now ignores a leading byte order mark in the response body, as Pi's Response.json() does.

BF-928e15c-51

Fullscreen double-click selects the word Pi selects: words come from the same ICU segmentation as…

  • Source: PiG's own defect
  • Symptom: Fullscreen double-click selects the word Pi selects: words come from the same ICU segmentation as Intl.Segmenter (Thai, Lao, Khmer and Burmese dictionary words, Devanagari conjuncts, emoji ZWJ sequences), and thinking-block wrapping and Mermaid widths split only between Intl.Segmenter graphemes.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Fullscreen double-click selects the word Pi selects: words come from the same ICU segmentation as Intl.Segmenter (Thai, Lao, Khmer and Burmese dictionary words, Devanagari conjuncts, emoji ZWJ sequences), and thinking-block wrapping and Mermaid widths split only between Intl.Segmenter graphemes.

BF-928e15c-52

Anthropic: a server-requested retry delay above the cap fails with `Server requested Ns retry delay (max…

  • Source: PiG's own defect
  • Symptom: Anthropic: a server-requested retry delay above the cap fails with Server requested Ns retry delay (max: Ms). <status> <body> like the SDK's error, not the HTTP client's Post "<url>": … 500 Internal Server Error. text.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Anthropic: a server-requested retry delay above the cap fails with Server requested Ns retry delay (max: Ms). <status> <body> like the SDK's error, not the HTTP client's Post "<url>": … 500 Internal Server Error. text.

BF-928e15c-53

OpenAI Codex: an error body whose error member is absent or falsy (an object without it, an array, null)…

  • Source: PiG's own defect
  • Symptom: OpenAI Codex: an error body whose error member is absent or falsy (an object without it, an array, null) keeps the raw body as the message instead of the usage-limit text.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: OpenAI Codex: an error body whose error member is absent or falsy (an object without it, an array, null) keeps the raw body as the message instead of the usage-limit text.

BF-928e15c-54

Provider streams read integers written as 12.0, 1.2e1 or 12E0 (a token count, an index) as JavaScript's…

  • Source: PiG's own defect
  • Symptom: Provider streams read integers written as 12.0, 1.2e1 or 12E0 (a token count, an index) as JavaScript's JSON.parse does; Go's decoder refused them for an integer field and ended the stream with an error.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Provider streams read integers written as 12.0, 1.2e1 or 12E0 (a token count, an index) as JavaScript's JSON.parse does; Go's decoder refused them for an integer field and ended the stream with an error. Anthropic, OpenAI completions and Responses (Azure, Codex), Google, Mistral and pi-messages are covered.

BF-928e15c-55

Radius OAuth login now reads the gateway's responses as Pi does: an error body that is a JSON string, number…

  • Source: PiG's own defect
  • Symptom: Radius OAuth login now reads the gateway's responses as Pi does: an error body that is a JSON string, number or array reports just the HTTP status (not the body text), a null body reports the JavaScript TypeError text, a JSON null expires_in counts as zero, device-authorization members of an unexpected JSON type report the missing-fields error, and ill-formed UTF-8 or a truncated body is read as fetch does.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Radius OAuth login now reads the gateway's responses as Pi does: an error body that is a JSON string, number or array reports just the HTTP status (not the body text), a null body reports the JavaScript TypeError text, a JSON null expires_in counts as zero, device-authorization members of an unexpected JSON type report the missing-fields error, and ill-formed UTF-8 or a truncated body is read as fetch does.

BF-928e15c-56

Radius OAuth login again signs in when the gateway's token response has no refresh token, as Pi does

  • Source: PiG's own defect
  • Symptom: Radius OAuth login again signs in when the gateway's token response has no refresh token, as Pi does.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Radius OAuth login again signs in when the gateway's token response has no refresh token, as Pi does. A success response that is not JSON, or whose body is cut off, reports the same message Pi shows (JavaScript's SyntaxError text, or terminated), and so does a discovery error whose body is cut off. Every OAuth login reads a body that starts with two byte order marks as Node's fetch does: both are dropped.

BF-928e15c-57

A provider request whose URL path Go cannot parse no longer sends the URL fragment to the server; fetch never…

  • Source: PiG's own defect
  • Symptom: A provider request whose URL path Go cannot parse no longer sends the URL fragment to the server; fetch never sends it.
  • Root cause: Not recorded.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A provider request whose URL path Go cannot parse no longer sends the URL fragment to the server; fetch never sends it. OpenAI Codex again reports a base URL with credentials with fetch's own message.

BF-928e15c-58

The Anthropic stream reader takes an event's type only from a member named exactly type, as Pi does; a…

  • Source: PiG's own defect
  • Symptom: The Anthropic stream reader takes an event's type only from a member named exactly type, as Pi does; a member such as TYPE no longer marks the start or end of a message.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: The Anthropic stream reader takes an event's type only from a member named exactly type, as Pi does; a member such as TYPE no longer marks the start or end of a message.

BF-928e15c-59

A provider HTTP error body that starts with two byte order marks reads as response.text() reads it: both…

  • Source: PiG's own defect
  • Symptom: A provider HTTP error body that starts with two byte order marks reads as response.text() reads it: both marks are dropped, so a JSON error message is found.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A provider HTTP error body that starts with two byte order marks reads as response.text() reads it: both marks are dropped, so a JSON error message is found. Pig dropped one.

BF-928e15c-6

The codemode-store session entry lists the keys of its set object as Pi does (array-index keys ascending…

  • Source: PiG's own defect
  • Symptom: The codemode-store session entry lists the keys of its set object as Pi does (array-index keys ascending, then the others in the order the script first wrote them) instead of in sorted order (execute.ts, host.ts parseStoreWrites).
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: The codemode-store session entry lists the keys of its set object as Pi does (array-index keys ascending, then the others in the order the script first wrote them) instead of in sorted order (execute.ts, host.ts parseStoreWrites).

BF-928e15c-60

A models.json whose contextWindow, maxTokens or a model override writes an integer as 128000.0 or…

  • Source: PiG's own defect
  • Symptom: A models.json whose contextWindow, maxTokens or a model override writes an integer as 128000.0 or 1.28E5 loads as Pi's JSON.parse reads it; Pig refused the whole file with "Failed to parse models.json".
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A models.json whose contextWindow, maxTokens or a model override writes an integer as 128000.0 or 1.28E5 loads as Pi's JSON.parse reads it; Pig refused the whole file with "Failed to parse models.json".

BF-928e15c-61

Classifier (typesafe-system-one, llama.cpp) and Radius gateway config responses are decoded as…

  • Source: PiG's own defect
  • Symptom: Classifier (typesafe-system-one, llama.cpp) and Radius gateway config responses are decoded as response.text() and response.json() decode them: two leading byte order marks are dropped and ill-formed UTF-8 becomes U+FFFD.
  • Root cause: Not recorded.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Classifier (typesafe-system-one, llama.cpp) and Radius gateway config responses are decoded as response.text() and response.json() decode them: two leading byte order marks are dropped and ill-formed UTF-8 becomes U+FFFD. A classifier answer behind two marks failed with a JSON syntax error, and a Radius config behind a mark was reported invalid.

BF-928e15c-62

Amazon Bedrock streams read integers written as 12.0 or 1e0 (a content block index, a token count) as the…

  • Source: PiG's own defect
  • Symptom: Amazon Bedrock streams read integers written as 12.0 or 1e0 (a content block index, a token count) as the AWS SDK does after JSON.parse; Pig ended the stream with a decode error.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Amazon Bedrock streams read integers written as 12.0 or 1e0 (a content block index, a token count) as the AWS SDK does after JSON.parse; Pig ended the stream with a decode error.

BF-928e15c-63

An OpenAI Codex HTTP error with an empty body reports the reason phrase the server sent, or "Request failed"…

  • Source: PiG's own defect
  • Symptom: An OpenAI Codex HTTP error with an empty body reports the reason phrase the server sent, or "Request failed" when it sent none, as Pi does; Pig used Go's status text, which is empty for a code such as 529.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: An OpenAI Codex HTTP error with an empty body reports the reason phrase the server sent, or "Request failed" when it sent none, as Pi does; Pig used Go's status text, which is empty for a code such as 529. A Codex error body behind three or four byte order marks is now read as Pi reads it, through response.text() twice.

BF-928e15c-64

Print mode (-p) now prints the partial text, writes a warning to stderr and exits 1 when the final response…

  • Source: PiG's own defect
  • Symptom: Print mode (-p) now prints the partial text, writes a warning to stderr and exits 1 when the final response was cut off at the model's output token limit, instead of exiting 0 without a warning.
  • Root cause: Pi's shutdown() stops the TUI, awaits runtimeHost.dispose() (session_shutdown), prints the resume command when the session file exists and calls process.exit(0); it never awaits the session_start, session_before_switch or session_before_fork handler that asked for the shutdown (interactive-mode.ts:2020-2024, 4251-4290).
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 928e15cd57 (2026-10-14)
  • Tests: internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-switch.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-start.toml
  • Release note: Print mode (-p) now prints the partial text, writes a warning to stderr and exits 1 when the final response was cut off at the model's output token limit, instead of exiting 0 without a warning.

BF-928e15c-65

Delayed terminal replies (OSC color, XTVersion, Kitty graphics) that arrive in pieces are no longer typed…

  • Source: PiG's own defect
  • Symptom: Delayed terminal replies (OSC color, XTVersion, Kitty graphics) that arrive in pieces are no longer typed into the editor or read as Ctrl+G; batched Alt+]/P/_ keystrokes still time out as keys (Pi 1.2.0 stdin-buffer).
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Delayed terminal replies (OSC color, XTVersion, Kitty graphics) that arrive in pieces are no longer typed into the editor or read as Ctrl+G; batched Alt+]/P/_ keystrokes still time out as keys (Pi 1.2.0 stdin-buffer).

BF-928e15c-66

In the alternate screen, wheel input that a focused overlay does not handle scrolls the transcript (Pi 1.2.0…

  • Source: PiG's own defect
  • Symptom: In the alternate screen, wheel input that a focused overlay does not handle scrolls the transcript (Pi 1.2.0 #10769).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: In the alternate screen, wheel input that a focused overlay does not handle scrolls the transcript (Pi 1.2.0 #10769).

BF-928e15c-67

A session file with invalid UTF-8 inside a record loads with one U+FFFD per maximal invalid subpart, as…

  • Source: PiG's own defect
  • Symptom: A session file with invalid UTF-8 inside a record loads with one U+FFFD per maximal invalid subpart, as Node's UTF-8 decoder reads it, instead of one per byte.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A session file with invalid UTF-8 inside a record loads with one U+FFFD per maximal invalid subpart, as Node's UTF-8 decoder reads it, instead of one per byte.

BF-928e15c-68

A system message's sections object now enumerates integer-like section names first, in ascending order…

  • Source: PiG's own defect
  • Symptom: A system message's sections object now enumerates integer-like section names first, in ascending order, when read from or written to JSON, as a JavaScript object does, so prompt section replay and planned section patches keep Pi's order.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A system message's sections object now enumerates integer-like section names first, in ascending order, when read from or written to JSON, as a JavaScript object does, so prompt section replay and planned section patches keep Pi's order.

BF-928e15c-69

A configured thinking budget of zero (or below) now wins over the default in the OpenAI-compatible, Google…

  • Source: PiG's own defect
  • Symptom: A configured thinking budget of zero (or below) now wins over the default in the OpenAI-compatible, Google and Bedrock Claude request builders and the shared simple-options helpers, as in Pi.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A configured thinking budget of zero (or below) now wins over the default in the OpenAI-compatible, Google and Bedrock Claude request builders and the shared simple-options helpers, as in Pi. This applies to thinkingBudgets in settings and to an extension model request's thinkingBudgets. ai.ThinkingBudgets gains a Set field and ai.NewThinkingBudgets, and reads and writes Pi's {"minimal":n,...} JSON object with only the configured levels. A Claude budget of zero is floored to 1024 and is not lowered further by the context clamp (thinkingBudgetTokens || 1024).

BF-928e15c-7

OpenAI Codex device-code login now reads the server's responses the way Pi does: a field of an unexpected…

  • Source: PiG's own defect
  • Symptom: OpenAI Codex device-code login now reads the server's responses the way Pi does: a field of an unexpected JSON type reports the response as invalid (quoted as JSON.stringify writes it, not as the raw body) instead of a Go decode error, interval strings follow JavaScript Number() (hex, exponent and whitespace forms), any 2xx status counts as success, error bodies and a leading byte order mark are read as Response.text() and Response.json() do, and an unparsable body fails the login instead of reporting a failed poll.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: OpenAI Codex device-code login now reads the server's responses the way Pi does: a field of an unexpected JSON type reports the response as invalid (quoted as JSON.stringify writes it, not as the raw body) instead of a Go decode error, interval strings follow JavaScript Number() (hex, exponent and whitespace forms), any 2xx status counts as success, error bodies and a leading byte order mark are read as Response.text() and Response.json() do, and an unparsable body fails the login instead of reporting a failed poll.

BF-928e15c-70

A tool call whose arguments hold a key with an escaped lone surrogate ("\ud800a") keeps that key's place in…

  • Source: PiG's own defect
  • Symptom: A tool call whose arguments hold a key with an escaped lone surrogate ("\ud800a") keeps that key's place in the member order, instead of moving it last.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A tool call whose arguments hold a key with an escaped lone surrogate ("\ud800a") keeps that key's place in the member order, instead of moving it last.

BF-928e15c-71

The agent loop now gives a custom stream function Pi's messages (errored and aborted turns kept, no synthetic…

  • Source: PiG's own defect
  • Symptom: The agent loop now gives a custom stream function Pi's messages (errored and aborted turns kept, no synthetic "No result provided" results); real providers repair orphaned, repeated and out-of-order tool results themselves (D48).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: The agent loop now gives a custom stream function Pi's messages (errored and aborted turns kept, no synthetic "No result provided" results); real providers repair orphaned, repeated and out-of-order tool results themselves (D48).

BF-928e15c-72

A real provider's tool-flow repair (D48) no longer moves a tool result back to an earlier unanswered call…

  • Source: PiG's own defect
  • Symptom: A real provider's tool-flow repair (D48) no longer moves a tool result back to an earlier unanswered call that has the same id, and no longer keeps a result in a turn that never made its call.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A real provider's tool-flow repair (D48) no longer moves a tool result back to an earlier unanswered call that has the same id, and no longer keeps a result in a turn that never made its call. A request whose tool flow Pi sends intact reaches the provider unchanged, even when a provider reuses call ids every turn.

BF-928e15c-73

Fixed the session tree selector keeping its cursor inside the rows

  • Source: PiG's own defect
  • Symptom: Fixed the session tree selector keeping its cursor inside the rows.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Fixed the session tree selector keeping its cursor inside the rows. As in Pi, Up and Page Down over an empty filtered list leave the selection at -1 (Down leaves it at 1), and a later filter change keeps it there until a row has been selected: the list then shows (0/N) with no highlighted row.

BF-928e15c-74

SettingsList draws Pi's bytes: its scroll indicator, description and hint rows are no longer padded to the…

  • Source: PiG's own defect
  • Symptom: SettingsList draws Pi's bytes: its scroll indicator, description and hint rows are no longer padded to the width, and a row or hint that does not fit ends in ... as pi-tui's truncateToWidth ends it.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: SettingsList draws Pi's bytes: its scroll indicator, description and hint rows are no longer padded to the width, and a row or hint that does not fit ends in ... as pi-tui's truncateToWidth ends it.

BF-928e15c-75

A ! command block draws Pi's bytes: two consecutive blocks are separated by one blank row instead of two…

  • Source: PiG's own defect
  • Symptom: A ! command block draws Pi's bytes: two consecutive blocks are separated by one blank row instead of two, the running hint names the tui.select.cancel keys (escape/ctrl+c to cancel) as Pi's does, and its rows are padded to the width with Pi's SGR closes.
  • Root cause: Not recorded.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A ! command block draws Pi's bytes: two consecutive blocks are separated by one blank row instead of two, the running hint names the tui.select.cancel keys (escape/ctrl+c to cancel) as Pi's does, and its rows are padded to the width with Pi's SGR closes. A quoted line followed by a lazy === stays quoted text, as in Pi, instead of becoming a heading.

BF-928e15c-76

A provider defined only by virtual models reports configured with source environment and label virtual…

  • Source: PiG's own defect
  • Symptom: A provider defined only by virtual models reports configured with source environment and label virtual from ModelRegistry.GetProviderAuthStatus/ModelRuntime.GetProviderAuthStatus, and GetProviderAuth/GetAuth resolve it with source virtual instead of nil, as Pi does.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: A provider defined only by virtual models reports configured with source environment and label virtual from ModelRegistry.GetProviderAuthStatus/ModelRuntime.GetProviderAuthStatus, and GetProviderAuth/GetAuth resolve it with source virtual instead of nil, as Pi does.

BF-928e15c-77

Provider order puts a provider of only virtual models where Pi's providerIds does (the place of its…

  • Source: PiG's own defect
  • Symptom: Provider order puts a provider of only virtual models where Pi's providerIds does (the place of its models.json ID when that file loaded, else after the others), allModels/allAvailable list its models there, and the virtual models of a provider with no credentials are not available.
  • Root cause: Not recorded.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: Provider order puts a provider of only virtual models where Pi's providerIds does (the place of its models.json ID when that file loaded, else after the others), allModels/allAvailable list its models there, and the virtual models of a provider with no credentials are not available.

BF-928e15c-78

xAI OAuth login and refresh now read the server's response the way Pi does: a body that is not a JSON object…

  • Source: PiG's own defect
  • Symptom: xAI OAuth login and refresh now read the server's response the way Pi does: a body that is not a JSON object counts as empty, a field of an unexpected JSON type is reported as that field being invalid (instead of "xAI OAuth returned invalid JSON"), a refresh_token or expires_in that is present but null is rejected rather than treated as absent, a leading byte order mark is ignored, and verification_uri_complete is validated before the required fields.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: xAI OAuth login and refresh now read the server's response the way Pi does: a body that is not a JSON object counts as empty, a field of an unexpected JSON type is reported as that field being invalid (instead of "xAI OAuth returned invalid JSON"), a refresh_token or expires_in that is present but null is rejected rather than treated as absent, a leading byte order mark is ignored, and verification_uri_complete is validated before the required fields. A refresh with an empty stored refresh token now sends the request as Pi does.

BF-928e15c-8

OpenAI Codex and OpenRouter OAuth responses read ill-formed UTF-8 and a body that fails to read as fetch…

  • Source: PiG's own defect
  • Symptom: OpenAI Codex and OpenRouter OAuth responses read ill-formed UTF-8 and a body that fails to read as fetch does: one U+FFFD per maximal ill-formed subpart, and a truncated body is never parsed (an unreadable error body reads as empty text, an unreadable success body as invalid JSON).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: OpenAI Codex and OpenRouter OAuth responses read ill-formed UTF-8 and a body that fails to read as fetch does: one U+FFFD per maximal ill-formed subpart, and a truncated body is never parsed (an unreadable error body reads as empty text, an unreadable success body as invalid JSON).

BF-928e15c-9

An OpenAI Codex login or refresh whose success response is not JSON reports JavaScript's own message, as Pi…

  • Source: PiG's own defect
  • Symptom: An OpenAI Codex login or refresh whose success response is not JSON reports JavaScript's own message, as Pi does (for example Unexpected token 'n', "not json" is not valid JSON, without a PiG prefix), and one whose body is cut off reports terminated, as Node's fetch does.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 928e15cd57 (2026-10-14)
  • Tests: None recorded.
  • Release note: An OpenAI Codex login or refresh whose success response is not JSON reports JavaScript's own message, as Pi does (for example Unexpected token 'n', "not json" is not valid JSON, without a PiG prefix), and one whose body is cut off reports terminated, as Node's fetch does.

BF-92da987

Codemode no longer reserves its whole 256 MiB script-memory limit when a session starts: the QuickJS linear…

  • Source: PiG's own defect
  • Symptom: Codemode no longer reserves its whole 256 MiB script-memory limit when a session starts: the QuickJS linear memory now grows on demand, so an idle session with one small script allocates about 4 MiB instead of about 359 MiB (Go heap held, 321 MiB before, 4 MiB after), while a runaway allocation still fails inside the script at the same limit.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 92da987a37 (2026-10-14)
  • Tests: codemode/baseline_memory_test.go::TestIdleBaselineMemoryStaysSmall
  • Release note: Codemode no longer reserves its whole 256 MiB script-memory limit when a session starts: the QuickJS linear memory now grows on demand, so an idle session with one small script allocates about 4 MiB instead of about 359 MiB (Go heap held, 321 MiB before, 4 MiB after), while a runaway allocation still fails inside the script at the same limit. Reported by @kayis-rahman (#211)

BF-93319f4

The terminal UI worked example now describes the typed /model prefix in words

  • Source: PiG's own defect
  • Symptom: The terminal UI worked example now describes the typed /model prefix in words.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 93319f4284 (2026-10-11)
  • Tests: test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: The terminal UI worked example now describes the typed /model prefix in words. A docs check no longer reports a nonexistent command from that page.

BF-93c09d6

A local package source that is a file Node cannot import, such as a packed .tgz, fails to load as in Pi…

  • Source: PiG's own defect
  • Symptom: A local package source that is a file Node cannot import, such as a packed .tgz, fails to load as in Pi (Failed to load extension: Unknown file extension ".tgz" for <path>, exit 1) instead of being run as a program and failing with "permission denied".
  • Root cause: pig install --validate-only on a Package root resolved the root as one Node extension, so a Package whose pi.extensions names a Go directory failed with "declares pi.extensions directories with no extension entry file" although pig package validate and pig install accept it. Validation now loads every extension the Package's discovery names, as an install does.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 93c09d62ff (2026-10-13)
  • Tests: coding/cli/extension_validate_command_test.go::TestInstallValidateOnlyValidatesPackageRootMembers, coding/extension/source/resolve_test.go::TestResolveFileWithoutLoaderMatchesNode
  • Release note: A local package source that is a file Node cannot import, such as a packed .tgz, fails to load as in Pi (Failed to load extension: Unknown file extension ".tgz" for <path>, exit 1) instead of being run as a program and failing with "permission denied". Like Pi, pig install ./x.tgz still records the path; Pi does not unpack tarballs either.

BF-93c09d6-2

pig install --validate-only on a Package root validates every extension the Package names in…

  • Source: PiG's own defect
  • Symptom: pig install --validate-only on a Package root validates every extension the Package names in pi.extensions (or its conventional directories), in any language, as an install loads them.
  • Root cause: pig install --validate-only on a Package root resolved the root as one Node extension, so a Package whose pi.extensions names a Go directory failed with "declares pi.extensions directories with no extension entry file" although pig package validate and pig install accept it. Validation now loads every extension the Package's discovery names, as an install does.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 93c09d62ff (2026-10-13)
  • Tests: coding/cli/extension_validate_command_test.go::TestInstallValidateOnlyValidatesPackageRootMembers, coding/extension/source/resolve_test.go::TestResolveFileWithoutLoaderMatchesNode
  • Release note: pig install --validate-only on a Package root validates every extension the Package names in pi.extensions (or its conventional directories), in any language, as an install loads them. It no longer treats the root as one Node extension and fails with "declares pi.extensions directories with no extension entry file" for a Go member.

BF-93c5def

A built-in extension named with -e builtin:<name> loads before the extensions from settings and the…

  • Source: PiG's own defect
  • Symptom: Every built-in loaded after the file extensions.
  • Root cause: Tests: TestExplicitBuiltinExtensionsPrecedeTheIndexOrderedOnes now drives the production build (loadProductionExtensionSetIn) and TestExtensionsInLoadOrderWithExplicitPutsExplicitBuiltinsFirst covers the ordering function (none, one, two, a path that did not load). Mutants killed: the build site using the plain order, the function ignoring explicit, the CLI passing no explicit paths. The reload sites share the function and are covered by the unit test, not by a reload-driving test. No generated files in this commit (exports change pig-go.json; the merger regenerates). Signed-off-by: Michael Kinsy mrkinsy5@gmail.com
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 93c5def063 (2026-10-10), f5e0378e28 (2026-10-10)
  • Tests: coding/cli/builtin_extension_order_test.go::TestBuiltinExtensionsLoadInIndexOrderWithLlamaFirst, coding/cli/builtin_extension_order_test.go::TestExplicitBuiltinExtensionsPrecedeTheIndexOrderedOnes, coding/cli/extension_set_builtin_mcp_upstream_test.go::TestUpstreamResourceLoaderNoExtensionsDisablesBuiltinsUnlessLoadedExplicitly, coding/extensionset/extensionset_test.go::TestBuiltinNamesListsARepeatedNameOnce
  • Release note: A built-in extension named with -e builtin:<name> loads before the extensions from settings and the extension directories, as in Pi, and keeps that place after /reload. Before, every built-in loaded after the file extensions. The order decides which extension owns a shared command, tool or flag name and the order of event handlers.

BF-93f8b7c

fix(agent): a listener failure at agent_start ends the run as in Pi; failure messages carry the model's api

  • Source: PiG's own defect
  • Symptom: fix(agent): a listener failure at agent_start ends the run as in Pi; failure messages carry the model's api
  • Root cause: Differential test of Pi's Agent class (agent.ts) against Pig's: 3000 seeded programs of prompt/continue/steer/followUp/queue modes/reset/abort, listeners that unsubscribe, act and throw, and scripted turns (text, tool call, error), comparing every operation's error and state and every listener event with its state snapshot.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 93f8b7c250 (2026-10-10)
  • Tests: agent/agent_class_pi_oracle_test.go::TestAgentClassMatchesPi

BF-94e3498

On Windows, PiG no longer searches C:\etc\pig\piglets or C:\opt\pig\piglet.yaml for a Piglet

  • Source: PiG's own defect
  • Symptom: On Windows, PiG no longer searches C:\etc\pig\piglets or C:\opt\pig\piglet.yaml for a Piglet.
  • Root cause: testenv.Start/Run/Output/CombinedOutput start a child in its own process group (Windows: kill-on-close Job Object), bound Wait with WaitDelay, stop the group SIGTERM then SIGKILL at cleanup, and fail the test when a descendant outlives an exited child. The lazy-startup test's logging shim guards against a real tool that re-execs the shim (PID sentinel) and starts the real tool without the shim directory on PATH. The D157 read-only-root test now expects no warning, and the --no-stdin help line no longer trips the strip leak gate.
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: 5c5c1498a1 (2026-10-10), 94e3498530 (2026-10-10)
  • Tests: coding/cli/extension_runtime_lazy_startup_upstream_test.go::TestRuntimeShimStopsWhenTheRealToolReentersIt, coding/cli/piglet_verify_cache_startup_test.go::TestSignedPigletBinaryRunsWhenItsVerifyCacheCannotBeWritten, coding/piglet/system_paths_test.go::TestClassifyLocationNamesTheSystemPigletDirectory, coding/piglet/system_paths_test.go::TestSystemPigletLocationsAreUnixOnly, internal/testenv/spawn_test.go::TestCleanupStopsARunningChild, internal/testenv/spawn_test.go::TestRunStopsADescendantOfAnExitedChild, internal/testenv/spawn_test.go::TestStopEndsAChildThatIgnoresSIGTERM, internal/testenv/spawn_test.go::TestWaitDoesNotBlockOnStdoutHeldByADescendant
  • Release note: On Windows, PiG no longer searches C:\etc\pig\piglets or C:\opt\pig\piglet.yaml for a Piglet. A rooted path without a volume is drive-relative there, so any local user could plant a Piglet that every other user's pig loaded.

BF-94e6796

A Durable Harness task that went to sleep for a short time, such as the 1 ms backoff before a generation…

  • Source: PiG's own defect
  • Symptom: A Durable Harness task that went to sleep for a short time, such as the 1 ms backoff before a generation retry, could stay asleep for good on a busy machine, so a Wait on its submission never returned (a CI run of the durable/harness tests hung until the 10-minute timeout).
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 94e6796e8d (2026-10-20, #234)
  • Tests: durable/harness/harness_blocked_test.go::TestPublishWakeOwedAfterSeal, durable/harness/scheduler_wake_clock_test.go::TestGenerationRetryWaitSettlesWhenWakeRecomputedAfterBackoffCameDue, durable/harness/scheduler_wake_clock_test.go::TestWakeRecomputedAfterSleepCameDueArmsItsTimer, durable/harness/scheduler_wake_clock_test.go::TestWakeRecomputedAfterSleepCameDueOffersItsAlarm
  • Release note: A Durable Harness task that went to sleep for a short time, such as the 1 ms backoff before a generation retry, could stay asleep for good on a busy machine, so a Wait on its submission never returned (a CI run of the durable/harness tests hung until the 10-minute timeout). The Harness recomputes when to wake the process in a goroutine that can run milliseconds after the change, and it read the clock when that goroutine ran, not at the change, as Pi's microtask does. A sleep that had been ahead of a scheduling pass was then already due, so no timer was armed for it, and a host's onWake was told that work was active instead of the time to wake at; nothing ran the task again. The recomputation now reads the clock of the change that queued it, so the timer is armed (or the time is offered to onWake) and the task wakes.

BF-94f1e6f

fix(coding): a provider of only virtual models is configured with source virtual

  • Source: PiG's own defect
  • Symptom: fix(coding): a provider of only virtual models is configured with source virtual
  • Root cause: Pi's registerVirtualModel marks a virtual-only provider configured (auth source 'virtual') and withVirtualModels resolves its auth with that source: getProviderAuthStatus is {configured, environment, label virtual} and getAuth returns {source virtual}. Pig reported unconfigured and nil. Known remaining (seeds 11, 13): provider list order for a provider of models.json plus virtual models, and allAvailable for an extension provider with virtual models; status after unvirtual keeps Pi's stale virtual entry when models.json exists.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 94f1e6f147 (2026-10-10)
  • Tests: None recorded.

BF-9515bcb

Code mode: a deterministic run's clocks round like Math.round (0.49999999999999994 ms read 1 ms), and a…

  • Source: PiG's own defect
  • Symptom: Code mode: a deterministic run's clocks round like Math.round (0.49999999999999994 ms read 1 ms), and a tool declaration's @default/const/enum values keep the schema's authored member order instead of sorted members.
  • Root cause: describe.ts declarationOf hands renderToolSample tool.parameters as authored, and @default/const/enum values are JSON.stringify'd; json.Marshal of the parameters map sorted their members. ParametersJSON keeps the decoded order.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 89b9ec58c4 (2026-10-13), 9515bcbba5 (2026-10-13)
  • Tests: codemode/deterministic_internal_test.go::TestTheVirtualClockRoundsLikeMathRound, durable/codemode/describe_test.go::TestDescribeRendersADefaultWithItsAuthoredMemberOrder
  • Release note: Code mode: a deterministic run's clocks round like Math.round (0.49999999999999994 ms read 1 ms), and a tool declaration's @default/const/enum values keep the schema's authored member order instead of sorted members.

BF-958e80d

A tool call whose arguments hold a number out of float64 range (1e999, -1e999) no longer ends the session…

  • Source: PiG's own defect
  • Symptom: Pi reads such a number as an infinity, keeps it in the call's arguments, and writes null wherever it serializes them (events, the session file, requests); PiG now does the same, for a streamed argument text, a tool call a Go caller or the faux provider supplies, and an Anthropic content_block_start input, which used to end the turn with a decode error.
  • Root cause: Pi's JSON.parse reads 1e999 as Infinity (and partial-json reads Infinity, -Infinity and NaN), keeps it in a tool call's arguments, and every serialization, JSON.stringify, writes null. A tool call in PiG could hold a non-finite float64 (the streamed text of a provider that hands the cell the parser's number, a Go caller's FauxToolCall, a provider that decodes the whole input) but publishing it copied the arguments through cloneJSONValue, which panicked with "clone validated JSON value: json: unsupported value: +Inf" and ended the session.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 958e80d99f (2026-10-25)
  • Tests: ai/tool_call_nonfinite_test.go::FuzzToolArgumentNumberLiterals, ai/tool_call_nonfinite_test.go::TestCellPublishesNonFiniteArguments, ai/tool_call_nonfinite_test.go::TestNonFiniteArgumentsAreValidCloneAndSerializeAsStringify, ai/tool_call_nonfinite_test.go::TestNonFiniteArgumentsDoNotExcuseOtherFaults, ai/tool_call_nonfinite_test.go::TestStreamedNonFiniteArgumentsDoNotPanic, ai/tool_call_nonfinite_test.go::TestStreamedNonFiniteArgumentsMatchPi, ai/transcript_frozen_test.go::TestFrozenContextStillValidatesAndStillCopiesTheCallersTools, ai/transcript_test.go::TestNormalizeContextAcceptsNonFiniteToolArguments and 3 more in docs/bug-ledger.json
  • Release note: A tool call whose arguments hold a number out of float64 range (1e999, -1e999) no longer ends the session with a panic (clone validated JSON value: json: unsupported value: +Inf). Pi reads such a number as an infinity, keeps it in the call's arguments, and writes null wherever it serializes them (events, the session file, requests); PiG now does the same, for a streamed argument text, a tool call a Go caller or the faux provider supplies, and an Anthropic content_block_start input, which used to end the turn with a decode error.

BF-95a5f79

RPC mode starts the --tools list active in its own order when it names extension tools, as Pi does

  • Source: PiG's own defect
  • Symptom: RPC mode starts the --tools list active in its own order when it names extension tools, as Pi does.
  • Root cause: Both fail on. - TestReloadKeepsTheActiveToolOrder: the reload rebuild keeps the active order (agent-session.ts:3666-3669). The interactive /reload test could not catch its loss, because the transcript's initial tool declaration fixes the request order.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 95a5f79e17 (2026-10-07)
  • Tests: coding/cli/tools_flag_order_test.go::TestReloadKeepsTheToolsFlagOrder, coding/cli/tools_flag_order_test.go::TestToolsFlagOrderReachesTheProviderInEveryMode, coding/cli/tools_flag_order_test.go::TestToolsFlagSelectsToolsInItsOrder, coding/session_tool_registry_port_test.go::TestAllowedToolsStartActiveInTheirOrder, coding/session_tool_registry_port_test.go::TestReloadKeepsTheActiveToolOrder, test/parity/scenarios/rpc/45-rpc-tools-flag-order.toml
  • Release note: RPC mode starts the --tools list active in its own order when it names extension tools, as Pi does. --tools grep,my_tool,read declares grep, my_tool, read to the model and lists them in that order in the system prompt; RPC mode listed the built-in tools first and appended the extension tools.

BF-95eefe2

openai-completions thinking formats now match Pi (ai/openai.go, openai-completions.ts:885-985 and…

  • Source: PiG's own defect
  • Symptom: openai-completions thinking formats now match Pi (ai/openai.go, openai-completions.ts:885-985 and 1034-1075), found by the new live-Pi oracle TestOpenAICompletionsThinkingFormatsPayloadMatchesPi (3,000 probes: every thinkingFormat, compat.chatTemplateKwargs/chatTemplateArgs with scalars, null, $var placeholders, omitWhenOff, arrays, and model thinkingLevelMap entries; 32 of 1,200 probes differed before any fix, 4 more after widening the corpus).
  • Root cause: JSON.parse keeps a member of any type, so Pi keeps a session message whose member has another type than Pi writes, in its entries, branch, context, projection and RPC get_messages (session-manager.ts parseSessionEntryLine, buildSessionContext). PiG's AgentMessage decode failed on such a member and dropped the message from the context, the projection and get_messages: a string timestamp, numeric content, a text block with a numeric text, a usage count written "1", isError "no", a numeric or missing role, and so on.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 95eefe293a (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/openai_completions_thinking_format_oracle_test.go::TestOpenAICompletionsThinkingFormatsPayloadMatchesPi, ai/openai_reasoning_effort_test.go::TestResolveChatTemplateValuesMapsEveryOtherObjectToTheEffort, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain and 2 more in docs/bug-ledger.json
  • Release note: openai-completions thinking formats now match Pi (ai/openai.go, openai-completions.ts:885-985 and 1034-1075), found by the new live-Pi oracle TestOpenAICompletionsThinkingFormatsPayloadMatchesPi (3,000 probes: every thinkingFormat, compat.chatTemplateKwargs/chatTemplateArgs with scalars, null, $var placeholders, omitWhenOff, arrays, and model thinkingLevelMap entries; 32 of 1,200 probes differed before any fix, 4 more after widening the corpus). Fixes: (1) chat-template with no resolved value sent "chat_template_kwargs": null (a typed nil map in an any field defeated omitempty) where Pi omits the member; (2) resolveChatTemplateValues dropped a placeholder whose $var is not thinking.enabled/thinking.budget or a thinking.effort that resolves, where Pi maps every other object (unknown $var, {}, arrays) to the mapped effort; (3) the baseten format sent reasoning_effort: "none" when the model had no off mapping (Pi sends nothing) and sent the clamped level for a level mapped to null (Pi sends nothing), now templateEffort; (4) the ant-ling format with no requested effort ignored Pi's fall-through to the off-value reasoning_effort (:970-975); (5) a thinkingLevelMap value of "" was dropped by omitempty where Pi sends reasoning_effort: "", so the request field is now *string. Regressions: the oracle plus TestResolveChatTemplateValuesMapsEveryOtherObjectToTheEffort; 8 mutations red, 1 not applicable. ./ai -race green except the env-only TestSDKProvidersRequestURLMatchesSDK and TestOpenAIStreamDecoderMatchesSDK (need npm ci in ai/testdata/sdk). Hot file: ai/openai.go belongs to go-idioms.

BF-9721c48

An auto, manual or overflow compaction status shown in the editor's top border now takes the editor border…

  • Source: PiG's own defect
  • Symptom: An auto, manual or overflow compaction status shown in the editor's top border now takes the editor border color, like the working status, as in Pi 1.2.0; it used to keep the accent spinner and muted label.
  • Root cause: Branch summary and retry keep accent and muted, as in Pi.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 9721c489d0 (2026-10-13)
  • Tests: tui/editor_status_test.go::TestEmbeddedCompactionStatusTakesTheEditorBorderColorLikeWorking
  • Release note: An auto, manual or overflow compaction status shown in the editor's top border now takes the editor border color, like the working status, as in Pi 1.2.0; it used to keep the accent spinner and muted label.

BF-9740507

pig extension upgrade now rewrites a Context.ConfigHome() call in an if or switch init statement and…

  • Source: PiG's own defect
  • Symptom: pig extension upgrade now rewrites a Context.ConfigHome() call in an if or switch init statement and in a call with other arguments, such as if err := c.save(ctx.ConfigHome()); err != nil or planDispatch(ctx.Cwd(), ctx.ConfigHome(), name).
  • Root cause:
    • Move a getter out of an if or switch init statement, and move a local Context accessor (Cwd, Mode, HasUI, Width, Height, ToolCallID) that runs before it in the statement out with it, so multi-argument calls such as validateStepRuntime(ctx.Cwd(), ctx.ConfigHome(), x) are rewritten with their order of evaluation kept. - Never discard Context.ConfigHome's error: the old getter could not fail, so in a function that does not return an error the use is listed for the author instead of rewritten to home, _ :=. Other getters keep their discard, which reproduces the old SDK's zero value on failure. - List a getter's method value passed where the old signature is wanted and classify its…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 974050757c (2026-10-12), d305b76bda (2026-10-12)
  • Tests: coding/cli/extension_upgrade_test.go::TestUpgraderRestoresEveryFileWhenTheRebuildFails, coding/cli/extension_upgrade_test.go::TestUpgraderRestoresWrittenFilesWhenAWriteFails, coding/extension/host/runtimecell/go_sdk_override_test.go::TestGoSDKPathOverrideIgnoresRetiredStagedSDK, coding/extension/host/runtimecell/go_sdk_override_test.go::TestGoSDKPathOverrideKeepsCurrentStagedSDK, coding/extension/host/runtimecell/go_sdk_override_test.go::TestRustSDKPathOverrideIgnoresRetiredStagedSDK, coding/extension/host/subprocess/builder_test.go::TestGoNeedsStagedSDK, coding/extension/host/subprocess/builder_test.go::TestRustNeedsStagedSDK, extensions/sdk/upgrade/fixtures_test.go::TestFixturesUpgradeAndBuild and 2 more in docs/bug-ledger.json
  • Release note: pig extension upgrade now rewrites a Context.ConfigHome() call in an if or switch init statement and in a call with other arguments, such as if err := c.save(ctx.ConfigHome()); err != nil or planDispatch(ctx.Cwd(), ctx.ConfigHome(), name). A Cwd, Mode, HasUI, Width, Height or ToolCallID call before the getter moves out with it, so the calls run in the same order as before.

BF-9740507-2

pig extension upgrade no longer discards the error of Context.ConfigHome with `home, _ :=…

  • Source: PiG's own defect
  • Symptom: pig extension upgrade no longer discards the error of Context.ConfigHome with home, _ := ctx.ConfigHome().
  • Root cause:
    • Move a getter out of an if or switch init statement, and move a local Context accessor (Cwd, Mode, HasUI, Width, Height, ToolCallID) that runs before it in the statement out with it, so multi-argument calls such as validateStepRuntime(ctx.Cwd(), ctx.ConfigHome(), x) are rewritten with their order of evaluation kept. - Never discard Context.ConfigHome's error: the old getter could not fail, so in a function that does not return an error the use is listed for the author instead of rewritten to home, _ :=. Other getters keep their discard, which reproduces the old SDK's zero value on failure. - List a getter's method value passed where the old signature is wanted and classify its…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 974050757c (2026-10-12), d305b76bda (2026-10-12)
  • Tests: coding/cli/extension_upgrade_test.go::TestUpgraderRestoresEveryFileWhenTheRebuildFails, coding/cli/extension_upgrade_test.go::TestUpgraderRestoresWrittenFilesWhenAWriteFails, coding/extension/host/runtimecell/go_sdk_override_test.go::TestGoSDKPathOverrideIgnoresRetiredStagedSDK, coding/extension/host/runtimecell/go_sdk_override_test.go::TestGoSDKPathOverrideKeepsCurrentStagedSDK, coding/extension/host/runtimecell/go_sdk_override_test.go::TestRustSDKPathOverrideIgnoresRetiredStagedSDK, coding/extension/host/subprocess/builder_test.go::TestGoNeedsStagedSDK, coding/extension/host/subprocess/builder_test.go::TestRustNeedsStagedSDK, extensions/sdk/upgrade/fixtures_test.go::TestFixturesUpgradeAndBuild and 2 more in docs/bug-ledger.json
  • Release note: pig extension upgrade no longer discards the error of Context.ConfigHome with home, _ := ctx.ConfigHome(). The old ConfigHome could not fail, and an empty configuration root would make its paths relative to the working directory. A function that returns an error now returns it. In a function that does not, the upgrade lists the line for you to change. It also lists a getter passed as a function value where the old signature is wanted, instead of reporting it as an unrelated compile error. Listed lines are line numbers in your source as it was before the upgrade.

BF-9740507-3

pig extension upgrade is all or nothing

  • Source: PiG's own defect
  • Symptom: pig extension upgrade is all or nothing.
  • Root cause:
    • Move a getter out of an if or switch init statement, and move a local Context accessor (Cwd, Mode, HasUI, Width, Height, ToolCallID) that runs before it in the statement out with it, so multi-argument calls such as validateStepRuntime(ctx.Cwd(), ctx.ConfigHome(), x) are rewritten with their order of evaluation kept. - Never discard Context.ConfigHome's error: the old getter could not fail, so in a function that does not return an error the use is listed for the author instead of rewritten to home, _ :=. Other getters keep their discard, which reproduces the old SDK's zero value on failure. - List a getter's method value passed where the old signature is wanted and classify its…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 974050757c (2026-10-12)
  • Tests: coding/cli/extension_upgrade_test.go::TestUpgraderRestoresEveryFileWhenTheRebuildFails, coding/cli/extension_upgrade_test.go::TestUpgraderRestoresWrittenFilesWhenAWriteFails, extensions/sdk/upgrade/fixtures_test.go::TestFixturesUpgradeAndBuild, extensions/sdk/upgrade/rules_test.go::TestGetterMovesOutOfASwitchInit, extensions/sdk/upgrade/rules_test.go::TestListedUsesNameTheirLineBeforeTheRewrite
  • Release note: pig extension upgrade is all or nothing. When writing a file or rebuilding the upgraded extension fails, it restores every file it changed and says so, instead of leaving the extension half rewritten.

BF-976af87

The documentation said PiG starts without a first-time setup screen and that Stock PiG has no built-in sprites

  • Source: PiG's own defect
  • Symptom: The documentation said PiG starts without a first-time setup screen and that Stock PiG has no built-in sprites.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 976af87a3d (2026-10-11)
  • Tests: None recorded.
  • Release note: The documentation said PiG starts without a first-time setup screen and that Stock PiG has no built-in sprites. PiG shows a three-step setup (theme, sprite, anonymous usage data) on the first interactive start when no settings.json exists and PIG_CODING_AGENT_DIR is not set (D88), and the Stock binary includes fifteen built-in sprites and /sprite. The Quickstart, the embedded onboarding page and the concepts page now say so, and "Your first ten minutes" shows each setup screen.

BF-97cd675

fix(agent): nested records keep a message whatever the spelling of its numbers

  • Source: PiG's own defect
  • Symptom: fix(agent): nested records keep a message whatever the spelling of its numbers
  • Root cause: JSON.parse keeps any number, so Pi keeps a session message whatever the spelling of its numbers and writes it with JSON.stringify. made the agent's own integer members (timestamp, usage, durationMs) read any number spelling; the integer members of the records package ai decodes (diagnostics[].timestamp, deferred.expiresAt/pollAfterMs, nestedCalls.calls[].argumentsBytes/durationMs) still failed on 2.0e3 or 4.0e0, and PiG dropped the message and the branch after it.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 97cd6750b8 (2026-10-10)
  • Tests: agent/message_number_test.go::TestDecodedMessageKeepsNumbersInAnySpelling, agent/message_number_test.go::TestDecodedMessageOwnsItsSourceText, test/parity/scenarios/rpc/57-rpc-session-number-spelling.toml

BF-97e178f

Tool-call arguments that arrive as incomplete or malformed JSON are now recovered exactly as Pi's…

  • Source: PiG's own defect
  • Symptom: Tool-call arguments that arrive as incomplete or malformed JSON are now recovered exactly as Pi's partial-json parser recovers them.
  • Root cause: The parser is now parseAny/parseStr/parseObj/parseArr/parseNum of partial-json with Allow.ALL and the same recovery points, JSON.parse as json.Valid plus a UseNumber decode, and the member order still recorded in schemaObjectOrder (including the replaced-key restore). The strict path falls back to the same JSON.parse reading when encoding/json refuses a valid document (1e400). A non-finite number cannot be held in a Go tool-argument object (encoding/json refuses to serialize one, Pi writes null), so it becomes null in nonFiniteToNull, the same text JSON.stringify gives. Test: TestStreamingJSONMatchesPiOnSeededInput (1850 inputs: well-formed documents cut at every boundary, corrupted with…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), e8cc072521 (2026-10-08), 15a5c0b670 (2026-10-09), 97e178f7f6 (2026-10-09)
  • Tests: ai/streaming_json_oracle_test.go::TestStreamingJSONMatchesPiOnSeededInput, ai/trusted_url_oracle_test.go::TestTrustedVerificationURLsMatchPi, chord/delta/own_key_order_test.go::TestDiffRevisionsVisitsArrayIndexKeysFirstInNumericOrder, internal/chord/chordjson/own_key_order_test.go::TestCopyFollowsPisKeyOrderAndMessages
  • Release note: Tool-call arguments that arrive as incomplete or malformed JSON are now recovered exactly as Pi's partial-json parser recovers them. When a value inside an object or array is unreadable, the containers around it keep their members and read on, so text after the bad value is kept; a truncated number is cut at a lowercase e only (1E is not an exponent); Infinity, -Infinity, NaN and out-of-range numbers such as 1e400 are read as values and become null (Pi serializes them as null, and Go cannot serialize them); a __proto__ member is dropped from the recovered object, as in Pi.

BF-983d651

The bash truncation notice for a last line over 50 KB that ends with a newline reports the line's size…

  • Source: PiG's own defect
  • Symptom: The bash truncation notice for a last line over 50 KB that ends with a newline reports the line's size instead of line is 0B (Pi #10015).
  • Root cause: output-accumulator.ts getLastLineBytes returns the bytes after the final newline; the notice read 'line is 0B'. LastLineBytes now returns the open line, else the line the final newline ended.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): 983d651097 (2026-10-09)
  • Tests: internal/codingagent/tools/last_line_bytes_test.go::TestBashLongLastLineNoticeNamesTheLineSize, internal/codingagent/tools/last_line_bytes_test.go::TestOutputAccumulatorLastLineBytes
  • Release note: The bash truncation notice for a last line over 50 KB that ends with a newline reports the line's size instead of line is 0B (Pi #10015).

BF-985b5f9

fix(coding): a resumed session takes its active tools from the transcript (pi-mono #10785) [SIGNOFF-PENDING]

  • Source: PiG's own defect
  • Symptom: fix(coding): a resumed session takes its active tools from the transcript (pi-mono #10785) [SIGNOFF-PENDING]
  • Root cause: Pi's sdk.ts always passes initialActiveToolNames, so agent-session.ts's _restoreToolsFromTranscript never runs on resume and the registration defaults replace the tools the user enabled. A resumed session (a transcript with messages) now restores them, unless the caller selected tools explicitly (an allowlist, +name/-name modifiers, NoTools). Observable difference from Pi: do not merge before owner sign-off.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 985b5f9242 (2026-10-10)
  • Tests: coding/cli/rpc_resume_tools_test.go::TestRPCResumeKeepsDynamicallyActivatedTools, coding/session_resume_tools_test.go::TestResumeRestoresTheTranscriptsActiveTools

BF-992f3cb

A before_provider_request replacement that is not an object leaves Anthropic Messages as Pi's `{ ...value…

  • Source: PiG's own defect
  • Symptom: A before_provider_request replacement that is not an object leaves Anthropic Messages as Pi's { ...value, stream: true } (null, a number or a boolean send {"stream":true}, an array or string its index members), "betas": null sends no anthropic-beta header, array-index keys lead a replacement body as in JavaScript, and a Codex WebSocket body's own type keeps the first position; a Codex payload hook that returns null no longer crashes PiG.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), e8cc072521 (2026-10-08), 433d232815 (2026-10-09), 992f3cbc69 (2026-10-10), d37c76932f (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/anthropic_messages_payload_oracle_test.go::TestAnthropicMessagesPayloadMatchesPi and 9 more in docs/bug-ledger.json
  • Release note: A before_provider_request replacement that is not an object leaves Anthropic Messages as Pi's { ...value, stream: true } (null, a number or a boolean send {"stream":true}, an array or string its index members), "betas": null sends no anthropic-beta header, array-index keys lead a replacement body as in JavaScript, and a Codex WebSocket body's own type keeps the first position; a Codex payload hook that returns null no longer crashes PiG.

BF-99a06cf

Interactive startup paints the editor and footer first and adds the startup header once the terminal reports…

  • Source: PiG's own defect
  • Symptom: Interactive startup paints the editor and footer first and adds the startup header once the terminal reports its colors or 100 ms pass, as Pi does.
  • Root cause: PiG waited for the colors first, then mounted and painted editor and header in one frame.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 99a06cfc16 (2026-10-09)
  • Tests: coding/cli/interactive_startup_pty_test.go::TestInteractiveStartupPaintsTheEditorBeforeTheHeaderWhenTheTerminalIsSilent, internal/codingagent/extension_header_spacing_test.go::TestStartupHeaderStaysOutOfTheFirstPaintUntilTheColorsArrive
  • Release note: Interactive startup paints the editor and footer first and adds the startup header once the terminal reports its colors or 100 ms pass, as Pi does. On a terminal that never answers the color query, the first frame no longer waits for that timeout.

BF-9af407c

fix(ai): race builds read provider sockets through syscall.Read

  • Source: PiG's own defect
  • Symptom: fix(ai): race builds read provider sockets through syscall.Read
  • Root cause: The one read(2) now lives in readSocketOnce: the raw system call in ordinary builds (socket_read_raw_linux.go), syscall.Read in race builds (socket_read_race_linux.go). The EINTR/EAGAIN loop, the error mapping and every result are unchanged. The monitor-wakeup test moves to a !race file because the race build's read enters a blocking system call by design.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 9af407c10f (2026-10-12)
  • Tests: ai/api_streams_lazy_upstream_test.go::TestLazyAPIEntryPointsRouteToTheirOwnAPIUpstream, ai/fetch_function_openrouter_accessor_test.go::TestOpenRouterImagesAPIForwardsTheKeyedRequestToTheServer, ai/socket_read_linux_test.go::TestSocketReaderMatchesConnRead, ai/socket_read_linux_test.go::TestSocketReaderOrdersThePeersEarlierWrites, ai/socket_read_monitor_linux_norace_test.go::TestSocketWriterHelper, ai/socket_read_monitor_linux_norace_test.go::TestStreamingTransportReadDoesNotWakeTheRuntimeMonitor, ai/tool_choice_codex_upstream_test.go::TestCodexToolChoiceDefaultsToAutoAndForwardsTheOption

BF-9b4312b

A Rust extension build that ends without a compiler diagnostic (cargo or rustc killed, cargo unable to start)…

  • Source: PiG's own defect
  • Symptom: A Rust extension build that ends without a compiler diagnostic (cargo or rustc killed, cargo unable to start) now says how it ended: the exit status or signal, the crate whose rustc was killed, how long cargo ran and whether the build's context had ended, instead of only "cargo build failed".
  • Root cause: A cargo build that was killed or failed to start was reported as "cargo build failed" with the cause only in a log that the owning test deleted. The summary now carries the exit status or signal, the process cargo says did not exit successfully (for example rustc for a crate, with its signal), how long cargo ran and whether the build's context had ended. The log records the same.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 9b4312b71e (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, coding/extension/host/runtimecell/build_failure_report_test.go::TestBuildFailureWithoutWritableLogKeepsTheSummary, coding/extension/host/runtimecell/build_failure_report_test.go::TestRustBuildFailureNamesAKilledBuildAndHowLongItRan, coding/extension/host/runtimecell/build_failure_report_test.go::TestRustBuildFailureSummarizesRustcDiagnostic, coding/extension/host/runtimecell/rust_packed_test.go::TestBuildRustPackedCellReportsHowAKilledCargoEnded and 4 more in docs/bug-ledger.json
  • Release note: A Rust extension build that ends without a compiler diagnostic (cargo or rustc killed, cargo unable to start) now says how it ended: the exit status or signal, the crate whose rustc was killed, how long cargo ran and whether the build's context had ended, instead of only "cargo build failed".

BF-9b4312b-2

The Rust build failure test asserts that a killed rustc is named by its program name, not its full path

  • Source: PiG's own defect
  • Symptom: The Rust build failure test asserts that a killed rustc is named by its program name, not its full path.
  • Root cause: A cargo build that was killed or failed to start was reported as "cargo build failed" with the cause only in a log that the owning test deleted. The summary now carries the exit status or signal, the process cargo says did not exit successfully (for example rustc for a crate, with its signal), how long cargo ran and whether the build's context had ended. The log records the same.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 9b4312b71e (2026-10-11)
  • Tests: coding/extension/host/runtimecell/build_failure_report_test.go::TestBuildFailureWithoutWritableLogKeepsTheSummary, coding/extension/host/runtimecell/build_failure_report_test.go::TestRustBuildFailureNamesAKilledBuildAndHowLongItRan, coding/extension/host/runtimecell/build_failure_report_test.go::TestRustBuildFailureSummarizesRustcDiagnostic, coding/extension/host/runtimecell/rust_packed_test.go::TestBuildRustPackedCellReportsHowAKilledCargoEnded
  • Release note: The Rust build failure test asserts that a killed rustc is named by its program name, not its full path.

BF-9b63b2b

On Windows, the bash tool no longer risks waiting forever after a command exits while a process it started in…

  • Source: PiG's own defect
  • Symptom: On Windows, the bash tool no longer risks waiting forever after a command exits while a process it started in the background still holds the output pipe.
  • Root cause: The bash tool on Windows puts the shell in a job object. When the shell exits, processGroupMayHoldOutput asks Windows whether the job still has processes (QueryInformationJobObject, JobObjectBasicAccountingInformation). If the answer is zero, childwait.waitIdle waits for the output pipe to reach EOF with no further bound, because an empty job means no process can still hold the pipe.
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: 2e0f88f794 (2026-10-06, #160), 65b7c13e09 (2026-10-06, #168), a4753ec61e (2026-10-06, #170), ba9c37b247 (2026-10-06, #171), 9b63b2bc4e (2026-10-07, #176), ce8587ef40 (2026-10-07, #179)
  • Tests: coding/cli/rpc_initial_tools_test.go::TestRPCFreshPromptDeclaresStructuredSystemOnce, coding/cli/rpc_shutdown_test.go::TestRPCInputEndAfterExtensionCommandComparedWithPi, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleBusyEntriesAreSkippedNotErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledBeforeStartTouchesNothing, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledDuringTheLastEntryReportsTheCancellation, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleEntryLeasedAfterClassificationIsSkippedNotAnError, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleOtherErrorsStayErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleStopsWhenItsContextIsCancelled and 88 more in docs/bug-ledger.json
  • Release note: On Windows, the bash tool no longer risks waiting forever after a command exits while a process it started in the background still holds the output pipe. The check that asks Windows whether the command's job still has processes could read an empty answer when Go moved the goroutine stack during the call, and the tool then waited for the pipe to close. The buffers these job-object calls pass to Windows now stay in place.

BF-9b63b2b-2

A running ! command or bash tool command no longer outlives pig when the session ends because its…

  • Source: PiG's own defect
  • Symptom: A running ! command or bash tool command no longer outlives pig when the session ends because its terminal closed or pig crashed.
  • Root cause: The bash tool on Windows puts the shell in a job object. When the shell exits, processGroupMayHoldOutput asks Windows whether the job still has processes (QueryInformationJobObject, JobObjectBasicAccountingInformation). If the answer is zero, childwait.waitIdle waits for the output pipe to reach EOF with no further bound, because an empty job means no process can still hold the pipe.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a4753ec61e (2026-10-06, #170), 9b63b2bc4e (2026-10-07, #176)
  • Tests: coding/cli/rpc_initial_tools_test.go::TestRPCFreshPromptDeclaresStructuredSystemOnce, coding/extension/host/runtimecell/hash_inputs_test.go::TestCommandVersionCachesWarmProbeAndDetectsReplacement, coding/extension/host/subprocess/node_recovery_test.go::TestNodeUnattributableCrashRestartsWholeGroup, internal/codingagent/exit_record_test.go::TestBackgroundGroupPanicReachesTheHandlerWithItsStack, internal/codingagent/exit_record_test.go::TestBackgroundTaskPanicEndsTheSessionWithACrashRecord, internal/codingagent/exit_record_test.go::TestCacheWarmerDecisionPanicKeepsPisDecision, internal/codingagent/exit_record_test.go::TestCacheWarmerRefreshPanicOutsideTheCaughtRequestReachesTheUncaughtHandler, internal/codingagent/exit_record_test.go::TestCacheWarmerRefreshRequestPanicIsCaughtAndReschedules and 16 more in docs/bug-ledger.json
  • Release note: A running ! command or bash tool command no longer outlives pig when the session ends because its terminal closed or pig crashed. The command runs in its own process group, so the terminal's hangup never reached it, and pig now kills it before it exits, as Pi does.

BF-9ba2589

fix(agent): write a decoded message as the object it was decoded from

  • Source: PiG's own defect
  • Symptom: fix(agent): write a decoded message as the object it was decoded from
  • Root cause: Pi keeps the object JSON.parse returned for a session line or an extension result and every writer stringifies it, so a message whose members are in another order, or which holds members PiG does not model, comes back unchanged from RPC get_messages, extension events and buildSessionContext. PiG re-encoded the decoded fields in its own order.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 9ba2589119 (2026-10-10)
  • Tests: agent/message_source_test.go::TestDecodedMessageInPiOrderRetainsNoText, agent/message_source_test.go::TestDecodedMessageWritesTheObjectItWasDecodedFrom, agent/message_source_test.go::TestWithEmptyContentSpreadsContent, agent/tool_result_fields_test.go::TestToolResultMessageFieldsMatchUpstream

BF-9be5765

Pressing Escape while an extension tool runs now lets the tool finish: a tool that stops on its abort signal…

  • Source: A Pi defect that PiG fixed first (divergence D111)
  • Symptom: Every extension tool call failed at once with "context canceled".
  • Root cause: On abort, the host cancelled the tool_call request and returned "context canceled" at once, so even a tool that honours its AbortSignal lost its own result. Pi's executePreparedToolCall awaits tool.execute and keeps its result or thrown message (agent-loop.ts:831-853); a tool that ignores the signal holds Pi's run forever.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: none recorded
  • Removed when Pi fixes it: Pi stops awaiting aborted tool calls.
  • Fix commits: 101fe6be1c (2026-10-09), 2bdd5278de (2026-10-09), 6b51bbc36c (2026-10-09), 9be576533f (2026-10-09)
  • Tests: coding/cli/rpc_tool_abort_grace_test.go::TestRPCAbortedExtensionToolSettlesAndFreesTheSession, coding/extension/host/subprocess/liveness_test.go::TestCancelledToolCallWaitsTheGraceForItsAnswer, coding/extension/host/subprocess/liveness_test.go::TestRequestCancellationWinsOverLateResponse, coding/extension/host/subprocess/tool_abort_grace_test.go::TestAbortedToolCallSettlesCooperativelyOrFailsAfterTheGrace, test/extension-conformance/tool_abort_grace_test.go::TestConformance_AbortedToolSettlesWithinTheGrace
  • Release note: Pressing Escape while an extension tool runs now lets the tool finish: a tool that stops on its abort signal reports its own result or error, as in Pi. A tool that ignores the signal fails with "Operation aborted" after 3 seconds, and the session becomes usable again; Pi waits for such a tool forever (D111). Before, every extension tool call failed at once with "context canceled".

BF-9c1a0e9

An OpenAI Responses stream event or output item with a member of an unexpected type is read as Pi's…

  • Source: PiG's own defect
  • Symptom: An OpenAI Responses stream event or output item with a member of an unexpected type is read as Pi's JavaScript reads it instead of ending the turn with openai-responses: invalid SSE JSON or silently skipping the item: a delta appends its string form (null and a missing delta append null and undefined), a finished message or reasoning item joins its parts' string forms and ends the turn with item.content?.map is not a function (or item.summary?...) when they are not an array, a function call's id interpolates a non-string call_id, non-string function-call arguments end the turn with Pi's partialJson.trim is not a function or startsWith error, a usage that is not an object reads as zeros, a status that is not a string ends the turn with Unhandled stop reason: <its string form>, and a failed response's non-string error code and message are written in their string form.
  • Root cause: google-generative-ai.ts:236-237 reads output: (usageMetadata.candidatesTokenCount || 0) + (usageMetadata.thoughtsTokenCount || 0), so true adds 1 and the usage stays numeric. PiG's JS read of a mistyped chunk refused a truthy non-number count and ended the turn with the decode error. normalizeGeminiUsage now reads a true candidates count as 1; every other count is stored as read (a truthy non-number thoughts, total or cached count still ends the turn, because Pi stores it raw). The prompt count is only subtracted from and already counted as its Number().
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 366969bbae (2026-10-11), 5078f73100 (2026-10-11), 5932542c21 (2026-10-11), 71e9d13eb9 (2026-10-11), 82414338e7 (2026-10-11), 8eb1202528 (2026-10-11), 9c1a0e938d (2026-10-11), b6afa7be1a (2026-10-11), db9a761dd8 (2026-10-11), e098463b19 (2026-10-11), e1abc6c615 (2026-10-11), f1a59f9161 (2026-10-11)
  • Tests: ai/anthropic_event_member_type_oracle_test.go::TestAnthropicEventMistypedMembersMatchPi, ai/google_chunk_member_type_oracle_test.go::TestGoogleChunkMistypedMembersMatchPi, ai/mistral_chunk_member_type_oracle_test.go::TestMistralChunkMistypedMembersMatchPi, ai/openai_completions_member_type_oracle_test.go::TestOpenAICompletionsMistypedMembersMatchPi, ai/openai_responses_member_type_oracle_test.go::TestOpenAIResponsesItemPiStoresRawEndsTheTurnWithTheDecodeError, ai/openai_responses_member_type_oracle_test.go::TestOpenAIResponsesMistypedMembersMatchPi, ai/openai_responses_response_oracle_test.go::TestProcessResponsesStreamEdgeEventsMatchPi, ai/utils_exports_upstream_test.go::TestTextSignatureV1WireShapeAndParsing and 7 more in docs/bug-ledger.json
  • Release note: An OpenAI Responses stream event or output item with a member of an unexpected type is read as Pi's JavaScript reads it instead of ending the turn with openai-responses: invalid SSE JSON or silently skipping the item: a delta appends its string form (null and a missing delta append null and undefined), a finished message or reasoning item joins its parts' string forms and ends the turn with item.content?.map is not a function (or item.summary?...) when they are not an array, a function call's id interpolates a non-string call_id, non-string function-call arguments end the turn with Pi's partialJson.trim is not a function or startsWith error, a usage that is not an object reads as zeros, a status that is not a string ends the turn with Unhandled stop reason: <its string form>, and a failed response's non-string error code and message are written in their string form. OpenAI Codex streams read their events the same way. A missing or null output item ends the turn with Pi's Cannot read properties of ... (reading 'type') error, a message's text signature records its id and any non-empty phase as Pi's JSON.stringify writes them, and an output item whose mistyped member Pi keeps as-is ends the turn with the decode error instead of being dropped from an otherwise successful answer.

BF-9c98038

An unclosed $$ or \[ display-math block no longer swallows or corrupts every Markdown table after it

  • Source: A fix Pi shipped that PiG ported
  • Symptom: An unclosed $$ or \[ display-math block no longer swallows or corrupts every Markdown table after it.
  • Root cause: Port Pi (packages/tui/src/components/markdown.ts containsTableStart, TABLE_DELIMITER_ROW_REGEX, tokenizeBlockLatex, #11159): a block-math token no longer spans a GFM table and the pending-math fallback skips one; a pending [ also needs a non-empty body. The eight upstream 'unclosed display math before tables' cases are ported verbatim, with a live-Pi comparison of them and their neighbours.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 9c980381eb (2026-10-20)
  • Tests: tui/markdown_latex_table_test.go::TestMarkdownContainsTableStart, tui/markdown_latex_table_test.go::TestMarkdownUnclosedDisplayMathBeforeTables, tui/markdown_latex_table_test.go::TestMarkdownUnclosedDisplayMathBeforeTablesMatchesPi
  • Release note: An unclosed $$ or \[ display-math block no longer swallows or corrupts every Markdown table after it. A display-math block never spans a table, and the streaming pending-math fallback no longer treats a following table as pending math (Pi 2ed3118b, #11159).

BF-9d4fe9c

Selecting a model or cycling models with Ctrl+P no longer freezes the interactive mode when an extension's…

  • Source: PiG's own defect
  • Symptom: Selecting a model or cycling models with Ctrl+P no longer freezes the interactive mode when an extension's model_select handler registers a tool.
  • Root cause: Pi's shutdown ends with process.exit and does not await a pending model_select (interactive-mode.ts:4251-4291). PiG's notification Emit ran on the Session's background context, so Stop's backgroundTasks.Wait joined a slow or hung handler. /quit, Ctrl+D or a signal therefore tore the UI down but did not exit.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 71d96b9841 (2026-10-12), 778d3b9398 (2026-10-12), 9d4fe9ce3d (2026-10-12), c21b118fc8 (2026-10-12), f059079215 (2026-10-12)
  • Tests: coding/session_model_extension_order_test.go::TestBeginModelCycleOnMainDefersNotificationsToTheCompletion, coding/session_model_extension_order_test.go::TestModelChangeOnMainNotificationsEndWithTheirContext, coding/session_model_owner_test.go::TestSetModelOnMainDispatchesAllStateBeforeNotifications, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, internal/codingagent/interactive_model_input_live_test.go::TestInteractiveModelChangeKeepsInputLive and 6 more in docs/bug-ledger.json
  • Release note: Selecting a model or cycling models with Ctrl+P no longer freezes the interactive mode when an extension's model_select handler registers a tool. As in Pi, the mode no longer waits for the handlers, whether the change comes from Ctrl+P, the model selector or a typed /model. The new model applies at once, and keys typed while the handlers run reach the focused component: the model selector, which stays open until the change settles (Escape closes it at once), or the editor. A prompt submitted meanwhile starts at once on the new model. A second change applies its model at once, and its handlers run alongside the first change's. An extension dialog opened meanwhile closes the selector, so later keys reach the editor. Quitting while a handler still runs no longer waits for it, and a failed model switch shows the error's message, as Pi does.

BF-9d64980

fix(coding): provider order and availability of virtual models follow Pi

  • Source: PiG's own defect
  • Symptom: fix(coding): provider order and availability of virtual models follow Pi
  • Root cause: A provider of only virtual models keeps the place of its ID in providerIds order (a loaded models.json lists it even when it composes to nothing; a failed one defines nothing), allModels and allAvailable place its models there, and the virtual models of a provider follow that provider's credentials, so an extension provider without a key no longer exposes them.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: 9d64980afc (2026-10-10)
  • Tests: None recorded.

BF-9f4702c

A failed extension shortcut handler is now reported as Error: Shortcut handler error: … in the error…

  • Source: PiG's own defect
  • Symptom: Previously it was shown as a warning.
  • Root cause: PiG used showWarning ("Warning: ...").
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 9f4702c4e1 (2026-10-13)
  • Tests: internal/codingagent/interactive_command_error_test.go::TestInteractiveShortcutDiagnosticOwnership
  • Release note: A failed extension shortcut handler is now reported as Error: Shortcut handler error: … in the error color, as in Pi. Previously it was shown as a warning.

BF-D110

Git sources: #subdirectory= selects a subdirectory; git:// and upper-case schemes yield usable…

  • Source: A Pi defect that PiG fixed first (divergence D110)
  • Symptom: for (2) and (3) Pi's parseGitUrl returns a repository no Git client can clone (https:////host/org/repo, https://HTTPS://host/org/repo), so pi install of such a source fails at clone time.
  • Root cause: (1) is additive: @<commit>#subdirectory=<path> is how PiG installs a Piglet that lives in a repository subdirectory, and no Pi input is rejected for it. (2) and (3) are Pi results that no Git client can clone; PiG returns the repository the user wrote.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Pi issue: none recorded
  • Removed when Pi fixes it: Pi accepts a #subdirectory= spelling or PiG stops installing Piglets from repository subdirectories (1); Pi fixes the git:// and upper-case scheme repositories (2, 3) or parses git:file:// sources (4).
  • Fix commits: 478157bb22 (2026-10-09), 9be576533f (2026-10-09), eda54e987b (2026-10-09)
  • Tests: coding/source/parse_git_oracle_test.go::TestParseGitMatchesPi, coding/source/ref_test.go::TestParseGitFragmentIsARefUnlessItIsSubdirectory, coding/source/ref_test.go::TestParseGitSubdirectory, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode

BF-D150

--no-stdin skips the startup read of piped stdin (Pi #10415)

  • Source: A Pi defect that PiG fixed first (divergence D150)
  • Symptom: pig --no-stdin -p "prompt" with an open stdin pipe runs the prompt; Pi hangs. Pi parses --no-stdin as an unknown extension flag.
  • Root cause: pi -p hi < <(sleep 15) never starts: readPipedStdin (packages/coding-agent/src/main.ts:80-97) waits for end on a pipe whose writer stays open, and the same happens in PiG. An idle open pipe cannot be told from a slow producer, so a timeout would drop real input; an explicit opt-out is the only fix that cannot lose piped input. Callers that keep an input pipe open (agent harnesses, CI wrappers) pass the flag. Pi has no fix: Pi #10415 has an unmerged --no-stdin proposal and no PR. Pi #5571 discusses the same hang.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: Unreleased
  • Pi issue: https://github.com/earendil-works/pi/issues/10415
  • Removed when Pi fixes it: Pi accepts --no-stdin or otherwise lets a caller start with an open stdin pipe, or fixes #10415.
  • Fix commits: 644b3e0a2a (2026-10-09), 0225a43aa5 (2026-10-10)
  • Tests: coding/cli/no_stdin_test.go::TestNoStdinStartsWithAnOpenInputPipe, coding/cli/no_stdin_test.go::TestParseArgsNoStdin

BF-D151

grep with context reads a window of each matched file (Pi #9276)

  • Source: A Pi defect that PiG fixed first (divergence D151)
  • Symptom: memory use of a grep call follows the context size, not the file size. A custom GrepOperations.ReadFile still returns the whole text, but only the current file is kept. A custom GrepOperations without ReadFile streams the local file.
  • Root cause: with context: 2 over a directory of 100 MB logs, Pi holds each matched file several times over (read string, split array) and exhausts the heap (Pi #9276). Pi has no fix. The output is the same: the window splits lines as getFileLines does, keeps the first GrepMaxLineLength + 1 code points of a line (the rest cannot reach the output), and still prints overlapping context blocks for nearby matches.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Pi issue: https://github.com/earendil-works/pi/issues/9276
  • Removed when Pi fixes it: Pi stops loading whole files for grep context.
  • Fix commits: 2a78d0efdf (2026-10-09), 644b3e0a2a (2026-10-09), e98c8af606 (2026-10-09)
  • Tests: internal/codingagent/tools/grep_context_memory_test.go::TestGrepContextDoesNotLoadTheMatchedFile, internal/codingagent/tools/grep_context_memory_test.go::TestGrepLineWindowMatchesTheWholeFileSplit

BF-D153

The bash truncation notice names the size of the last line, not the bytes after the final newline (Pi #10015)

  • Source: A Pi defect that PiG fixed first (divergence D153)
  • Symptom: PiG prints line is 58.6KB; Pi prints line is 0B. Output without a trailing newline is the same in both.
  • Root cause: The tail it shows belongs to line 1, which is 58.6KB. Pi has no fix (Pi #10015 is open, with no PR).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Pi issue: https://github.com/earendil-works/pi/issues/10015
  • Removed when Pi fixes it: Pi reports the size of the last counted line.
  • Fix commits: 983d651097 (2026-10-09)
  • Tests: internal/codingagent/tools/last_line_bytes_test.go::TestBashLongLastLineNoticeNamesTheLineSize, internal/codingagent/tools/last_line_bytes_test.go::TestOutputAccumulatorLastLineBytes

BF-D155

Selection lists page with PageUp and PageDown (Pi #7629)

  • Source: A Pi defect that PiG fixed first (divergence D155)
  • Symptom: in a selector, PageDown moves the selection down one page and does not scroll the transcript behind it. Pi scrolls the transcript or does nothing. An open editor autocomplete list pages the same way, and a user binding of the same key to an app action does not fire while the list is open. Extension-provided overlays cannot yet report that they capture the page keys, so the viewport still takes them there.
  • Root cause: the selection stays where the user is looking. Pi #7629 is open, and its PR #7680 (packages/tui/src/tui-alt-screen.ts, getPageSelectionIndex, capturesSelectPageInput) is unmerged.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Pi issue: https://github.com/earendil-works/pi/issues/7629
  • Removed when Pi fixes it: Pi merges PR #7680 or another change that pages selection lists on these keys; then restore the keys in the oracle generators.
  • Fix commits: 68a4ed2b54 (2026-10-09), 0d867c440e (2026-10-10)
  • Tests: internal/codingagent/fullscreen_modal_viewport_test.go::TestFullscreenSelectionKeepsItsPageKeys, internal/codingagent/llama/ui_test.go::TestHuggingFaceSearchPageKeysMoveTenResults, internal/codingagent/modal_viewport_input_test.go::TestEditorSlotSelectorPageKeyMovesSelectionInEveryMode, tui/select_page_keys_test.go::TestAltScreenGivesSelectionPageKeysToACapturingFocus, tui/select_page_keys_test.go::TestEditorAutocompletePageKeys, tui/select_page_keys_test.go::TestPageSelectionIndex, tui/select_page_keys_test.go::TestSelectListPageKeysMoveByAPage, tui/select_page_keys_test.go::TestSelectorComponentsMoveByTheirPage and 1 more in docs/bug-ledger.json

BF-D156

MCP server url expands environment references (Pi #10654)

  • Source: A Pi defect that PiG fixed first (divergence D156)
  • Symptom: the /mcp server menu adds a connects to: <expanded url> line under a url that expands to something else (owner-approved; the expansion can contain a secret from the environment, and the menu shows it). An entry whose url is only a reference loads in PiG and is rejected by Pi. An entry whose url contains $$, or $NAME or ${NAME} with NAME set, connects to the expanded URL in PiG and to the literal text in Pi. The stored config and getMcpServers keep the URL as written, and the server list shows it as written.
  • Root cause: The reporter asks for the URL to be resolved with the same logic as headers and env values. Pi #10654 is open with no PR.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: Unreleased
  • Pi issue: https://github.com/earendil-works/pi/issues/10654
  • Removed when Pi fixes it: Pi expands the url of an MCP server.
  • Fix commits: b06ad928d7 (2026-10-09), 0c8e8b07be (2026-10-10), 922bd78903 (2026-10-10)
  • Tests: coding/extension/mcp_url_expansion_test.go::TestExpandMcpURL, coding/extension/mcp_url_expansion_test.go::TestValidateMcpServerConfigChecksTheExpandedURL, coding/mcpext/connection_test.go::TestMCPConnectionsExpandTheServerURLReferences, coding/mcpext/manager_test.go::TestMcpManagerServerMenuShowsTheExpandedURL

BF-D195

An empty AWS_BEARER_TOKEN_BEDROCK is no token, where Pi's AWS SDK treats the variable as set and the request…

  • Source: A Pi defect that PiG fixed first (divergence D195)
  • Symptom: the failure is an AWS JavaScript SDK side effect that Pi's own getProviderEnvValue does not intend. Pi decides "no token" and the SDK decides "bearer".
  • Root cause: an empty variable names no credential. Failing every request because an unset secret was exported as an empty string breaks a working IAM or profile setup for no reason a user can act on, and PiG's own lookup, like Pi's, already treats the value as absent. The Go SDK PiG uses agrees: resolveEnvBearerToken returns on an empty value (third_party/awssdk/service/bedrockruntime/api_client.go:980-984).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Pi issue: none recorded
  • Removed when Pi fixes it: Pi or its AWS SDK treats an empty AWS_BEARER_TOKEN_BEDROCK as unset (Pi passes authSchemePreference itself, or the SDK tests for a non-empty value), or an owner decision reverses this one; then the marker, the D195 cases of the test and this record go.
  • Fix commits: cca788e454 (2026-10-24)
  • Tests: ai/bedrock_bearer_divergence_test.go::TestBedrockBearerTokenDivergencesFromPi, ai/bedrock_request_body_oracle_test.go::TestBedrockRequestBodyMatchesPi

BF-D196

Under skip-auth a bearer token from the process environment is not sent over an http:// endpoint, where Pi's…

  • Source: A Pi defect that PiG fixed first (divergence D196)
  • Symptom: Pi sends a credential over plain HTTP because the JavaScript SDK has no HTTPS check for bearer tokens, and its skip-auth option does not stop the SDK from picking up the token.
  • Root cause: the refusal keeps a bearer token off an unencrypted connection. Pi's behavior sends it in clear text to a host the user chose; PiG does not override the Go SDK's guard to repeat that.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: Unreleased
  • Pi issue: none recorded
  • Removed when Pi fixes it: Pi refuses to send a bearer token over HTTP (its SDK adds an HTTPS check, or Pi installs a signer that does), or PiG installs bedrockBearerSigner under skip-auth and the owner approves sending the token over HTTP there; then the markers, the D196 case of the test and this record go.
  • Fix commits: cca788e454 (2026-10-24)
  • Tests: ai/bedrock_bearer_divergence_test.go::TestBedrockBearerTokenDivergencesFromPi

BF-D198

The Go, Rust and Python Type has no Codec family (Codec, Decode, Encode, DecodeBuilder…

  • Source: A Pi defect that PiG fixed first (divergence D198)
  • Symptom: none.
  • Root cause: a Codec's callbacks have no consumer outside Value.Decode and Value.Encode (typebox/build/value/codec/callback.mjs, the only reader of ~codec), which pi-ai does not export (ai/src/index.ts exports Static, TSchema and Type from typebox) and no SDK serves. Pi's own tool-argument validation does not call them (ai/src/utils/validation.ts uses Value.Convert and Compile(schema).Check). A port could store the callbacks, but nothing in any SDK or in the host could ever call them, which is an inert member that accepts the call and does nothing.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Pi issue: none recorded
  • Removed when Pi fixes it: an SDK serves Value.Decode/Value.Encode (or a host call runs a codec) so a stored callback pair has a consumer.
  • Fix commits: a8c3cdfb85 (2026-10-24), eb9071495b (2026-10-25)
  • Tests: extensions/sdk/type_vectors_test.go::TestTypeOmitsTheCodecFamily, test/extension-conformance/type_schema_registration_test.go::TestConformance_TypeSchemaToolParametersAgreeAcrossSDKs

BF-D200

With no home directory PiG still runs the commands that load no resources, where Pi crashes at import, and…

  • Source: A Pi defect that PiG fixed first (divergence D200)
  • Symptom: none claimed; Pi's behaviour is a crash at load rather than a reported error, and is kept for the paths that need a home directory.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: Unreleased
  • Pi issue: none recorded
  • Removed when Pi fixes it: Pi stops resolving the home directory at import (so --version and list run there without one), or an owner decision reverses this one; then PiG's startup fails at the same point as Pi's, the markers, the allowance test and this record go.
  • Fix commits: 09c5680c80 (2026-10-24), 99f0027ef1 (2026-10-24)
  • Tests: coding/cli/no_home_directory_test.go::TestNoHomeDirectoryPiCrashesAtImportWherePiGRunsResourceFreeCommands, coding/cli/no_home_directory_test.go::TestStartupFailsWhereNoHomeDirectoryExists, coding/cli/no_home_directory_test.go::TestStartupFollowsNodeWhenThereIsNoHomeDirectory

BF-D203

registerProvider with an empty apiKey leaves the provider unconfigured where Pi treats the empty string as…

  • Source: A Pi defect that PiG fixed first (divergence D203)
  • Symptom: the inconsistency above: "" is a configured key to configuredApiKey, configuredRequestAuthStatus and the composed check, and a missing one to provider-composer.ts:318.
  • Root cause: Pi's behaviour is an accident of ?? on a string. The empty string is not a form the option documents (packages/coding-agent/src/core/extensions/types.ts:1936: "API key literal, env interpolation ($ENV_VAR or ${ENV_VAR}), or leading !command"), and Pi treats "" as missing where it validates the same option (provider-composer.ts:318, !config.apiKey). Its effect is a provider that reads configured and available whose every request fails with No API key for provider: <id> (for example packages/ai/src/api/openai-completions.ts:95), so an extension that passes process.env.KEY ?? "" gets a selectable provider that cannot answer. Distinguishing "" from an absent key needs presence…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Pi issue: none recorded
  • Removed when Pi fixes it: Pi treats "" as missing in configuredApiKey, or the SDK API gains explicit presence for apiKey (a pointer or an option) and an owner decision asks for Pi's behaviour; then the three markers, the test and this record go.
  • Fix commits: 90ffbef9d1 (2026-10-25)
  • Tests: coding/register_provider_empty_apikey_test.go::TestRegisterProviderWithEmptyAPIKeyIsNotConfigured

BF-a076a50

A model request on a reused connection gets the full HTTP idle timeout to receive its first response bytes…

  • Source: PiG's own defect
  • Symptom: The time the connection had spent idle in the pool was deducted from the next request's timeout.
  • Root cause: With a 3 s idle timeout, a POST on a connection idle for 2 s whose headers take 2 s failed with "fetch failed", 3 of 3. A GET hid it, because net/http replays an idempotent request on a fresh connection. A model POST whose body was written is not replayed.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): a076a50576 (2026-10-10)
  • Tests: ai/http_transport_reuse_test.go::TestReusedStreamingConnectionGivesTheNextRequestTheFullIdleTimeout
  • Release note: A model request on a reused connection gets the full HTTP idle timeout to receive its first response bytes, as in Pi. Before, the time the connection had spent idle in the pool was deducted from the next request's timeout.

BF-a09da26

pi.getSettings() in a subprocess extension lists the settings in Pi's order: the global settings file's…

  • Source: PiG's own defect
  • Symptom: pi.getSettings() in a subprocess extension lists the settings in Pi's order: the global settings file's members as written (after the legacy-key migration), then members only the project file sets, with nested objects merged the same way and setters and overrides adding members after the others.
  • Root cause: settings.go (mapkeyorder): pi.getSettings() is structuredClone(this.settings), deepMergeSettings of the migrated global and project file objects (settings-manager.ts:195-212, 566-568), so a Node extension sees the files' member order. The state push sorted them (json.Marshal of a map). The manager now keeps each layer's object as Pi holds it (the migrated file object, patched in place by setters as persistScopedSettings patches the file), merges them with deepMergeObjects semantics, and encodes the extension settings in that order (orderedjson.MarshalInSourceOrder). Interactive mode binds getSettingsJSON too. The typed-Settings-to-object decode stays: allowed as the conversion mechanic…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a09da26616 (2026-10-13)
  • Tests: coding/session_settings_json_test.go::TestSessionSettingsJSONIsTheEncodingOfGetSettings, internal/codingagent/settings_extension_json_test.go::TestExtensionSettingsJSONEncodesOncePerSettingsGeneration, internal/codingagent/settings_extension_json_test.go::TestExtensionSettingsJSONKeepsPiMemberOrder, internal/codingagent/settings_shared_read_test.go::TestSettingsReadHonorsPiWriterLock, internal/codingagent/tool_render_shell_details_test.go::TestShellCardIgnoresACaseVariantTruncatedBy, internal/codingagent/tool_render_shell_details_test.go::TestShellDetailsFromReadsTheExactKeys
  • Release note: pi.getSettings() in a subprocess extension lists the settings in Pi's order: the global settings file's members as written (after the legacy-key migration), then members only the project file sets, with nested objects merged the same way and setters and overrides adding members after the others. PiG sorted them alphabetically.

BF-a09da26-2

A bash or PowerShell tool card reads the result's truncation details by their exact keys, as Pi does: a…

  • Source: PiG's own defect
  • Symptom: A bash or PowerShell tool card reads the result's truncation details by their exact keys, as Pi does: a member such as TRUNCATEDBY no longer stands in for truncatedBy.
  • Root cause: settings.go (mapkeyorder): pi.getSettings() is structuredClone(this.settings), deepMergeSettings of the migrated global and project file objects (settings-manager.ts:195-212, 566-568), so a Node extension sees the files' member order. The state push sorted them (json.Marshal of a map). The manager now keeps each layer's object as Pi holds it (the migrated file object, patched in place by setters as persistScopedSettings patches the file), merges them with deepMergeObjects semantics, and encodes the extension settings in that order (orderedjson.MarshalInSourceOrder). Interactive mode binds getSettingsJSON too. The typed-Settings-to-object decode stays: allowed as the conversion mechanic…
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: a09da26616 (2026-10-13)
  • Tests: coding/session_settings_json_test.go::TestSessionSettingsJSONIsTheEncodingOfGetSettings, internal/codingagent/settings_extension_json_test.go::TestExtensionSettingsJSONEncodesOncePerSettingsGeneration, internal/codingagent/settings_extension_json_test.go::TestExtensionSettingsJSONKeepsPiMemberOrder, internal/codingagent/settings_shared_read_test.go::TestSettingsReadHonorsPiWriterLock, internal/codingagent/tool_render_shell_details_test.go::TestShellCardIgnoresACaseVariantTruncatedBy, internal/codingagent/tool_render_shell_details_test.go::TestShellDetailsFromReadsTheExactKeys
  • Release note: A bash or PowerShell tool card reads the result's truncation details by their exact keys, as Pi does: a member such as TRUNCATEDBY no longer stands in for truncatedBy.

BF-a110f68

fix(tui,codingagent): owner-loop frame deadline and reader-side input routing (TU-T11, TU-T12)

  • Source: PiG's own defect
  • Symptom: fix(tui,codingagent): owner-loop frame deadline and reader-side input routing (TU-T11, TU-T12)
  • Root cause: Throttled frames keep their deadline on one reusable owner-loop timer (SetRenderDeadline/RunDueRender) instead of an AfterFunc goroutine per frame. The terminal reader frames input and routes it to the owner itself; the last chunk of a read goes out without waiting for its ticket, which settles into one per-router channel, and the read boundary travels on the chunk (more) instead of a readDone channel.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: a110f6800b (2026-10-13)
  • Tests: None recorded.

BF-a13bae4

A read result shown in the transcript no longer keeps the file text a second time as its structured content

  • Source: PiG's own defect
  • Symptom: A read result shown in the transcript no longer keeps the file text a second time as its structured content.
  • Root cause: A finished tool card keeps its result for its renderers, and its body renderer closure keeps it too, for as long as the transcript shows the card. A read result's structured content is the text again as a JSON string (toReadOutput, read.ts), so every read card held its text twice, once escaped. In Pi structuredContent is the same string as the text, so the card holds it once.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 06c408c21d (2026-10-11), 8ab5a89874 (2026-10-11), a13bae4664 (2026-10-11), eab3caf769 (2026-10-11)
  • Tests: internal/codingagent/session_store_test.go::TestAppendedMessageIsHeldOnceWhenItsRecordDecodesToIt, internal/codingagent/session_store_test.go::TestAppendedToolResultWithTypedDetailsIsHeldOnce, internal/codingagent/tool_card_structured_test.go::TestReadCardKeepsTheTextOnceAndRenderersGetStructuredContent, internal/codingagent/tool_card_structured_test.go::TestToolCardKeepsStructuredContentItCannotDerive, internal/codingagent/tools/truncation_share_test.go::TestTruncatedResultDetailsShareTheReturnedText
  • Release note: A read result shown in the transcript no longer keeps the file text a second time as its structured content. Renderers, including extension renderers, still receive the structured content the tool returned. In a 75-prompt session of file reads (about 4.2M tokens of context), memory at the end drops by another 20 MB or so.

BF-a156ef3

On Linux, clipboard reads accept every DISPLAY spelling Pi's xcb_connect accepts: white space or a sign…

  • Source: PiG's own defect
  • Symptom: Previously these spellings failed, and image and text paste reported the clipboard as unavailable.
  • Root cause: Pi opens the clipboard with xcb_connect (linux-platform-x11.c:185), whose parser reads the display and screen with C strtoul into an int (libxcb 1.15 xcb_util.c:176, :186): leading white space and a sign are accepted, a minus negates modulo 2^64, an overflow saturates, the int keeps the low 32 bits, and a network display's port is the low 16 bits of 6000 plus the number (:244). Pi then steps past screen_number screens (linux-platform-x11.c:188), so a negative screen selects the first. PiG used strconv.Atoi and rejected negative numbers and TCP displays above 59535, so DISPLAY=": 1", ":1.-1" or "host:65536+n" failed where Pi connects.
  • User impact: An operation failed or was rejected
  • Platform: Linux
  • Fixed in: 0.5.0
  • Fix commits: 74b56b5573 (2026-10-13), a156ef3cfb (2026-10-13)
  • Tests: internal/nativeplatform/clipboard_x11_display_linux_test.go::TestX11DisplayTransportPrefixes, internal/nativeplatform/clipboard_x11_display_range_linux_test.go::TestX11DisplayNumbersParseAsXcbDoes, internal/nativeplatform/clipboard_x11_display_range_linux_test.go::TestX11DisplaySpellingsConnectAsPiDoes
  • Release note: On Linux, clipboard reads accept every DISPLAY spelling Pi's xcb_connect accepts: white space or a sign before the display or screen number (: 1, :+1), a negative screen, which selects the first screen, and display numbers that wrap as libxcb's strtoul and 16-bit port do (host:65536 reaches port 6000). Previously these spellings failed, and image and text paste reported the clipboard as unavailable.

BF-a178939

The Go and Python serializeConversation keep an unpaired UTF-16 surrogate in a message, a tool-call name or…

  • Source: PiG's own defect
  • Symptom: The Go and Python serializeConversation keep an unpaired UTF-16 surrogate in a message, a tool-call name or argument, and when the 2,000-unit cut splits a surrogate pair, as Pi does; the conformance test now compares those cases against Pi's own code.
  • Root cause: A 6,000-message differential against Pi's own serializeConversation (node + dist) now has 0 differences in Go and Python. Before the fix it had 53 in Go and 33 in Python. The conformance row cannot see this, because the harness decodes reports with encoding/json, which turns every lone surrogate into U+FFFD.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 058e7a0e4b (2026-10-11), 15cb4f2db8 (2026-10-11), 19966b4089 (2026-10-11), 50f526d69e (2026-10-11), a178939d6a (2026-10-11), a4e5b071cb (2026-10-11), e4aa58a078 (2026-10-11), e8473d13c5 (2026-10-11), f51d8b4bea (2026-10-11)
  • Tests: extensions/sdk-py/tests/test_compaction_estimate.py, extensions/sdk-py/tests/test_serialize_conversation_surrogates.py, extensions/sdk/compaction_estimate_test.go::TestEstimateTokensCountsUTF16UnitsAndJSONStringifyLength, extensions/sdk/compaction_estimate_test.go::TestJSNumberStringMatchesNumberToString, extensions/sdk/serialize_conversation_surrogate_test.go::TestSerializeConversationKeepsUnpairedSurrogates, test/extension-conformance/compaction_usage_test.go::TestConformance_CompactionUsageHelpersAgreeAcrossSDKs, test/extension-conformance/host_parser_helpers_test.go::TestConformance_HostParserHelpersAgreeAcrossSDKs, test/extension-conformance/message_conversion_test.go::TestConformance_MessageConversionAgreesAcrossSDKs and 2 more in docs/bug-ledger.json
  • Release note: The Go and Python serializeConversation keep an unpaired UTF-16 surrogate in a message, a tool-call name or argument, and when the 2,000-unit cut splits a surrogate pair, as Pi does; the conformance test now compares those cases against Pi's own code.

BF-a1fa6d1

NO_PROXY entries and ports are trimmed with JavaScript's whitespace rules, as in Pi: an entry or port that…

  • Source: PiG's own defect
  • Symptom: NO_PROXY entries and ports are trimmed with JavaScript's whitespace rules, as in Pi: an entry or port that starts with U+0085 is no longer stripped, and one that starts with U+FEFF is.
  • Root cause: It went red on U+0085: Go's TrimSpace stripped a leading NEL from a NO_PROXY entry (parseNoProxyEntry, node-http-proxy.ts:42 entry.trim()) and from a port (Number.parseInt), where JavaScript does not, so a proxied target was excluded. parseNoProxyEntry uses jsstring.Trim then jsstring.ToLower in Pi's order, proxyPort jsstring.TrimStart (parseInt ignores trailing text), and the two NO_PROXY lowercase calls use jsstring.ToLower. The ToLower swaps on the whole NO_PROXY text and the target host are unobservable here (entries are lowercased again, and the URL parser already gave an ASCII host); they stay as Pi writes them. Trim vs TrimStart on the port is equivalent.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0b4e1e3376 (2026-10-09), 90882933f6 (2026-10-09), a1fa6d1a33 (2026-10-09)
  • Tests: ai/azure_config_oracle_test.go::TestAzureBaseURLNormalizationMatchesPi, ai/codex_vertex_urls_oracle_test.go::TestCodexWebSocketAndVertexVersionURLsMatchPi, ai/node_http_proxy_oracle_test.go::TestResolveHTTPProxyURLForTargetMatchesPiOracle, internal/evals/plan_oracle_test.go::TestPlanMatchesPiOnSeededProbes
  • Release note: NO_PROXY entries and ports are trimmed with JavaScript's whitespace rules, as in Pi: an entry or port that starts with U+0085 is no longer stripped, and one that starts with U+FEFF is.

BF-a238095

coding: fix session executor queue-prefix race and durable execMu waits

  • Source: PiG's own defect
  • Symptom: coding: fix session executor queue-prefix race and durable execMu waits
  • Root cause: The queue overflow prefix was snapshotted from len(rawEvents) while the executor had already received (but not finished) one event, so a reentrant queue update jumped ahead of it. The prefix now counts queued events and shrinks when one finishes.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: a23809539a (2026-10-13)
  • Tests: None recorded.

BF-a3859aa

An OpenAI-compatible provider that streams a tool call without an id or a function name (OpenRouter and GLM…

  • Source: A Pi defect that PiG fixed first (divergence D120)
  • Symptom: An OpenAI-compatible provider that streams a tool call without an id or a function name (OpenRouter and GLM do) no longer leaves the session failing every later turn with tool messages must include a non-empty string tool_call_id.
  • Root cause: Pi openai-completions.ts:513-514 (id: toolCall.id || "") and :1427 (tool_call_id: toolMsg.toolCallId) replay an id-less call and its result with empty ids; providers answer 400. Give the call an id at block end and omit nameless calls and their results on replay.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10299
  • Removed when Pi fixes it: Pi fills the id at stream end and omits unreplayable calls on replay.
  • Fix commits: 3ef234b949 (2026-10-09), 82994975e6 (2026-10-09), a3859aa658 (2026-10-09), 4180310931 (2026-10-10), 543dba2cbe (2026-10-10), 66765600a6 (2026-10-10), 6693198f8c (2026-10-10), 9cc4d406f7 (2026-10-10), 74ed845011 (2026-10-11), b567ebab3c (2026-10-11)
  • Tests: ai/openai_empty_tool_call_reviewer_test.go::TestOpenAICompletionsGeneratedToolCallIDsAreUnique, ai/openai_empty_tool_call_reviewer_test.go::TestOpenAICompletionsReplayOmitsStoredToolCallsWithoutIDOrNameReviewed, ai/openai_empty_tool_call_test.go::TestOpenAICompletionsEmptyToolCallDoesNotWedgeReplay, ai/unreplayable_tool_call_test.go::TestReplayOmitsUnreplayableToolCallsOnEveryProviderPath, ai/unreplayable_tool_call_test.go::TestStreamedToolCallsWithoutIDGetDistinctIDsAcrossTurns
  • Release note: An OpenAI-compatible provider that streams a tool call without an id or a function name (OpenRouter and GLM do) no longer leaves the session failing every later turn with tool messages must include a non-empty string tool_call_id. PiG gives the call a unique id, leaves a call without a name or id and its results out of every provider's replayed request, and a stored session that already holds such a call works again (D120, pi-mono #10299).

BF-a3bfab2

A Radius gateway config cached on an OAuth credential is sanitized as Pi does

  • Source: PiG's own defect
  • Symptom: A Radius gateway config cached on an OAuth credential is sanitized as Pi does.
  • Root cause: Pi reads the exact properties. baseUrl and models now come from the exact-key member map. - A gateway model was decoded through the same struct first, so "ID", "Name" or "contextwindow" after "id", "name", "contextWindow" replaced Pi's value (the model id changed). Every model is now decoded member by member by exact key (radiusGatewayModelMembers, formerly the lenient fallback only). - The fallback clamped a number member to int32: 3e9 became 2147483647 while 3000000000 decoded exactly; 1e400 (Infinity in Pi) became 0. jsNumberToInt keeps the integer part over the whole int range, with Infinity as the largest int. Mutants killed: struct-first decode, int32 clamp, no clamp, floor for trunc…
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 093cf5c781 (2026-10-09), a3bfab2c03 (2026-10-09), b5356c4269 (2026-10-09), d1b3ad0151 (2026-10-09), da99854d1b (2026-10-09)
  • Tests: ai/error_body_oracle_test.go::TestErrorBodyHelpersMatchPiOnSeededProbes, ai/estimate_oracle_test.go::TestTokenEstimatesMatchPiOnSeededTranscripts, ai/overflow_oracle_test.go::TestContextOverflowMatchesPiOnSeededMessages, ai/radius_config_oracle_test.go::TestRadiusConfigMatchesPiOnSeededProbes, coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage
  • Release note: A Radius gateway config cached on an OAuth credential is sanitized as Pi does. A model whose contextWindow or maxTokens is null is dropped (it was kept). A model with a fractional or out-of-range window, a non-object cost tier, headers that are not a string map or another member Pig cannot represent is kept (it was dropped): a window keeps its integer part, clamped to the integer range, and any other such member is left at its zero value. baseUrl, models and each model member are read by their exact key, so a member such as ID or contextwindow no longer replaces id or contextWindow.

BF-a40a16d

fix(codingagent): typed input restarts the idle collection's wait on every route

  • Source: PiG's own defect
  • Symptom: fix(codingagent): typed input restarts the idle collection's wait on every route
  • Root cause: After the frames change, a key the main loop paints synchronously or a key a selector takes through its modal route never woke the spinner, so the one forced GC could run while the user typed, about 500 ms after a run ended. D172 says input restarts the wait, as Pi-Bolt's ui.addInputListener restarts idle-gc's timer (packages/coding-agent/src/core/idle-gc.ts schedule/watchActivity, modes/interactive/interactive-mode.ts IdleCollector). The reader-side router now wakes the ticker for every input event while the wait runs; outside it (and during runs) a key costs one atomic load.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: a40a16d205 (2026-10-13)
  • Tests: None recorded.

BF-a46f930

A Package configured as a settings object reads a resource kind its pi manifest does not declare (or…

  • Source: PiG's own defect
  • Symptom: A Package configured as a settings object reads a resource kind its pi manifest does not declare (or declares empty, when patterns are given) from the conventional directory, and a pi manifest field that is not an array of strings counts as absent, as in Pi's collectPackageResources.
  • Root cause: TestLoadMcpConfigMatchesPiOnRandomFiles loads 1500 random global and project mcp.json files through Pi's loadMcpConfig and PiG's LoadMcpConfig: 187 differed, among them every file with a numeric server name, because Object.entries lists array-index keys first and orderedjson.Object kept file order. Object.Set now inserts an array-index key at its ascending place before the other keys (Keys, MarshalJSON, Clone follow), with TestObjectEnumeratesArrayIndexKeysFirst. Dependent packages (codingagent, extension, mcpext, mcp, packagecontent) pass; the failures left in tools and the node-backed subprocess tests are the fd/rg and node-shim environment ones.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 12cd60837e (2026-10-09), 1a29f511e7 (2026-10-09), 3def2a7bd8 (2026-10-10), 7e1a05ccfd (2026-10-10), a46f930200 (2026-10-10), f3b4045e8a (2026-10-10)
  • Tests: coding/mcpext/config_load_oracle_test.go::TestLoadMcpConfigMatchesPiOnRandomFiles, coding/packagecontent/discover_filtered_test.go::TestDiscoverFilteredFallsBackToConventionalDirectories, internal/codingagent/migrations_tree_oracle_test.go::TestRunMigrationsTreeMatchesPi, internal/codingagent/settings_file_bytes_oracle_test.go::TestSettingsFileBytesMatchPi, internal/ignorerules/ignore_oracle_test.go::TestIgnoredMatchesPiIgnorePackage, internal/ignorerules/ignorerules_test.go::TestIgnoreRulesFollowTheIgnorePackage, internal/orderedjson/orderedjson_test.go::TestObjectEnumeratesArrayIndexKeysFirst, internal/packagemanager/resolve_oracle_test.go::TestResolveMatchesPiOnRandomFixtures
  • Release note: A Package configured as a settings object reads a resource kind its pi manifest does not declare (or declares empty, when patterns are given) from the conventional directory, and a pi manifest field that is not an array of strings counts as absent, as in Pi's collectPackageResources.

BF-a4753ec

A panic in a background goroutine of an interactive session no longer ends PiG with only a stderr dump

  • Source: PiG's own defect
  • Symptom: A panic in a background goroutine of an interactive session no longer ends PiG with only a stderr dump.
  • Root cause: Every other exit the user did not ask for now leaves a trace (D102, additive): an exit.log line in the agent directory for SIGHUP, SIGTERM, an external SIGINT, a closed terminal or input, an extension's shutdown() and a failed run; a crash record for a panic or fatal error in any goroutine (through a per-session crash-output marker the next start reconciles); and pig diagnose lists the recent unexpected exits. The background cache warm recovers a panic and reschedules as Pi's does, the emergency terminal exit stops extension processes, and a reconciled crash keeps its real time.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a4753ec61e (2026-10-06, #170), c019ea9155 (2026-10-06, #163)
  • Tests: coding/cli/rpc_initial_tools_test.go::TestRPCFreshPromptDeclaresStructuredSystemOnce, internal/codingagent/agent_env_isolation_test.go::TestPackageTestsDoNotReachTheExportedAgentDirectory, internal/codingagent/exit_record_test.go::TestBackgroundGroupPanicReachesTheHandlerWithItsStack, internal/codingagent/exit_record_test.go::TestBackgroundTaskPanicEndsTheSessionWithACrashRecord, internal/codingagent/exit_record_test.go::TestCacheWarmerDecisionPanicKeepsPisDecision, internal/codingagent/exit_record_test.go::TestCacheWarmerRefreshPanicOutsideTheCaughtRequestReachesTheUncaughtHandler, internal/codingagent/exit_record_test.go::TestCacheWarmerRefreshRequestPanicIsCaughtAndReschedules, internal/codingagent/exit_record_test.go::TestCleanEndLeavesNoMarker and 23 more in docs/bug-ledger.json
  • Release note: A panic in a background goroutine of an interactive session no longer ends PiG with only a stderr dump. PiG restores the terminal, prints the error, records it in crashes.json and exits 1, as Pi does for an uncaught exception. Any other goroutine panic or runtime fatal error is copied to <agent dir>/crash-output/ and becomes a crash record at the next start.

BF-a4753ec-2

The prompt-cache refresh that fires minutes after a prompt now treats a panic in the extension decision or in…

  • Source: PiG's own defect
  • Symptom: The prompt-cache refresh that fires minutes after a prompt now treats a panic in the extension decision or in the refresh request as Pi treats a thrown error there: it keeps Pi's decision or skips that refresh and keeps warming, instead of ending the session.
  • Root cause: Every other exit the user did not ask for now leaves a trace (D102, additive): an exit.log line in the agent directory for SIGHUP, SIGTERM, an external SIGINT, a closed terminal or input, an extension's shutdown() and a failed run; a crash record for a panic or fatal error in any goroutine (through a per-session crash-output marker the next start reconciles); and pig diagnose lists the recent unexpected exits. The background cache warm recovers a panic and reschedules as Pi's does, the emergency terminal exit stops extension processes, and a reconciled crash keeps its real time.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): a4753ec61e (2026-10-06, #170)
  • Tests: coding/cli/rpc_initial_tools_test.go::TestRPCFreshPromptDeclaresStructuredSystemOnce, internal/codingagent/exit_record_test.go::TestBackgroundGroupPanicReachesTheHandlerWithItsStack, internal/codingagent/exit_record_test.go::TestBackgroundTaskPanicEndsTheSessionWithACrashRecord, internal/codingagent/exit_record_test.go::TestCacheWarmerDecisionPanicKeepsPisDecision, internal/codingagent/exit_record_test.go::TestCacheWarmerRefreshPanicOutsideTheCaughtRequestReachesTheUncaughtHandler, internal/codingagent/exit_record_test.go::TestCacheWarmerRefreshRequestPanicIsCaughtAndReschedules, internal/codingagent/exit_record_test.go::TestCleanEndLeavesNoMarker, internal/codingagent/exit_record_test.go::TestDeadTerminalExitLeavesALineWithTheReason and 13 more in docs/bug-ledger.json
  • Release note: The prompt-cache refresh that fires minutes after a prompt now treats a panic in the extension decision or in the refresh request as Pi treats a thrown error there: it keeps Pi's decision or skips that refresh and keeps warming, instead of ending the session.

BF-a4753ec-3

When the terminal's input ends while its output is still there, PiG keeps running as Pi does instead of…

  • Source: PiG's own defect
  • Symptom: When the terminal's input ends while its output is still there, PiG keeps running as Pi does instead of exiting 1 with Error: EOF.
  • Root cause: Every other exit the user did not ask for now leaves a trace (D102, additive): an exit.log line in the agent directory for SIGHUP, SIGTERM, an external SIGINT, a closed terminal or input, an extension's shutdown() and a failed run; a crash record for a panic or fatal error in any goroutine (through a per-session crash-output marker the next start reconciles); and pig diagnose lists the recent unexpected exits. The background cache warm recovers a panic and reschedules as Pi's does, the emergency terminal exit stops extension processes, and a reconciled crash keeps its real time.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a4753ec61e (2026-10-06, #170)
  • Tests: coding/cli/rpc_initial_tools_test.go::TestRPCFreshPromptDeclaresStructuredSystemOnce, internal/codingagent/exit_record_test.go::TestBackgroundGroupPanicReachesTheHandlerWithItsStack, internal/codingagent/exit_record_test.go::TestBackgroundTaskPanicEndsTheSessionWithACrashRecord, internal/codingagent/exit_record_test.go::TestCacheWarmerDecisionPanicKeepsPisDecision, internal/codingagent/exit_record_test.go::TestCacheWarmerRefreshPanicOutsideTheCaughtRequestReachesTheUncaughtHandler, internal/codingagent/exit_record_test.go::TestCacheWarmerRefreshRequestPanicIsCaughtAndReschedules, internal/codingagent/exit_record_test.go::TestCleanEndLeavesNoMarker, internal/codingagent/exit_record_test.go::TestDeadTerminalExitLeavesALineWithTheReason and 13 more in docs/bug-ledger.json
  • Release note: When the terminal's input ends while its output is still there, PiG keeps running as Pi does instead of exiting 1 with Error: EOF.

BF-a47eb1a

A tool result from a Rust or Python extension can set the collapsed-view preview the Go SDK already had…

  • Source: PiG's own defect
  • Symptom: A tool result from a Rust or Python extension can set the collapsed-view preview the Go SDK already had (ToolResult::with_preview, or a "preview" key of the result dict), and the preview footer reads Took 1m 15s as the shell renderers do instead of Took 75.0s.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), a47eb1a22b (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/streaming_json_resume_oracle_test.go::TestStreamingArgumentsParserMatchesPiAtEveryChunk and 5 more in docs/bug-ledger.json
  • Release note: A tool result from a Rust or Python extension can set the collapsed-view preview the Go SDK already had (ToolResult::with_preview, or a "preview" key of the result dict), and the preview footer reads Took 1m 15s as the shell renderers do instead of Took 75.0s.

BF-a53f72f

A context an extension kept across a session replacement or reload now raises Pi's stale message from cwd…

  • Source: PiG's own defect
  • Symptom: A context an extension kept across a session replacement or reload now raises Pi's stale message from cwd, mode, hasUI and model in the Go, Python and Rust SDKs, as Pi throws it.
  • Root cause: Renames the Go types that implement packages/coding-agent InteractiveModeOptions, FooterComponent (footer.ts), ArminComponent (armin.ts) and AgentSessionServices/createAgentSessionServices (agent-session-services.ts). No behavior change; the existing tests cover them.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0dc27ec862 (2026-10-06), a53f72f519 (2026-10-06), e2f7a2b423 (2026-10-06)
  • Tests: extensions/sdk/stale_context_test.go::TestFirstInvalidateMessageWins, extensions/sdk/stale_context_test.go::TestLocalContextMembersPanicWithTheStaleMessageAfterInvalidate, extensions/sdk/stale_context_test.go::TestReplacementContextIsNotStaleAfterInvalidate, test/extension-conformance/context_stale_test.go::TestStaleContextLocalMembersRaiseTheStaleMessageInEverySDK
  • Release note: A context an extension kept across a session replacement or reload now raises Pi's stale message from cwd, mode, hasUI and model in the Go, Python and Rust SDKs, as Pi throws it. Go and Rust panic with the message and Python raises RuntimeError; the handler's error carries it. The Rust SDK's handler_panic error now includes the panic text.

BF-a5524c8

In print and JSON mode, an extension whose session_shutdown handler throws after SIGTERM or SIGHUP now has…

  • Source: PiG's own defect
  • Symptom: In print and JSON mode, an extension whose session_shutdown handler throws after SIGTERM or SIGHUP now has its error reported, as Pi does, instead of "connection closed".
  • Root cause: SIGTERM during a tool: the signal's disposal awaits session_shutdown while the killed tool lets the run finish. Print mode's normal teardown then closed the runtime and retired the extension host under that in-flight request, so a throwing handler was reported as "Extension error (<path>): connection closed". Pi's finally awaits disposeRuntime, which does nothing once the signal handler has started it (print-mode.ts:42-48,162-167), and the process lives until the disposal settles (print-mode.ts:58-61), so Pi prints the handler's error. The teardown now joins the signal's disposal before it closes the runtime.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a5524c8b01 (2026-10-11)
  • Tests: None recorded.
  • Release note: In print and JSON mode, an extension whose session_shutdown handler throws after SIGTERM or SIGHUP now has its error reported, as Pi does, instead of "connection closed".

BF-a5524c8-2

In print and JSON mode, SIGTERM or SIGHUP during a tool call stops the run where Pi does while the…

  • Source: PiG's own defect
  • Symptom: In print and JSON mode, SIGTERM or SIGHUP during a tool call stops the run where Pi does while the extensions' session_shutdown handlers finish without waiting: no further provider request and no tool result.
  • Root cause: SIGTERM during a tool: the signal's disposal awaits session_shutdown while the killed tool lets the run finish. Print mode's normal teardown then closed the runtime and retired the extension host under that in-flight request, so a throwing handler was reported as "Extension error (<path>): connection closed". Pi's finally awaits disposeRuntime, which does nothing once the signal handler has started it (print-mode.ts:42-48,162-167), and the process lives until the disposal settles (print-mode.ts:58-61), so Pi prints the handler's error. The teardown now joins the signal's disposal before it closes the runtime.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a5524c8b01 (2026-10-11)
  • Tests: None recorded.
  • Release note: In print and JSON mode, SIGTERM or SIGHUP during a tool call stops the run where Pi does while the extensions' session_shutdown handlers finish without waiting: no further provider request and no tool result. A handler that waits on a timer or I/O lets the run continue, as in Pi, and text mode then prints the run's result.

BF-a690122

PIG_PROFILE=heap and the heap_live_bytes of PIG_PROFILE=metrics count only memory PiG retains

  • Source: PiG's own defect
  • Symptom: PIG_PROFILE=heap and the heap_live_bytes of PIG_PROFILE=metrics count only memory PiG retains.
  • Root cause: PIG_PROFILE=heap and metrics heap_live_bytes ran one runtime.GC before measuring. The first cycle only moves sync.Pool contents to the victim cache, which keeps them reachable until the next cycle, so pooled buffers counted as live: at a 1M-token session the encoding/json encoder buffer of the last provider request showed as 4.5 MB at ai/openai.go json.Marshal (test/longsession measure, GODEBUG=memprofilerate=1: heap total 17.8 MB -> 13.2 MB with the fix).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a690122479 (2026-10-10)
  • Tests: internal/profiling/profiling_test.go::TestHeapMeasurementsOmitPooledBuffers
  • Release note: PIG_PROFILE=heap and the heap_live_bytes of PIG_PROFILE=metrics count only memory PiG retains. They now run two garbage collections before measuring. After the single collection used before, buffers that pools keep for reuse still counted as live, such as the JSON encoder buffer that holds the last provider request. At a 1M-token session that buffer is about 4.5 MB.

BF-a6932b4

fix(coding): pass a Node extension request's client identity to the provider

  • Source: PiG's own defect
  • Symptom: fix(coding): pass a Node extension request's client identity to the provider
  • Root cause: The model-stream host call decoded no identity option, so a Node extension's createAgentSession (core/sdk.ts getClientIdentity) and any pi-ai call that names an identity sent no attribution headers. Decode it as ai.ClientIdentity, including an explicit null attribution.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: a6932b4c2b (2026-10-21)
  • Tests: internal/codingagent/subprocess_identity_options_test.go::TestSubprocessStreamOptionsCarryTheRequestIdentity

BF-a6e0ce8

fix(codingagent): session selector page keys keep the index valid (Pi #7680)

  • Source: PiG's own defect
  • Symptom: fix(codingagent): session selector page keys keep the index valid (Pi #7680)
  • Root cause: An earlier change let every move leave -1 over an empty list, which broke TestPR7680SessionSelectionStaysValidWhenPagingBeforeTheLoad.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: a6e0ce89e3 (2026-10-10)
  • Tests: internal/codingagent/select_page_keys_pr7680_test.go::TestPR7680SessionSelectionStaysValidWhenPagingBeforeTheLoad

BF-a739b6d

The Bedrock endpoint region is read from the WHATWG hostname of the base URL, as Pi's new URL does…

  • Source: PiG's own defect
  • Symptom: The Bedrock endpoint region is read from the WHATWG hostname of the base URL, as Pi's new URL does: full-width and mixed-case hosts, tabs and newlines inside the URL, and other schemes are handled as in Pi, and a URL without a scheme no longer yields a region.
  • Root cause: getStandardBedrockEndpointRegion (cut from Pi's bedrock-converse-stream.ts:1223, testdata/bedrock_region.mjs) runs on 3006 seeded base URLs (regions, fips, cn partition, mixed case, full-width letters, tabs/newlines, userinfo, ports, schemes, schemeless and non-URL text). net/url differed on every URL with a tab/newline inside or a full-width/uppercase host (the WHATWG parser strips and folds them) and returned a region for a schemeless '//host' that new URL rejects (red before). bedrockEndpointRegion now reads nodeurl.ParseURL's hostname. Survivor: dropping the empty-string guard, which is equivalent because ParseURL rejects ''.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 7c4d874438 (2026-10-09), 90882933f6 (2026-10-09), a739b6dcf2 (2026-10-09), d80c1cd150 (2026-10-09)
  • Tests: ai/azure_config_oracle_test.go::TestAzureBaseURLNormalizationMatchesPi, ai/bedrock_region_oracle_test.go::TestBedrockEndpointRegionMatchesPi, ai/githubcopilot_oracle_test.go::TestGitHubCopilotHelpersMatchPi, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode, internal/nodeurl/url_oracle_test.go::TestParseMatchesNodeURL
  • Release note: The Bedrock endpoint region is read from the WHATWG hostname of the base URL, as Pi's new URL does: full-width and mixed-case hosts, tabs and newlines inside the URL, and other schemes are handled as in Pi, and a URL without a scheme no longer yields a region.

BF-a7be906

Pressing Enter in the model picker's search box selects the highlighted model even when tui.select.confirm…

  • Source: PiG's own defect
  • Symptom: Pressing Enter in the model picker's search box selects the highlighted model even when tui.select.confirm is rebound.
  • Root cause: The picker now uses NewInput with OnSubmit = selectHighlighted, shared with the confirm case.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 082355f166 (2026-10-07), 0ae3fcf17c (2026-10-07), a7be906e71 (2026-10-07), ccaade27af (2026-10-07), f2a2b3409b (2026-10-07)
  • Tests: internal/codingagent/remote_overlay_production_review_test.go::TestRemoteOverlayProductionRejectsCachedTerminalWidth, internal/codingagent/session_selector_oracle_test.go::TestSessionSelectorInputMatchesPi, internal/codingagent/thinking_selector_oracle_test.go::TestThinkingSelectorInputMatchesPi, internal/codingagent/tree_row_format_test.go::TestFormatTreeRow_AllEntryTypes, internal/codingagent/tree_selector_oracle_test.go::TestTreeSelectorInputMatchesPi, internal/codingagent/w3_pi_test.go::TestW3SessionPiComparison, test/parity/scenarios/settings/06-thinking-selector-search-editing.toml, tui/model_selector_oracle_test.go::TestModelSelectorInputMatchesPi and 1 more in docs/bug-ledger.json
  • Release note: Pressing Enter in the model picker's search box selects the highlighted model even when tui.select.confirm is rebound.

BF-a7d2ef5

Path autocomplete after resuming a session created in another directory now lists files from that session's…

  • Source: PiG's own defect
  • Symptom: Path autocomplete after resuming a session created in another directory now lists files from that session's directory instead of the launch directory.
  • Root cause: Pi builds the autocomplete provider on sessionManager.getCwd() (interactive-mode.ts:820); PiG used os.Getwd(), so after resuming a session from another directory path completion listed the launch dir.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a7d2ef5c78 (2026-10-13)
  • Tests: internal/codingagent/autocomplete_session_cwd_test.go::TestAutocompleteProviderCompletesPathsFromSessionCwd
  • Release note: Path autocomplete after resuming a session created in another directory now lists files from that session's directory instead of the launch directory.

BF-a81ec0d

A key pressed while an extension's custom overlay is open no longer freezes pig when the key reaches the…

  • Source: PiG's own defect
  • Symptom: A key pressed while an extension's custom overlay is open no longer freezes pig when the key reaches the overlay on the main input loop.
  • Root cause: Every other exit the user did not ask for now leaves a trace (D102, additive): an exit.log line in the agent directory for SIGHUP, SIGTERM, an external SIGINT, a closed terminal or input, an extension's shutdown() and a failed run; a crash record for a panic or fatal error in any goroutine (through a per-session crash-output marker the next start reconciles); and pig diagnose lists the recent unexpected exits. The background cache warm recovers a panic and reschedules as Pi's does, the emergency terminal exit stops extension processes, and a reconciled crash keeps its real time.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 65b7c13e09 (2026-10-06, #168), a4753ec61e (2026-10-06, #170), a81ec0dc55 (2026-10-08), af1fe1a8fe (2026-10-08, #175), f680b9591a (2026-10-08, #197)
  • Tests: coding/cli/rpc_initial_tools_test.go::TestRPCFreshPromptDeclaresStructuredSystemOnce, coding/cli/rpc_shutdown_test.go::TestRPCInputEndAfterExtensionCommandComparedWithPi, coding/extension/host/subprocess/tool_call_input_reply_test.go::TestToolCallReplyEditsTheEventInputInPlace, internal/codingagent/auth_warnings_test.go::TestCycleModelChecksAnthropicSubscriptionAuth, internal/codingagent/exit_record_test.go::TestBackgroundGroupPanicReachesTheHandlerWithItsStack, internal/codingagent/exit_record_test.go::TestBackgroundTaskPanicEndsTheSessionWithACrashRecord, internal/codingagent/exit_record_test.go::TestCacheWarmerDecisionPanicKeepsPisDecision, internal/codingagent/exit_record_test.go::TestCacheWarmerRefreshPanicOutsideTheCaughtRequestReachesTheUncaughtHandler and 35 more in docs/bug-ledger.json
  • Release note: A key pressed while an extension's custom overlay is open no longer freezes pig when the key reaches the overlay on the main input loop. The loop read the overlay's state by queueing a task on itself and waiting for it, so the key never reached the extension. A caller already on the main loop now runs that work inline, as Pi's single event loop does, and a task the loop posts to its own full queue no longer blocks.

BF-a8a04f4

PIG_DEBUG writes its log with mode 0600 and refuses a symlink or a file owned by another user in the shared…

  • Source: PiG's own defect
  • Symptom: PIG_DEBUG writes its log with mode 0600 and refuses a symlink or a file owned by another user in the shared temporary directory; PIG_DEBUG_KEYS logs typed text and pastes only by length, so a key entered at a masked login prompt no longer reaches the log; pig-render.log is private (0600).
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a8a04f452d (2026-10-10)
  • Tests: internal/codingagent/debug_log_security_test.go::TestRedactKeyChunkHidesTypedAndPastedText, internal/codingagent/debug_log_security_unix_test.go::TestDebugLogRefusesASymlinkAndIsPrivate
  • Release note: PIG_DEBUG writes its log with mode 0600 and refuses a symlink or a file owned by another user in the shared temporary directory; PIG_DEBUG_KEYS logs typed text and pastes only by length, so a key entered at a masked login prompt no longer reaches the log; pig-render.log is private (0600).

BF-a8ff0c7

The anthropic-messages and google-generative-ai requests build their URL as @anthropic-ai/sdk and…

  • Source: PiG's own defect
  • Symptom: The anthropic-messages and google-generative-ai requests build their URL as @anthropic-ai/sdk and @google/genai do (query rewritten through qs or the form serializer, WHATWG normalisation, alt=sse placement), and a base URL with credentials is refused as fetch refuses it.
  • Root cause: Not recorded.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a8ff0c7afe (2026-10-10)
  • Tests: ai/sdk_request_url_oracle_test.go::TestSDKProvidersRefuseCredentialsLikeFetch, ai/sdk_request_url_oracle_test.go::TestSDKProvidersRequestURLMatchesSDK
  • Release note: The anthropic-messages and google-generative-ai requests build their URL as @anthropic-ai/sdk and @google/genai do (query rewritten through qs or the form serializer, WHATWG normalisation, alt=sse placement), and a base URL with credentials is refused as fetch refuses it.

BF-a901509

OpenRouter turns (the built-in provider and custom providers on OpenRouter's endpoint) now take OpenRouter's…

  • Source: PiG's own defect
  • Symptom: OpenRouter turns (the built-in provider and custom providers on OpenRouter's endpoint) now take OpenRouter's reported total as usage.cost.total for OpenAI Completions and Anthropic Messages streams, plus the upstream inference cost for a BYOK request, instead of the catalog estimate, and mark it provider-reported.
  • Root cause: Ports Pi (#10286, fixes #9980). For an OpenRouter model the OpenAI Completions usage chunk and the Anthropic Messages message_delta usage replace usage.cost.total with the reported cost (plus the upstream inference cost for BYOK) and set usage.cost.totalFromProvider. Retires divergence D124, which carried the unreleased branch of this fix: the call-site markers, the export-html normalize_replace rules and the pinned-asset note are removed because template.js is now Pi's file.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.3
  • Fix commits: a9015090a5 (2026-10-22)
  • Tests: ai/openrouter_cost_test.go::TestAnthropicMessagesUsesOpenRouterReportedCost, ai/openrouter_cost_test.go::TestApplyOpenRouterReportedCost, ai/openrouter_cost_test.go::TestOpenAICompletionsOpenRouterReportedTotalWithoutCatalogRates, ai/openrouter_cost_test.go::TestOpenAICompletionsUsesOpenRouterReportedCost, internal/codingagent/export/upstream_test.go::TestExportHTMLPinnedAssets
  • Release note: OpenRouter turns (the built-in provider and custom providers on OpenRouter's endpoint) now take OpenRouter's reported total as usage.cost.total for OpenAI Completions and Anthropic Messages streams, plus the upstream inference cost for a BYOK request, instead of the catalog estimate, and mark it provider-reported. The export HTML header uses the recorded total. Replaces PiG's earlier D124 form of this fix with Pi's.

BF-a94455c

The durable code mode joins the text items of a script's output in linear time (Pi f11e50f7…

  • Source: PiG's own defect
  • Symptom: The durable code mode joins the text items of a script's output in linear time (Pi f11e50f7, #10678): 100,000 items took about 3 s because every item read the end of the growing joined text.
  • Root cause: ExecuteTool's arguments are a Go map and Execute receives one, so the key order of a script's context was lost before the provider saw it (the questions reached it alphabetical). callTool now sends the order as data, an 'order' argument of the internal model tools (a tree of keys, orderOf), and the tool rebuilds the context from it (inOrder). The row preview and the journal keep Pi's {model, context} arguments. Test: the questions zeta, alpha reach the provider in that order; the state's key order stays lost because ai.ClassifierContext.State is a Go map (changelog known issue narrowed). Mutants red: no order sent, order ignored by the tool, pairs skipped.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 3b9c8e7b11 (2026-10-11), 3d751efbda (2026-10-11), 676874441a (2026-10-11), a94455cb5e (2026-10-12)
  • Tests: durable/codemode/join_text_test.go::TestJoinAdjacentTextEqualsTheQuadraticJoinOnRandomItems, durable/codemode/join_text_test.go::TestJoinAdjacentTextIsLinearAtTheItemLimit, durable/codemode/models_test.go::TestCodeModeModels, durable/codemode/output_test.go::TestFormatResultMatchesPi, durable/harness/tool_nested_records_test.go::TestNestedErrorResultsAndStructuredOutput
  • Release note: The durable code mode joins the text items of a script's output in linear time (Pi f11e50f7, #10678): 100,000 items took about 3 s because every item read the end of the growing joined text. The coding-agent code mode already joined each run once.

BF-a9757c8

A Cloudflare model built with coding.BuildModel and streamed through its own Provider resolves…

  • Source: PiG's own defect
  • Symptom: A Cloudflare model built with coding.BuildModel and streamed through its own Provider resolves {CLOUDFLARE_ACCOUNT_ID} and {CLOUDFLARE_GATEWAY_ID} from the stored credential's env again, under the request env, as Pi's Models.applyAuth merges the resolution env before cloudflareStreams runs.
  • Root cause: Pi's Models.applyAuth merges the auth resolution env under the request env (models.ts:868) before the provider's cloudflareStreams resolves the endpoint placeholders (cloudflare-stream.ts:21-28). ai.WrapBuiltinProvider handed the wrapper only the request env, so a per-model provider whose configured Env holds the stored credential's account and gateway ids (coding.BuildModel, then model.Provider.Stream) sent the request to the literal {CLOUDFLARE_ACCOUNT_ID} URL; before the provider-ID gate was removed the API provider merged its configured Env itself.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a9757c8ed3 (2026-10-10)
  • Tests: ai/provider_wrap_test.go::TestWrapBuiltinProviderResolvesTheEndpointFromTheConfiguredEnv, coding/cloudflare_build_model_test.go::TestBuildModelCloudflareEndpointUsesTheStoredCredentialEnv
  • Release note: A Cloudflare model built with coding.BuildModel and streamed through its own Provider resolves {CLOUDFLARE_ACCOUNT_ID} and {CLOUDFLARE_GATEWAY_ID} from the stored credential's env again, under the request env, as Pi's Models.applyAuth merges the resolution env before cloudflareStreams runs. Since ai.WrapBuiltinProvider the wrapper read only the request env, so such a request went to the literal placeholder URL.

BF-aa2003c

The edit tool's prepared arguments keep the members in the order the model sent them

  • Source: PiG's own defect
  • Symptom: A tool_call handler used to see the members sorted, with <, > and & escaped.
  • Root cause: prepareEditArguments assigns args.edits in place and returns { ...rest, edits } (edit.ts:103-133), so the tool_call input keeps the model's member order. The Go port re-marshalled a map, sorting every member and escaping <, > and &.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: aa2003ca07 (2026-10-06)
  • Tests: internal/codingagent/tools/edit_diff_test.go::TestEditPrepareArgumentsKeepsMemberOrder
  • Release note: The edit tool's prepared arguments keep the members in the order the model sent them. Legacy oldText/newText arguments are folded into edits after the other members, and a JSON-string edits stays where it was, as in Pi. A tool_call handler used to see the members sorted, with <, > and & escaped.

BF-aa69959

Key matching follows Pi for more terminal inputs and key identifiers

  • Source: PiG's own defect
  • Symptom: Key matching follows Pi for more terminal inputs and key identifiers.
  • Root cause:
    • keys.ts's sequence patterns use \d+ (:597, :609, :620, :641, :697); Pig's Atoi accepted a sign (ESC[1;+5A, ESC[27;1;-1~ as up) and an arrow event with no digits (ESC[1;5:A). - parseInt yields a JavaScript number and the bitwise lock mask applies ToInt32, so ESC[1;4294967301A is ctrl+up in Pi; codepoints past 2^53 round (ESC[9007199254741039;5u is not ctrl+/). Pig refused or misread both. - parseKeyId (:788) ignores a part that names no modifier and lowercases with toLowerCase: meta+a matches a, hyper+up matches up, meta+f1 matches f1, ctrl+İ matches nothing. Pig matched none of the first three and read İ as i. Mutants killed: the inexact-modifier check, ToInt32 wrap, Unicode…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e8cc072521 (2026-10-08), 056bbfb74e (2026-10-09), 2c04c1445a (2026-10-09), aa69959a64 (2026-10-09), da99854d1b (2026-10-09)
  • Tests: ai/authorization_input_oracle_test.go::TestAuthorizationInputParsersMatchPi, chord/delta/own_key_order_test.go::TestDiffRevisionsVisitsArrayIndexKeysFirstInNumericOrder, coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, internal/chord/chordjson/own_key_order_test.go::TestCopyFollowsPisKeyOrderAndMessages, internal/evals/plan_oracle_test.go::TestPlanMatchesPiOnSeededProbes, internal/evals/report_oracle_test.go::TestEvalReportMatchesPiOnSeededDesigns, tui/fuzzy_oracle_test.go::TestFuzzyMatchesPiOnSeededProbes, tui/keys_oracle_test.go::TestParseAndMatchKeyMatchPiOnSeededInput and 2 more in docs/bug-ledger.json
  • Release note: Key matching follows Pi for more terminal inputs and key identifiers. Unmodified enter and tab no longer match an xterm modifyOtherKeys sequence. A sequence number with a sign (ESC [ 1 ; +5 A) or an event field with no digits (ESC [ 1 ; 5 : A) is no longer read as a key. Modifier and codepoint numbers are read as JavaScript numbers, so very large values wrap and round as in Pi. A key identifier part that names no modifier, such as meta in meta+a, is ignored as in Pi instead of making the identifier match nothing, and identifiers are lowercased as JavaScript's toLowerCase does.

BF-aabd300

An OpenAI Responses, Azure OpenAI Responses or Google stream event whose JSON is a number, string, boolean or…

  • Source: PiG's own defect
  • Symptom: An OpenAI Responses, Azure OpenAI Responses or Google stream event whose JSON is a number, string, boolean or array is skipped as Pi skips it, instead of ending the reply with a decode error; a null Responses event fails with Pi's TypeError message.
  • Root cause: Follows finding 108, the pi-messages instance, for the other providers. TestProviderStreamNonObjectEventsMatchPi puts data: <value> with 5, null, [1], "x" or false after the first record of 20 seeded bodies per provider and compares Pi's provider with Pig's (anthropic, openai-completions, openai-responses, azure-openai-responses, google, mistral).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), 8070fcc38b (2026-10-10), a882d85b79 (2026-10-10), c4d7bd0b2f (2026-10-10), 212fece3ec (2026-10-11), 869cef866a (2026-10-11), aabd300c94 (2026-10-11), e720ec0d0a (2026-10-11)
  • Tests: ai/bedrock_eventstream_integer_float_test.go::TestBedrockStreamAcceptsIntegerValuedFloats, ai/bedrock_eventstream_integer_float_test.go::TestBedrockStreamEventReadsIntegerValuedFloats, ai/fetch_request_url_test.go::TestCodexAndAzureRequestTargetIsParsedLikeFetch, ai/fetch_request_url_test.go::TestCodexRequestTargetDropsTheFragmentOfAnOpaquePath, ai/openrouter_exchange_oracle_test.go::TestOpenRouterExchangeMatchesPi, ai/pi_messages_non_object_event_test.go::TestPiMessagesNonObjectEventIsSkippedLikeAnUnknownEvent, ai/provider_error_oracle_test.go::TestProviderErrorResponsesMatchPi, ai/provider_stream_non_object_oracle_test.go::TestProviderStreamNonObjectEventsMatchPi and 1 more in docs/bug-ledger.json
  • Release note: An OpenAI Responses, Azure OpenAI Responses or Google stream event whose JSON is a number, string, boolean or array is skipped as Pi skips it, instead of ending the reply with a decode error; a null Responses event fails with Pi's TypeError message.

BF-ab0ee71

Hiding an overlay whose preFocus is another overlay, such as a panel focused when the overlay was shown over…

  • Source: PiG's own defect
  • Symptom: Hiding an overlay whose preFocus is another overlay, such as a panel focused when the overlay was shown over it, focuses that panel as an overlay, as Pi does: its handle reports isFocused() and its own hide() moves focus on.
  • Root cause: TestCustomOverlayFocusChangesReachSDKs (bridge in place, SDK changes and pack reverted): every cell (subprocess-go, -node, -node-packed, -rust, -python, fused-go) "the component did not render focused=false after it lost focus".
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2a055a7a3a (2026-10-13), a9058bf992 (2026-10-13), ab0ee713e2 (2026-10-13), d87b70c54c (2026-10-13), ec2080a75f (2026-10-13)
  • Tests: coding/extension/host/subprocess/node_library_bundle_test.go::TestNodeLibraryBundlesKeepSharedIdentitiesWithoutRawCatalogLoads, coding/extension/host/subprocess/runtime_node_overlay_handle_test.go::TestNodeFactoryTuiShowOverlayHandleIsSynchronous, coding/extension/host/subprocess/runtime_node_prompt_end_test.go::TestNodePromptEndWaitEndsWhenAnotherProcessTurnsTheLoop, coding/extension/host/subprocess/ui_bridge_raw_overlay_test.go::TestRawOverlayEndsWithItsConnection, coding/extension/host/subprocess/ui_bridge_raw_overlay_test.go::TestRawOverlayShowsAtOnceAndEndsWithItsHide, coding/extension/host/subprocess/ui_bridge_raw_overlay_test.go::TestRawOverlayWithoutTUIIsNotShown, coding/extension/host/subprocess/ui_bridge_test.go::TestFocusedCustomComponentsStack, coding/extension/host/subprocess/ui_bridge_test.go::TestInteractiveDialogOpensOverFocusedOverlay and 17 more in docs/bug-ledger.json
  • Release note: Hiding an overlay whose preFocus is another overlay, such as a panel focused when the overlay was shown over it, focuses that panel as an overlay, as Pi does: its handle reports isFocused() and its own hide() moves focus on.

BF-ab5be97

Long streamed answers render each frame faster: the Markdown view re-wraps and re-renders only the lines and…

  • Source: PiG's own defect
  • Symptom: Long streamed answers render each frame faster: the Markdown view re-wraps and re-renders only the lines and inline text that changed since the last frame, with the same output.
  • Root cause: The main screen asks the root for the lines kept since the revision it drew last. Line resets and the differential scan start at the first line that can differ: the earlier of the kept prefix and both frames' cursor rows. The per-line reuse of earlier reset results stays, but only while the previous input array is intact: it was composed with overlays, or the root has rendered at most once since. Overlay frames keep nothing, and a frame after RestoreRenderState scans everything. Mouse dispatch reads child heights from the window.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 10aebd5c05 (2026-10-09), 136e3703be (2026-10-10), 79eadcf623 (2026-10-10), ab5be97bbe (2026-10-10)
  • Tests: ai/complexity_scaling_norace_test.go::TestScalingProviderRequestPerTurn, ai/complexity_scaling_norace_test.go::TestScalingProviderStreamPerDelta, coding/cli/complexity_scaling_norace_test.go::TestScalingExtensionEventStatePush, coding/complexity_scaling_norace_test.go::TestScalingSessionTurn, coding/extension/host/subprocess/complexity_scaling_norace_test.go::TestScalingMessageUpdatePayload, internal/codingagent/compaction/complexity_scaling_norace_test.go::TestScalingCompactionAndContextEstimate, internal/codingagent/complexity_scaling_norace_test.go::TestScalingSessionLogOperations, internal/codingagent/tools/complexity_scaling_norace_test.go::TestScalingTools and 10 more in docs/bug-ledger.json
  • Release note: Long streamed answers render each frame faster: the Markdown view re-wraps and re-renders only the lines and inline text that changed since the last frame, with the same output.

BF-ac0dc3b

An empty list item ends its list: a following unindented line is no longer taken for its lazy continuation…

  • Source: PiG's own defect
  • Symptom: An empty list item ends its list: a following unindented line is no longer taken for its lazy continuation, and spaces ending a list's last line render the empty row Pi's space token gives.
  • Root cause: marked's list tokenizer breaks on a line indented less than the item when the previous line is empty, indented code, a fence/heading start or a rule (Tokenizer.list: O/h), and trims the list raw so trailing spaces on the last line leave a space token (markdown.ts case space). 11 new rows in TestMarkdownRenderMatchesPi (7 red before).
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ac0dc3bee4 (2026-10-10), cdf0be76e4 (2026-10-10)
  • Tests: tui/markdown_render_oracle_test.go::TestMarkdownRenderMatchesPi
  • Release note: An empty list item ends its list: a following unindented line is no longer taken for its lazy continuation, and spaces ending a list's last line render the empty row Pi's space token gives.

BF-ac6fcea

A session_start handler that calls ctx.shutdown() during /reload ends the session and exits 0, as Pi…

  • Source: PiG's own defect
  • Symptom: A session_start handler that calls ctx.shutdown() during /reload ends the session and exits 0, as Pi does, instead of leaving the reload screen up.
  • Root cause: Pig awaits each reload step with awaitReloadStep, whose loop serviced UI tasks but never checked requestExit. requestShutdown's wake reached that loop instead of the input loop it interrupted, so the request was recorded and ignored: pig kept the "Reloading" box on screen and never exited.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ac6fceacad (2026-10-13)
  • Tests: internal/codingagent/extension_ui_phase_shutdown_test.go::TestReloadEndedByShutdownDuringSDKStagingStopsTheReload, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedReloadStepEndsTheSession, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-start-reload.toml
  • Release note: A session_start handler that calls ctx.shutdown() during /reload ends the session and exits 0, as Pi does, instead of leaving the reload screen up.

BF-aca35e7

In print and JSON mode, a Node extension no longer runs a timer or other callback between a prompt's last…

  • Source: PiG's own defect
  • Symptom: Pi-goal-x's create_goal could start a goal turn while the run was ending, print Extension error (<runtime>): This extension ctx is stale ... and leave the goal paused.
  • Root cause: Pi's print and JSON modes await each prompt and go on in the same continuation: to the next prompt, or to disposeRuntime(), which emits session_shutdown and then invalidates the runner (print-mode.ts:131-166, agent-session-runtime.ts:404-410, agent-session.ts:1379-1392). A timer or immediate an extension queues in the prompt's last call therefore runs only after its ctx is stale. PiG's Node runtime answered over IPC and returned to its event loop first, so pi-goal-x's agent_settled continuation timer could start a goal turn during disposal, which failed with "This extension ctx is stale" and paused the goal.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 7aca9bede6 (2026-10-07), aca35e7ff5 (2026-10-08)
  • Tests: coding/extension/host/subprocess/runtime_node_prompt_end_test.go::TestNodePromptEndCallbacksRunOnlyAfterTheRuntimeIsStale, coding/extension/host/subprocess/runtime_node_prompt_end_test.go::TestNodePromptEndWaitEndsWhenReloadBegins, coding/extension/host/subprocess/runtime_node_prompt_end_test.go::TestNodePromptEndWaitLeavesOtherRequestsRunning, coding/extension/host/subprocess/runtime_node_prompt_end_test.go::TestNodeRPCPromptEndCallbacksRunBeforeTheNextStep, coding/extension/host/subprocess/runtime_node_prompt_end_unix_test.go::TestNodePromptEndWaitEndsWithTheProcess, test/parity/scenarios/extensions-runtime/73-print-prompt-end-command.toml, test/parity/scenarios/extensions-runtime/73-print-prompt-end-input.toml, test/parity/scenarios/extensions-runtime/73-print-prompt-end-settled.toml
  • Release note: In print and JSON mode, a Node extension no longer runs a timer or other callback between a prompt's last extension call (its command, a handled input or agent_settled) and session_shutdown, nor between session_shutdown and the runtime going stale. Pi runs those steps without an event-loop turn between them. Before, pi-goal-x's create_goal could start a goal turn while the run was ending, print Extension error (<runtime>): This extension ctx is stale ... and leave the goal paused.

BF-ad49f9f

pig status no longer reports a shared extension name as an unhealthy duplicate

  • Source: PiG's own defect
  • Symptom: pig status no longer reports a shared extension name as an unhealthy duplicate.
  • Root cause: pig status built each resource identity from the public name alone, so two Packages that both expose src/index.ts resolved to the same "src" name and the second one became a fatal duplicate. The loader does not work that way: it identifies an extension by its path, and DisambiguateIdentities renames the later copies ("src:2", "src:3") and loads them all, as Pi loads every extension path. A shared extension name is therefore a supported condition, and pig status exited 1 while the session it described was healthy.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: ad49f9f02e (2026-10-23)
  • Tests: coding/cli/status_command_test.go::TestStatusStillFailsOnDuplicateNonExtensionResourceIdentity, coding/cli/status_command_test.go::TestStatusWarnsOnDuplicatePackageExtensionIdentity
  • Release note: pig status no longer reports a shared extension name as an unhealthy duplicate. Two Packages that both expose src/index.ts resolve to the same "src" public name, and the loader identifies an extension by its path: it renames the later copies ("src:2") and loads them all, as Pi loads every extension path. The report keeps them enabled and moves the finding to a new warnings list, so pig status exits 0 and pig status --json stays "healthy": true. Skills, prompts and themes key their runtime set by name, so their duplicates remain errors.

BF-ad9ed64

Appending a tool result whose details are a tool's own value (a truncated read, bash output details, an…

  • Source: PiG's own defect
  • Symptom: Appending a tool result whose details are a tool's own value (a truncated read, bash output details, an extension's details object) no longer parses the session record it has just written.
  • Root cause: Tests: - TestAppendToolResultWithTypedDetailsDoesNotDecodeItsRecord: 13.45x payload on the base, 7.14x without the sizing, 4.52x without the escape room, 2.26x now; ceiling 3x. Its mutants are red: no non-null check, no marshaler check, DetailsNull dropped, no deep compare, a nil map counted as non-null. Dropping the ToolResult source is equivalent, because the deep compare refuses a form without it. - TestAppendingATurnDoesNotParseItsRecordsForAccounting ceiling 300 -> 125 (111 now, 141 with the decode). - TestMessageEntryRecordKeepsNoSpareCapacity: a long record keeps at most its escape room plus the overhead. The new source-text row is red on the base (an 88600-byte record kept 17896…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ad9ed64513 (2026-10-13)
  • Tests: internal/codingagent/session_append_allocs_norace_test.go::TestAppendingATurnDoesNotParseItsRecordsForAccounting, internal/codingagent/session_append_typed_details_test.go::TestAppendToolResultWithTypedDetailsDoesNotDecodeItsRecord, internal/codingagent/session_held_entry_test.go::FuzzHeldMessageEntryMatchesTheRecordDecode, internal/codingagent/session_held_entry_test.go::TestHeldMessageEntryMatchesTheRecordDecode, internal/codingagent/session_line_capacity_test.go::TestMessageEntryRecordKeepsNoSpareCapacity, test/perfgate/process_test.go::TestRPCTurnProcessGate
  • Release note: Appending a tool result whose details are a tool's own value (a truncated read, bash output details, an extension's details object) no longer parses the session record it has just written. Appending a 1 MiB truncated read now takes about 3 ms instead of 72 ms.

BF-ad9ed64-2

A large message's session record no longer keeps a regrown buffer when its text has escapes such as newlines…

  • Source: PiG's own defect
  • Symptom: A truncated 1 MiB read used to keep 1.6 MB of unused space for the life of the session and now keeps about 160 KB; a 1 MiB source-code message kept 226 KB and now keeps 88 KB.
  • Root cause: Tests: - TestAppendToolResultWithTypedDetailsDoesNotDecodeItsRecord: 13.45x payload on the base, 7.14x without the sizing, 4.52x without the escape room, 2.26x now; ceiling 3x. Its mutants are red: no non-null check, no marshaler check, DetailsNull dropped, no deep compare, a nil map counted as non-null. Dropping the ToolResult source is equivalent, because the deep compare refuses a form without it. - TestAppendingATurnDoesNotParseItsRecordsForAccounting ceiling 300 -> 125 (111 now, 141 with the decode). - TestMessageEntryRecordKeepsNoSpareCapacity: a long record keeps at most its escape room plus the overhead. The new source-text row is red on the base (an 88600-byte record kept 17896…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ad9ed64513 (2026-10-13)
  • Tests: internal/codingagent/session_append_allocs_norace_test.go::TestAppendingATurnDoesNotParseItsRecordsForAccounting, internal/codingagent/session_append_typed_details_test.go::TestAppendToolResultWithTypedDetailsDoesNotDecodeItsRecord, internal/codingagent/session_held_entry_test.go::FuzzHeldMessageEntryMatchesTheRecordDecode, internal/codingagent/session_held_entry_test.go::TestHeldMessageEntryMatchesTheRecordDecode, internal/codingagent/session_line_capacity_test.go::TestMessageEntryRecordKeepsNoSpareCapacity, test/perfgate/process_test.go::TestRPCTurnProcessGate
  • Release note: A large message's session record no longer keeps a regrown buffer when its text has escapes such as newlines, tabs or quotes. A truncated 1 MiB read used to keep 1.6 MB of unused space for the life of the session and now keeps about 160 KB; a 1 MiB source-code message kept 226 KB and now keeps 88 KB.

BF-aeaaf97

Model requests reuse their HTTP connection

  • Source: PiG's own defect
  • Symptom: Every provider build used to create its own transport, so each request and tool round trip opened a new TCP (and TLS, and proxy CONNECT) connection and left an idle socket with its goroutines for two minutes.
  • Root cause: Pi installs one process-wide dispatcher (core/http-dispatcher.ts configureHttpDispatcher), so requests reuse connections. PiG built a transport per provider: 20 requests opened 20 TCP connections and left idle sockets and goroutines. Cache the transport by TLS mode, idle timeout and proxy environment.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 04b10bcaa0 (2026-10-09), aeaaf97297 (2026-10-09)
  • Tests: ai/http_transport_reuse_test.go::TestProvidersReuseOneConnectionAcrossBuilds, ai/http_transport_reuse_test.go::TestStreamingTransportIsKeyedByItsSettings, coding/model_runtime_conn_reuse_test.go::TestModelRuntimeReusesOneConnectionAcrossRequests
  • Release note: Model requests reuse their HTTP connection. Every provider build used to create its own transport, so each request and tool round trip opened a new TCP (and TLS, and proxy CONNECT) connection and left an idle socket with its goroutines for two minutes. Providers now share one transport per TLS mode; a changed idle timeout or proxy environment replaces it, as Pi's configureHttpDispatcher replaces its single dispatcher.

BF-af1fe1a

Cycling models in the interactive UI goes through the Session as in Pi: the new model persists as the…

  • Source: PiG's own defect
  • Symptom: Cycling models in the interactive UI goes through the Session as in Pi: the new model persists as the default, extends a non-empty enabledModels scope, keeps scope keys for model IDs that start with their provider, and checks Anthropic subscription auth after the switch.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: af1fe1a8fe (2026-10-08, #175)
  • Tests: internal/codingagent/auth_warnings_test.go::TestCycleModelChecksAnthropicSubscriptionAuth, internal/codingagent/model_cycle_parity_test.go::TestInteractiveCycleStatusMatchesSessionResult, internal/codingagent/model_cycle_parity_test.go::TestInteractiveCycleUsesSessionSelections, internal/codingagent/scoped_models_selection_test.go::TestScopedModelStartupKeepsProviderPrefixedIDs, internal/codingagent/scoped_models_selection_test.go::TestScopedSelectionKeepsProviderPrefixedIDsInSessionScope, internal/codingagent/scoped_models_selection_test.go::TestScopedSelectionSynchronizesSessionScope, test/parity/scenarios/model-resolver-selector/26-interactive-cycle-custom-scope.toml
  • Release note: Cycling models in the interactive UI goes through the Session as in Pi: the new model persists as the default, extends a non-empty enabledModels scope, keeps scope keys for model IDs that start with their provider, and checks Anthropic subscription auth after the switch.

BF-af36fe8

A streamed tool call whose arguments are valid JSON but not an object ([], null, 5, "s") keeps them…

  • Source: PiG's own defect
  • Symptom: A streamed tool call whose arguments are valid JSON but not an object ([], null, 5, "s") keeps them as the model sent them, so the model is told root: must be object ... Received arguments: [] and the session stores them, as Pi does; they used to be replaced by {} and reported as missing required properties.
  • Root cause: parseStreamingJson returns the value JSON.parse reads. PiG replaced [] / null / 5 / "s" with {}, so the model was told path was missing instead of root: must be object, and the session stored {}. ToolCall keeps the value's JSON text beside the nil Arguments (nonObjectArguments): ArgumentsJSON, MarshalJSON, UnmarshalJSON, the streaming parsers, clones and validation use it. The partial parser's null stays the empty object (result ?? {}); the nested-call recorder keeps arguments ?? {}.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: af36fe827b (2026-10-10)
  • Tests: ai/streaming_json_oracle_test.go::TestStreamingJSONMatchesPiOnSeededInput, ai/streaming_json_resume_oracle_test.go::TestStreamingArgumentsParserMatchesPiAtEveryChunk, ai/tool_call_argument_order_test.go::TestSetStreamingArgumentsKeepsMemberOrder, ai/tool_call_non_object_arguments_test.go::TestStreamedToolArgumentsThatAreNotAnObjectAreKeptAsPiKeepsThem
  • Release note: A streamed tool call whose arguments are valid JSON but not an object ([], null, 5, "s") keeps them as the model sent them, so the model is told root: must be object ... Received arguments: [] and the session stores them, as Pi does; they used to be replaced by {} and reported as missing required properties.

BF-afa1dd5

Amazon Bedrock requestMetadata keeps its keys' insertion order on the wire, as Pi's AWS SDK writes the…

  • Source: PiG's own defect
  • Symptom: Amazon Bedrock requestMetadata keeps its keys' insertion order on the wire, as Pi's AWS SDK writes the object.
  • Root cause: Lead ruling 2026-10-11T00:27Z: the caller's insertion order of requestMetadata reaches the wire (parity, no D-number). Pi passes options.requestMetadata to the AWS SDK, which writes the object in its own-key order (bedrock-converse-stream.ts; ConverseStreamRequest$).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: afa1dd5185 (2026-10-10), f02269c6e1 (2026-10-10)
  • Tests: ai/anthropic_replaced_payload_order_oracle_test.go::TestAnthropicReplacedPayloadKeepsPiMemberOrder, ai/bedrock_request_body_oracle_test.go::TestBedrockRequestBodyMatchesPi, ai/bedrock_wire_metadata_order_test.go::TestBedrockStringMapEnumeratesLikeAJavaScriptObject, ai/google_request_body_oracle_test.go::TestGoogleRequestBodyMatchesPi, coding/extension/host/subprocess/native_stream_options_union_test.go::TestNativeProviderOptionsKeepRawValuesAndNumericPresence, coding/extension/host/subprocess/request_metadata_order_test.go::TestCompleteRequestKeepsRequestMetadataOrder, coding/extension/host/subprocess/request_metadata_order_test.go::TestModelStreamRequestKeepsRequestMetadataOrder, coding/extension/host/subprocess/request_metadata_order_test.go::TestProviderOptionsWireKeepsRequestMetadataOrder and 1 more in docs/bug-ledger.json
  • Release note: Amazon Bedrock requestMetadata keeps its keys' insertion order on the wire, as Pi's AWS SDK writes the object. Go callers set the new StreamOptions.RequestMetadataOrder (keys missing from it follow in sorted order); an extension's requestMetadata keeps its order through streamModel, complete and an extension provider's options.

BF-afa1dd5-2

make port-lint has three more checks: regexpinfunc (a constant pattern compiled inside a function)…

  • Source: PiG's own defect
  • Symptom: make port-lint has three more checks: regexpinfunc (a constant pattern compiled inside a function), jsonclone (a value encoded to JSON and decoded again in one function) and ctxbackground (context.Background()/TODO() in a library function with no context).
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), afa1dd5185 (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/bedrock_request_body_oracle_test.go::TestBedrockRequestBodyMatchesPi and 10 more in docs/bug-ledger.json
  • Release note: make port-lint has three more checks: regexpinfunc (a constant pattern compiled inside a function), jsonclone (a value encoded to JSON and decoded again in one function) and ctxbackground (context.Background()/TODO() in a library function with no context). Today's findings are in the ratchet baseline.

BF-affc944

Bedrock requests that fail with The pending stream has been canceled after a stalled HTTP/2 connection are…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: Bedrock requests that fail with The pending stream has been canceled after a stalled HTTP/2 connection are retried.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: affc944ad8 (2026-10-05)
  • Tests: ai/assistant_retry_test.go::TestIsRetryableAssistantErrorClassification, mcp/oauth/oauth_test.go::TestMCPOAuthRegistersWithAnApplicationTypeDerivedFromTheRedirectURIsUnlessOneIsSet
  • Release note: Bedrock requests that fail with The pending stream has been canceled after a stalled HTTP/2 connection are retried.

BF-b02876b

chord/host: a link that is already closed when a host accepts or routes to it is no longer kept as a…

  • Source: PiG's own defect
  • Symptom: A route that returned a dead link (a child that exited before its link was wrapped) made every later call to that host name fail with unavailable until restart, and started a ping timer for the dead connection (Pi 7f022043).
  • Root cause: A route that returned a dead link (a child that exited before its link was wrapped) left a closed connection in the host, so every later call to that host name failed with unavailable until restart. addConnection no longer keeps a connection whose link closed in newConnection, childNamed skips closed connections, and newConnection starts no ping timer on a closed link, as Pi's connection.ts and host.ts now do. TestLinksARouteThatReturnsAnAlreadyClosedLinkDoesNotPoisonTheHostName ports the new link.test.ts case.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: b02876b1d5 (2026-10-13)
  • Tests: chord/host/link_upstream_test.go::TestLinksARouteThatReturnsAnAlreadyClosedLinkDoesNotPoisonTheHostName, chord/host/link_upstream_test.go::TestLinksAnAlreadyClosedLinkIsNotKeptAndStartsNoPinger
  • Release note: chord/host: a link that is already closed when a host accepts or routes to it is no longer kept as a connection. Before, a route that returned a dead link (a child that exited before its link was wrapped) made every later call to that host name fail with unavailable until restart, and started a ping timer for the dead connection (Pi 7f022043).

BF-b0608a9

OpenRouter generated-image data URLs are matched exactly as Pi does (^data:([^;]+);base64,(.+)$): a URL…

  • Source: PiG's own defect
  • Symptom: OpenRouter generated-image data URLs are matched exactly as Pi does (^data:([^;]+);base64,(.+)$): a URL with media type parameters, an empty media type or a line break in the payload is skipped, and a payload that is not valid base64 is kept instead of dropped.
  • Root cause: openrouter-images.ts:101 /^data:(;+);base64,(.+)$/ : no media type parameters, no empty media type, no line terminator, payload not validated.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: b0608a98b9 (2026-10-10)
  • Tests: ai/openrouter_images_data_url_test.go::TestParseDataImageURLMatchesPiRegex
  • Release note: OpenRouter generated-image data URLs are matched exactly as Pi does (^data:([^;]+);base64,(.+)$): a URL with media type parameters, an empty media type or a line break in the payload is skipped, and a payload that is not valid base64 is kept instead of dropped.

BF-b06ad92

An MCP server url can use environment variables ("url": "${TOOLS_URL}"): PiG checks and connects to the…

  • Source: PiG's own defect
  • Symptom: An MCP server url can use environment variables ("url": "${TOOLS_URL}"): PiG checks and connects to the expanded URL, as it does for headers and env (Pi #10654).
  • Root cause: validateMcpServerConfig, createDefaultTransport and oauthUrl use the raw url; headers and env are resolved. ExpandMcpURL expands $VAR/${VAR}/$$ when fully resolvable, else keeps the url as written.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: b06ad928d7 (2026-10-09)
  • Tests: coding/extension/mcp_url_expansion_test.go::TestExpandMcpURL, coding/extension/mcp_url_expansion_test.go::TestValidateMcpServerConfigChecksTheExpandedURL, coding/mcpext/connection_test.go::TestMCPConnectionsExpandTheServerURLReferences
  • Release note: An MCP server url can use environment variables ("url": "${TOOLS_URL}"): PiG checks and connects to the expanded URL, as it does for headers and env (Pi #10654).

BF-b18a7ff

ctx.compact answers onComplete with the compaction result's members in Pi's order (summary…

  • Source: PiG's own defect
  • Symptom: ctx.compact answers onComplete with the compaction result's members in Pi's order (summary, firstKeptEntryId, tokensBefore, estimatedTokensAfter, usage, details); they were written alphabetically.
  • Root cause: types.ts CompactOptions.onComplete and SessionBeforeCompactResult.compaction carry compaction.ts CompactionResult (summary, firstKeptEntryId, tokensBefore, estimatedTokensAfter?, usage?, details?). extension.CompactionResult was any, and Session.CompactForExtension built a map[string]any, which JSON-marshals its members alphabetically; Pi's object lists them in the order above. It is now a struct with Pi's member names and order; SessionBeforeCompactResult.Compaction is *CompactionResult; the 17 test handlers that returned a map build the struct. Production reads the result through JSON (session_compaction_extensions.go), unchanged.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: b18a7ffd06 (2026-10-07)
  • Tests: coding/session_compact_extension_test.go::TestCompactForExtensionReturnsUpstreamResultShape
  • Release note: ctx.compact answers onComplete with the compaction result's members in Pi's order (summary, firstKeptEntryId, tokensBefore, estimatedTokensAfter, usage, details); they were written alphabetically.

BF-b2b0c63

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: The lone-surrogate fidelity moves to the root: chordjson's default string unquote reads a surrogate escape as its unit, so delta.DecodeJson callers need no wrapper, durable/json.go and nested_result.go are back to main's calls (the nested result decodes with delta.DecodeJson and so keeps document order), NestedToolCallRecord is main's text again, and the stale any-trap entries are deleted.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: b2b0c63c54 (2026-10-25)
  • Tests: None recorded.

BF-b2c219f

fix(runtimecell): satisfy the divergence, port and perf gates for the usage-lease and fingerprint changes

  • Source: PiG's own defect
  • Symptom: fix(runtimecell): satisfy the divergence, port and perf gates for the usage-lease and fingerprint changes
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: b2c219f432 (2026-10-20)
  • Tests: coding/extension/host/subprocess/sdk_fingerprint_marker_test.go::TestBuilderRecordsTheSDKFingerprintByReplacingTheMarker

BF-b341a82

go install github.com/MichaelKinsy/PiG/cmd/pig@latest works again

  • Source: PiG's own defect
  • Symptom: go install github.com/MichaelKinsy/PiG/cmd/pig@latest works again.
  • Root cause: The 0.5.0 Go module was larger than the Go module size limit because it carried parity ledgers for every earlier Pi pin; only the current pin's ledgers ship now, and v0.5.0 is retracted.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: b341a82e9d (2026-10-19, #221)
  • Tests: None recorded.
  • Release note: go install github.com/MichaelKinsy/PiG/cmd/pig@latest works again. The 0.5.0 Go module was larger than the Go module size limit because it carried parity ledgers for every earlier Pi pin; only the current pin's ledgers ship now, and v0.5.0 is retracted.

BF-b3543f8

Fixed MCP stdio servers staying alive after PiG exited because the terminal closed, it crashed, or it…

  • Source: PiG's own defect
  • Symptom: Fixed MCP stdio servers staying alive after PiG exited because the terminal closed, it crashed, or it received a termination signal.
  • Root cause: Fixed MCP stdio servers staying alive after PiG exited because the terminal closed, it crashed, or it received a termination signal.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1cbd9b094c (2026-10-07), b3543f848c (2026-10-07)
  • Tests: ai/abort_race_test.go::TestOperationSignalKeepsASignalAndSuppliesAnUncancelledOne, ai/abort_race_test.go::TestRaceWithAbortSignalSettlesWithTheFirstOfOperationAndAbort, ai/abort_utils_oracle_test.go::TestAbortUtilsMatchPi, internal/codingagent/tools/shell_tracked_test.go::TestKillTrackedDetachedChildrenEndsRunningMCPStdioServers, test/parity/scenarios/slash-commands/16-mcp-manager-while-connecting.toml
  • Release note: Fixed MCP stdio servers staying alive after PiG exited because the terminal closed, it crashed, or it received a termination signal. Pi ends their process groups from an exit hook; PiG now does the same on those exits.

BF-b430c7a

Extension widgets now appear in the order they were set when Node or other extension-process widgets and Go…

  • Source: PiG's own defect
  • Symptom: Extension widgets now appear in the order they were set when Node or other extension-process widgets and Go in-process widgets share a side of the editor, as in Pi.
  • Root cause: Pi's renderWidgets walks one Map per side in insertion order. PiG listed the extension-process widgets before the host-process ones. Widgets of both hosts now take a shared set sequence, and a replaced or moved one moves to the end.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): b430c7ae8d (2026-10-13)
  • Tests: internal/codingagent/widget_layout_test.go::TestWidgetsFollowTheOrderTheyWereSetAcrossExtensionHosts
  • Release note: Extension widgets now appear in the order they were set when Node or other extension-process widgets and Go in-process widgets share a side of the editor, as in Pi. The extension-process widgets came first.

BF-b4504dd

Amazon Bedrock request bodies match Pi's: PiG writes the ConverseStream body itself in the member order Pi's…

  • Source: PiG's own defect
  • Symptom: Amazon Bedrock request bodies match Pi's: PiG writes the ConverseStream body itself in the member order Pi's AWS SDK writes (messages, system, inferenceConfig, toolConfig, additionalModelRequestFields; role before content; toolUseId, name, input), tool schemas and tool call inputs keep their authored member order, and a tool schema with an empty property name no longer fails the request.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), da99854d1b (2026-10-09), 2309604d80 (2026-10-10), 25a75ebc88 (2026-10-10), 8e4a778324 (2026-10-10), b4504dd82c (2026-10-10), b4e83dbe17 (2026-10-10), c10cde302d (2026-10-10), eeac307095 (2026-10-10), f29e906cb3 (2026-10-10), fc886e3bd2 (2026-10-10), fe16108cc7 (2026-10-10)
  • Tests: agent/assistant_member_order_oracle_test.go::TestAssistantLateMembersFollowPiCreationOrder, agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses and 20 more in docs/bug-ledger.json
  • Release note: Amazon Bedrock request bodies match Pi's: PiG writes the ConverseStream body itself in the member order Pi's AWS SDK writes (messages, system, inferenceConfig, toolConfig, additionalModelRequestFields; role before content; toolUseId, name, input), tool schemas and tool call inputs keep their authored member order, and a tool schema with an empty property name no longer fails the request.

BF-b47b6bc

fix(tui): number the editor wrap divergence D159 (lead block), cite Pi's measured RangeError; add…

  • Source: PiG's own defect
  • Symptom: fix(tui): number the editor wrap divergence D159 (lead block), cite Pi's measured RangeError; add text-component width fuzz
  • Root cause: FuzzTextComponentsStayWithinWidth (Text, TruncatedText, Input, Box, WrapTextWithAnsi, TruncateToWidth; 60s green) added.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: b47b6bccd1 (2026-10-10)
  • Tests: tui/components_fuzz_test.go::FuzzTextComponentsStayWithinWidth

BF-b4822ef

pig install <dir> now installs a local Go, Rust or Python extension folder and loads it as an extension, as…

  • Source: PiG's own defect
  • Symptom: It refused the folder with "is an extension, not a package".
  • Root cause: Pi's resolveLocalExtensionSource loads a local directory that declares no filter, "pi" manifest or conventional resource directory as one extension (package-manager.ts:1377-1384), and Pig does so since. D57's refusal of a local Go, Rust or Python extension folder ("installing it as one loads nothing") was therefore false. VerifyContributesResources now refuses only npm and Git roots, where Pi's collectPackageResources has no such fallback (package-manager.ts:1317-1340).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: b4822efa1d (2026-10-09)
  • Tests: coding/cli/package_install_empty_test.go::TestInstallRecordsALocalExtensionDirectoryGivenRelativeToTheWorkingDirectory, coding/cli/package_install_empty_test.go::TestInstallRejectsAnExtensionDirectoryAsAPackage
  • Release note: pig install <dir> now installs a local Go, Rust or Python extension folder and loads it as an extension, as it already did for a Node folder. Before, it refused the folder with "is an extension, not a package". An npm or Git source whose root is a bare extension is still refused (D57).

BF-b4c6569

~/dir/ expands to the home directory with the trailing separator kept ($HOME/dir/), as Node's path.join…

  • Source: PiG's own defect
  • Symptom: ~/dir/ expands to the home directory with the trailing separator kept ($HOME/dir/), as Node's path.join does in Pi's path normalization; it was cleaned to $HOME/dir.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): b4c65694dc (2026-10-12)
  • Tests: None recorded.
  • Release note: ~/dir/ expands to the home directory with the trailing separator kept ($HOME/dir/), as Node's path.join does in Pi's path normalization; it was cleaned to $HOME/dir.

BF-b4e83db

Anthropic tools list eager_input_streaming and strict before input_schema, and the input schema and…

  • Source: PiG's own defect
  • Symptom: Anthropic tools list eager_input_streaming and strict before input_schema, and the input schema and Google function declarations keep the tool's member order (type, properties, required for the legacy Anthropic schema), as Pi does.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0ae4683246 (2026-10-08), e8cc072521 (2026-10-08), 433d232815 (2026-10-09), 2309604d80 (2026-10-10), b4e83dbe17 (2026-10-10), f29e906cb3 (2026-10-10), fe16108cc7 (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/anthropic_tool_schema_order_oracle_test.go::TestAnthropicToolsFollowPiMemberOrder and 13 more in docs/bug-ledger.json
  • Release note: Anthropic tools list eager_input_streaming and strict before input_schema, and the input schema and Google function declarations keep the tool's member order (type, properties, required for the legacy Anthropic schema), as Pi does. A strict schema adds required and additionalProperties after the members it was authored with, also when those were authored in sorted order.

BF-b4f1d4a

Pasting a large text that arrives in many terminal reads no longer stalls input: the paste is joined once…

  • Source: PiG's own defect
  • Symptom: Pasting a large text that arrives in many terminal reads no longer stalls input: the paste is joined once instead of being copied and searched again on every read.
  • Root cause: StdinBuffer kept upstream's pasteBuffer as one string (stdin-buffer.ts:324-343: pasteBuffer += buffer, then indexOf(BRACKETED_PASTE_END)). Each read copied, canonicalized and searched the whole paste so far, so a paste that arrives over n reads cost O(n^2): a 3.8 MB paste in 5,000 reads allocated 12 GB and took 7.1 s.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: b4f1d4a41a (2026-10-10)
  • Tests: tui/complexity_scaling_norace_test.go::TestScalingBracketedPaste, tui/stdin_buffer_paste_chunks_test.go::TestPasteOverManyReadsEqualsOnePasteBuffer
  • Release note: Pasting a large text that arrives in many terminal reads no longer stalls input: the paste is joined once instead of being copied and searched again on every read.

BF-b54f9aa

The tree selector label time of a session entry whose timestamp is not a valid date prints "aN/NaN/NaN…

  • Source: PiG's own defect
  • Symptom: The tree selector label time of a session entry whose timestamp is not a valid date prints "aN/NaN/NaN NaN:NaN", and a timestamp is read in any form Date.parse accepts, not only RFC 3339.
  • Root cause: tree-selector.ts:862-885 builds the label time from new Date(timestamp). Go parsed four RFC 3339 layouts only (any other date form gave no label time), returned "" for an invalid date where Pi prints "aN/NaN/NaN NaN:NaN" (NaN hours, month and day, and "NaN".slice(-2)), and padded the two-digit year with %02d of year%100 where Pi slices the last two characters of the year ("5" for year 5, "-5" for year -5). formatLabelTimestamp reads the timestamp with ai.DateParse and slices the year string. An entry written by Pi always carries an ISO timestamp, so this shows for edited or foreign session files; the old test that pinned "" for an unparseable string had no upstream basis and now pins Pi's…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: b54f9aa86a (2026-10-11)
  • Tests: tui/tree_label_timestamp_oracle_test.go::TestFormatLabelTimestampMatchesPi, tui/tree_select_test.go::TestFormatLabelTimestampForms
  • Release note: The tree selector label time of a session entry whose timestamp is not a valid date prints "aN/NaN/NaN NaN:NaN", and a timestamp is read in any form Date.parse accepts, not only RFC 3339.

BF-b5b7b43

Frontmatter YAML parsing matches the yaml library on text outside ASCII and on unterminated quotes: an…

  • Source: PiG's own defect
  • Symptom: Frontmatter YAML parsing matches the yaml library on text outside ASCII and on unterminated quotes: an unterminated quoted scalar reports its error at the end of the input, a line that starts with a non-ASCII character no longer changes the continuation indent of the lines after it, Invalid escape sequence quotes UTF-16 units, an implicit key's 1024-character limit counts UTF-16 units, anchors may not contain Unicode whitespace, and -0 is 0 as an !!omap or !!set key.
  • Root cause: TestParseMatchesTheLibraryOnMutatedCorpus and TestLexerMatchesTheLibraryOnMutatedCorpus compare eemeli/yaml 2.9.0 with Parse/lex on 6000 mutated corpus texts (40 differed before, 15-17 per 20000 after the first fixes). Fixed: an unterminated quote (buffer.substring(0, -1) is empty: the error is at the end of input, not after the first line); a line starting with a non-ASCII character reduced the continuation indent because the lexer looked at the second byte, not the second UTF-16 unit (charAt(1)); Invalid escape sequence raw text and the 1024-unit implicit key limit counted bytes; anchors reject JavaScript whitespace (RE2 \s is narrower); -0 is 0 as an omap/set key; Buffer.from(.., base64)…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 10e9b5c109 (2026-10-09), 24232ef878 (2026-10-09), 2b3403045b (2026-10-09), ba3f1c51ac (2026-10-09), d07038cb99 (2026-10-09), 919575a79c (2026-10-10), b5b7b43f82 (2026-10-10)
  • Tests: ai/classifier_answers_oracle_test.go::TestClassifierAnswersMatchPi, ai/provider_retry_matrix_oracle_test.go::TestProviderRetryMatchesPi, ai/system_one_shared_test.go::TestSystemOneSharedFailureMessages, coding/nested_recorder_oracle_test.go::TestNestedCallRecorderMatchesPi, coding/rpcclient/jsonl_reader_oracle_test.go::TestJSONLReaderMatchesPi, internal/codingagent/compaction/compaction_oracle_test.go::TestCompactionMatchesPi, internal/codingagent/paths_oracle_test.go::TestPathsMatchPi, internal/codingagent/session_file_bytes_oracle_test.go::TestSessionFileBytesMatchPi and 11 more in docs/bug-ledger.json
  • Release note: Frontmatter YAML parsing matches the yaml library on text outside ASCII and on unterminated quotes: an unterminated quoted scalar reports its error at the end of the input, a line that starts with a non-ASCII character no longer changes the continuation indent of the lines after it, Invalid escape sequence quotes UTF-16 units, an implicit key's 1024-character limit counts UTF-16 units, anchors may not contain Unicode whitespace, and -0 is 0 as an !!omap or !!set key.

BF-b5ebeb5

A resumed session draws its ! and !! command blocks, as Pi does

  • Source: PiG's own defect
  • Symptom: A resumed session draws its ! and !! command blocks, as Pi does.
  • Root cause: Every other exit the user did not ask for now leaves a trace (D102, additive): an exit.log line in the agent directory for SIGHUP, SIGTERM, an external SIGINT, a closed terminal or input, an extension's shutdown() and a failed run; a crash record for a panic or fatal error in any goroutine (through a per-session crash-output marker the next start reconciles); and pig diagnose lists the recent unexpected exits. The background cache warm recovers a panic and reschedules as Pi's does, the emergency terminal exit stops extension processes, and a reconciled crash keeps its real time.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a4753ec61e (2026-10-06, #170), ba9c37b247 (2026-10-06, #171), 4610c9f4a1 (2026-10-08, #189), b5ebeb58d1 (2026-10-08, #191)
  • Tests: coding/cli/rpc_initial_tools_test.go::TestRPCFreshPromptDeclaresStructuredSystemOnce, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleBusyEntriesAreSkippedNotErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledBeforeStartTouchesNothing, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledDuringTheLastEntryReportsTheCancellation, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleEntryLeasedAfterClassificationIsSkippedNotAnError, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleOtherErrorsStayErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleStopsWhenItsContextIsCancelled, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestIsBusyErrorLooksThroughWrappedJoins and 39 more in docs/bug-ledger.json
  • Release note: A resumed session draws its ! and !! command blocks, as Pi does. --session, --continue, /resume, /tree, /fork, /clone, /reload and a compaction rebuild drew nothing for a persisted command. Each block now shows its output, exit status, cancelled or truncation line as when it ran, starts collapsed, and keeps the dim borders of a !! command.

BF-b5fb4aa

fix(tui): a streamed Markdown is not resumed past a paragraph that a later LaTeX closer clips; keep wraps…

  • Source: PiG's own defect
  • Symptom: fix(tui): a streamed Markdown is not resumed past a paragraph that a later LaTeX closer clips; keep wraps across the trailing space row; regenerate interface inventories
  • Root cause:
    • resumeBarrier now also stops at a line whose rest after its first character opens block LaTeX (Lexer.ts:241-270 with markdown.ts:127-130 latexBlock start()); one helper, latexClipsFirstCharacter, serves the clip and the barrier. - The wrap memo rotates once per render (keepWraps), so the trailing space row's layout no longer drops it.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: b5fb4aad04 (2026-10-10)
  • Tests: tui/markdown_resume_test.go::TestResumedMarkdownEqualsAFreshRender, tui/markdown_streaming_reuse_test.go::TestStreamedMarkdownWrapsOnlyChangedLines

BF-b607c35

Started from a directory reached through a symlink (a $PWD naming a link), pig now uses the physical…

  • Source: PiG's own defect
  • Symptom: Started from a directory reached through a symlink (a $PWD naming a link), pig now uses the physical working directory as Pi does, so @file arguments are named by their physical path.
  • Root cause: main.ts:593 takes cwd from process.cwd() (getcwd(3)); os.Getwd returns $PWD when it names the same directory through a symlink, so a relative @file argument was named <file name="<link>/a.txt"> where Pi writes the physical path. Use nodepath.Process(), which already mirrors process.cwd().
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: b607c35564 (2026-10-13)
  • Tests: coding/cli/startup_physical_cwd_test.go::TestStartupWorkingDirectoryIsThePhysicalPath, internal/codingagent/file_processor_oracle_test.go::TestProcessCLIFileArgumentsMatchesPi
  • Release note: Started from a directory reached through a symlink (a $PWD naming a link), pig now uses the physical working directory as Pi does, so @file arguments are named by their physical path.

BF-b684b81

RPC and session JSON write an assistant message's members in the order Pi's providers assign them…

  • Source: PiG's own defect
  • Symptom: RPC and session JSON write an assistant message's members in the order Pi's providers assign them: reasoning before totalTokens in usage, and responseId after timestamp, in message events, get_messages, get_entries, get_tree and the session file.
  • Root cause: A provider builds its output as role, content, api, provider, model, usage, stopReason, timestamp and assigns responseId, responseModel and errorMessage afterwards (packages/ai/src/api/openai-completions.ts output literal and :564-566; the catch block's errorMessage). PiG's encoder wrote responseModel, responseId, providerThinkingLevel and diagnostics before usage, so the session file and the get_entries and get_tree wire differed from Pi in member order. The order now equals the event encoder's. Found by the rpc-session journey.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 3ec34c481e (2026-10-09), 412f886f75 (2026-10-09), b684b81d72 (2026-10-09)
  • Tests: agent/message_json_member_order_test.go::TestAssistantMessageJSONKeepsProviderMemberOrder, coding/cli/rpc_event_bytes_test.go::TestRPCGetMessagesKeepsPiMemberOrder, coding/cli/rpc_event_bytes_test.go::TestRPCUsageWireOrderPutsReasoningBeforeTotalTokens
  • Release note: RPC and session JSON write an assistant message's members in the order Pi's providers assign them: reasoning before totalTokens in usage, and responseId after timestamp, in message events, get_messages, get_entries, get_tree and the session file.

BF-b6c65b3

! command and RPC bash output no longer keeps fragments of color codes, such as a stray m, when an…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: ! command and RPC bash output no longer keeps fragments of color codes, such as a stray m, when an escape sequence is split across output chunks.
  • Root cause: Pi (#10504): utils/ansi.ts splitIncompleteAnsiSuffix and core/bash-executor.ts hold back a trailing unfinished OSC/CSI sequence (window of 256 UTF-16 units) until the next chunk, flush it and the streaming decoder at exit (also after an abort), and skip chunks that sanitize to nothing. SplitIncompleteANSISuffix replaces the lookahead with a scanner (RE2 has no lookahead); it agrees with Pi's function on 20000 random strings (one-off differential against node).
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: b6c65b3c10 (2026-10-07)
  • Tests: coding/session_bash_split_ansi_upstream_test.go::TestUpstreamBashEscapeSequencesSplitAcrossOutputChunks
  • Release note: ! command and RPC bash output no longer keeps fragments of color codes, such as a stray m, when an escape sequence is split across output chunks. PiG holds a trailing unfinished sequence of up to 256 characters until the next chunk or the end of the command.

BF-b6d8ab0

Fix changelog fragment and bug ledger naming the pre-squash commit of #307 (#317)

  • Source: PiG's own defect
  • Symptom: Fix changelog fragment and bug ledger naming the pre-squash commit of #307 (#317)
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: b6d8ab0bfa (2026-10-24, #317)
  • Tests: ai/socket_read_monitor_linux_norace_test.go::TestSocketWriterHelper, ai/socket_read_monitor_linux_norace_test.go::TestStreamingTransportReadDoesNotWakeTheRuntimeMonitor, coding/extension/host/subprocess/conn_close_test.go::TestCloseAfterThePeerClosedSucceeds, coding/extension/host/subprocess/conn_close_test.go::TestCloseReturnsARealTransportCloseFailure, coding/extension/host/subprocess/conn_close_test.go::TestTransportAlreadyClosedClasses, tui/config_selector_oracle_test.go::TestConfigSelectorMatchesPi, tui/terminal_negotiation_stop_test.go::TestTerminalInputCarriesOnlyWhatIsSent, tui/terminal_split_reply_test.go::TestForwardTerminalConsumesAnOwedReplySplitAcrossSlowReads

BF-b730205

OpenRouter image models that only the Images API serves (for example openai/gpt-image-2.5-flare) now…

  • Source: A Pi defect that PiG fixed first (divergence D128)
  • Symptom: OpenRouter image models that only the Images API serves (for example openai/gpt-image-2.5-flare) now generate: when chat/completions refuses the model, PiG retries through /images (D128, pi-mono #10652).
  • Root cause: the refusal is OpenRouter's own statement of which endpoint serves the model; the catalog carries no field that tells the two apart (FLUX and Gemini image models work through chat completions), so the endpoint is discovered from the response.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10652
  • Removed when Pi fixes it: Pi sends these models to the Images API.
  • Fix commits: 9cc4d406f7 (2026-10-10), b73020581b (2026-10-10)
  • Tests: ai/openrouter_images_api_endpoint_test.go::TestOpenRouterImagesAPIFallbackIsNarrowAndRunsOnce, ai/openrouter_images_api_endpoint_test.go::TestOpenRouterImagesAPIRequestRunsTheHooksAndJoinsTheTextInput, ai/openrouter_images_api_endpoint_test.go::TestOpenRouterImagesOtherFailuresDoNotUseTheImagesAPI, ai/openrouter_images_api_endpoint_test.go::TestOpenRouterImagesUsesTheImagesAPIWhenChatCompletionsRefusesTheModel
  • Release note: OpenRouter image models that only the Images API serves (for example openai/gpt-image-2.5-flare) now generate: when chat/completions refuses the model, PiG retries through /images (D128, pi-mono #10652).

BF-b751326

When an extension in a shared Go, Rust or Python process crashes, the error for each stopped extension now…

  • Source: PiG's own defect
  • Symptom: The message could only say "packed member connection closed".
  • Root cause: A Go extension in a packed Go cell that exits mid-tool-call stops its healthy siblings in the same process. Native packed cells are not restarted automatically; /reload recreates them (docs/runtime-cell-reload.md). The quarantine notice says so ("<names> stopped; /reload restarts them"), but when a member's socket-close handler detaches it before watchPackedProcess sees the process exit, the quarantine finds no members left. The user then sees only "extension "go" disabled: packed member connection closed", and later tool calls fail with "read failed: EOF" without explanation. Observed through pig with the robust-notes Go extension beside a crashing one. The connection-closed notice now…
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 311609427b (2026-10-07), 66bb7180b3 (2026-10-07), b751326e80 (2026-10-09)
  • Tests: coding/extension/host/subprocess/liveness_test.go::TestPackedMemberTransportClosureDoesNotProveProcessDeath, coding/extension/host/subprocess/packed_go_test.go::TestHost_KillingPackedProcessNeverClaimsItRemainedAlive, coding/resource_loader_extend_test.go::TestResourceLoaderInterfaceExtendsAndReloads, coding/session_extend_resources_test.go::TestSessionExtendsItsLoaderAfterExtensionReplacement, coding/session_extend_resources_test.go::TestSessionExtendsItsLoaderFromResourcesDiscover, coding/session_extend_resources_test.go::TestSessionWithNoResourcesDoesNotEmitResourcesDiscover
  • Release note: When an extension in a shared Go, Rust or Python process crashes, the error for each stopped extension now says that /reload restarts it. Before, the message could only say "packed member connection closed".

BF-b78cdae

A YAML parse error on a block scalar used as an implicit key (name: x followed by |description: ...)…

  • Source: PiG's own defect
  • Symptom: A YAML parse error on a block scalar used as an implicit key (name: x followed by |description: ...) draws the carets of the Implicit keys need to be on a single line message as the yaml library does when the scalar's content holds a non-ASCII character: the range ends one UTF-16 unit per character after the key's offset, where it used to count bytes and print one caret or too many.
  • Root cause: The library reports MULTILINE_IMPLICIT_KEY for a block scalar key at [offset, offset + source.length], where offset is the header and the source is the content that follows, so the range counts the content's UTF-16 units from the header. posOfToken added the content's byte length to the byte offset and converted that, so a multi-byte character in the content moved the end past the line and the message printed one caret, or too many ("a: 1\n|b: x\nÿ: 1" printed 1 caret, the library 5). pos now carries the length in units and prettify adds it to the converted start.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: b78cdae340 (2026-10-11)
  • Tests: internal/yaml12/parse_mutation_test.go::TestParseMatchesTheLibraryOnErrorRangesPastNonASCII
  • Release note: A YAML parse error on a block scalar used as an implicit key (name: x followed by |description: ...) draws the carets of the Implicit keys need to be on a single line message as the yaml library does when the scalar's content holds a non-ASCII character: the range ends one UTF-16 unit per character after the key's offset, where it used to count bytes and print one caret or too many.

BF-b84facb

fix(codingagent,tui): extension editor follows the terminal rows; title and image fallback close only the…

  • Source: PiG's own defect
  • Symptom: fix(codingagent,tui): extension editor follows the terminal rows; title and image fallback close only the foreground
  • Root cause: Pi's ExtensionEditorComponent builds its Editor with the TUI (extension-editor.ts), so the window is max(5, floor(rows*0.3)) lines; Pig fixed it at 5. The title used Accent+Reset (ESC[0m) where theme.fg closes with ESC[39m, and DefaultImageTheme did the same for the image fallback line.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: b84facb7d0 (2026-10-10)
  • Tests: internal/codingagent/extension_editor_test.go::TestExtensionEditorHandleInputMatchesPi, tui/image_theme_test.go::TestDefaultImageThemeClosesOnlyTheForeground

BF-b88044f

A login flow's text prompt accepts an empty answer, as Pi's does, instead of failing with "<prompt> is…

  • Source: PiG's own defect
  • Symptom: A login flow's text prompt accepts an empty answer, as Pi's does, instead of failing with "<prompt> is required".
  • Root cause: runLoginRegisteredOAuth rejected an empty text answer with "<prompt> is required" unless the prompt set allowEmpty. Pi's showPrompt resolves the submitted text, empty or not (login-dialog.ts:58-66), and showAuthPrompt never enforces allowEmpty (interactive-mode.ts:6278-6300). Since the bridge now reports non-cancel failures as errors, that check failed the flow. The prompt now returns the empty answer.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: b88044ff8a (2026-10-09)
  • Tests: internal/codingagent/config_oauth_login_cancel_test.go::TestConfigOAuthLoginSelectCancelReturnsToMenu, internal/codingagent/login_prompt_empty_test.go::TestOAuthLoginTextPromptAcceptsAnEmptyAnswer
  • Release note: A login flow's text prompt accepts an empty answer, as Pi's does, instead of failing with "<prompt> is required".

BF-b8c5de6

Interactive startup no longer reads terminal input while it checks for fd and rg unless one of them is being…

  • Source: PiG's own defect
  • Symptom: Interactive startup no longer reads terminal input while it checks for fd and rg unless one of them is being downloaded, as in Pi.
  • Root cause: Pi runs each ensureTool up to its first await in Promise.all argument order, so a present tool or one offline mode skips settles without reading input. ToolsManager.BeginEnsureTool now runs that prefix on the caller and returns the download when one remains; ensureManagedTools runs the prefixes on the owner loop and serves startup input only while downloads are in flight.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 2d99958e18 (2026-10-13), b8c5de64d3 (2026-10-13), f12896bcc6 (2026-10-13)
  • Tests: coding/extension/host/subprocess/runtime_node_sdk_bundle_test.go::TestNodeLibraryBundlesRegenerateExactly, coding/extension/host/subprocess/runtime_node_sdk_bundle_test.go::TestNodeSDKBundleRegeneratesExactly, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, internal/codingagent/interactive_init_state_pty_test.go::TestInitBuildsTheInteractiveStateBeforeTheSessionBindChild, internal/codingagent/startup_submit_test.go::TestColorReplyCompletesTheStartupWaitBeforeTheNextInput and 5 more in docs/bug-ledger.json
  • Release note: Interactive startup no longer reads terminal input while it checks for fd and rg unless one of them is being downloaded, as in Pi. When both tools are present, or offline mode skips the download, a prompt typed during startup reaches the ordinary submit handler instead of reporting "Startup is still in progress".

BF-b8df7eb

On Windows, a session's private snapshot file is created in the temporary directory instead of the session…

  • Source: PiG's own defect
  • Symptom: On Windows, a session's private snapshot file is created in the temporary directory instead of the session directory, where it was listed while the session was open.
  • Root cause: Product: - A Windows session snapshot is created in the temporary directory. It is a copy there, never a clone, and an open file beside the session file was listed in the session directory (TestAgentSessionRuntimeMatchesPi). - An image fallback links a slash-rooted path without a drive (/tmp/x.png) as Pi does: nodepath.IsAbsolute, not filepath.IsAbs. - An alt-screen frame reads the WezTerm environment only for the Kitty protocol, the one use of it; each Windows environment read allocated (TestAltScreenStreamingFrameAllocationCeiling).
  • User impact: Wrong output or ordering
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: b8df7eb579 (2026-10-15)
  • Tests: coding/agent_session_runtime_oracle_test.go::TestAgentSessionRuntimeMatchesPi, tui/alt_screen_frame_allocs_norace_test.go::TestAltScreenStreamingFrameAllocationCeiling, tui/terminal_split_reply_test.go::TestTerminalInputConsumesAnOwedReplySplitAcrossSlowReads
  • Release note: On Windows, a session's private snapshot file is created in the temporary directory instead of the session directory, where it was listed while the session was open.

BF-b8df7eb-2

On Windows, an image fallback links a path that starts with a slash but no drive (/tmp/x.png) as Pi does…

  • Source: PiG's own defect
  • Symptom: On Windows, an image fallback links a path that starts with a slash but no drive (/tmp/x.png) as Pi does, since path.isAbsolute counts it absolute.
  • Root cause: Product: - A Windows session snapshot is created in the temporary directory. It is a copy there, never a clone, and an open file beside the session file was listed in the session directory (TestAgentSessionRuntimeMatchesPi). - An image fallback links a slash-rooted path without a drive (/tmp/x.png) as Pi does: nodepath.IsAbsolute, not filepath.IsAbs. - An alt-screen frame reads the WezTerm environment only for the Kitty protocol, the one use of it; each Windows environment read allocated (TestAltScreenStreamingFrameAllocationCeiling).
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: b8df7eb579 (2026-10-15)
  • Tests: coding/agent_session_runtime_oracle_test.go::TestAgentSessionRuntimeMatchesPi, tui/alt_screen_frame_allocs_norace_test.go::TestAltScreenStreamingFrameAllocationCeiling, tui/terminal_split_reply_test.go::TestTerminalInputConsumesAnOwedReplySplitAcrossSlowReads
  • Release note: On Windows, an image fallback links a path that starts with a slash but no drive (/tmp/x.png) as Pi does, since path.isAbsolute counts it absolute.

BF-b8df7eb-3

Alt-screen frames outside the Kitty protocol no longer read the environment for WezTerm on every render…

  • Source: PiG's own defect
  • Symptom: Alt-screen frames outside the Kitty protocol no longer read the environment for WezTerm on every render, which allocated on Windows.
  • Root cause: Product: - A Windows session snapshot is created in the temporary directory. It is a copy there, never a clone, and an open file beside the session file was listed in the session directory (TestAgentSessionRuntimeMatchesPi). - An image fallback links a slash-rooted path without a drive (/tmp/x.png) as Pi does: nodepath.IsAbsolute, not filepath.IsAbs. - An alt-screen frame reads the WezTerm environment only for the Kitty protocol, the one use of it; each Windows environment read allocated (TestAltScreenStreamingFrameAllocationCeiling).
  • User impact: Wrong display
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: b8df7eb579 (2026-10-15)
  • Tests: coding/agent_session_runtime_oracle_test.go::TestAgentSessionRuntimeMatchesPi, tui/alt_screen_frame_allocs_norace_test.go::TestAltScreenStreamingFrameAllocationCeiling, tui/terminal_split_reply_test.go::TestTerminalInputConsumesAnOwedReplySplitAcrossSlowReads
  • Release note: Alt-screen frames outside the Kitty protocol no longer read the environment for WezTerm on every render, which allocated on Windows.

BF-b99457f

tool_search and codemode tool search match Chinese, Japanese and Korean queries: each such character is a…

  • Source: A Pi defect that PiG fixed first (divergence D173)
  • Symptom: tool_search and codemode tool search match Chinese, Japanese and Korean queries: each such character is a search term, where Pi's tokenizer dropped them (earendil-works/pi#10816; D173).
  • Root cause: A user who writes tool descriptions or queries in Chinese, Japanese or Korean cannot find their tools by search.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10816
  • Removed when Pi fixes it: Pi's tokenizer keeps Chinese, Japanese and Korean characters.
  • Fix commits: b99457fa23 (2026-10-11)
  • Tests: coding/extension/builtin/toolsearch/toolsearch_cjk_test.go::TestBm25RankerFindsAChineseQueryInChineseToolMetadata, coding/extension/builtin/toolsearch/toolsearch_cjk_test.go::TestTokenizeKeepsCJKCharactersAsTerms, coding/extension/builtin/toolsearch/toolsearch_oracle_test.go::TestToolSearchMatchesPi
  • Release note: tool_search and codemode tool search match Chinese, Japanese and Korean queries: each such character is a search term, where Pi's tokenizer dropped them (earendil-works/pi#10816; D173). Text in other scripts tokenizes as before.

BF-b9db06c

fix(subprocess): keep a user_bash operations object alive while its exec runs

  • Source: PiG's own defect
  • Symptom: fix(subprocess): keep a user_bash operations object alive while its exec runs
  • Root cause: The cleanup that releases the extension's operations handle runs when the object is unreachable, and Exec's last use of the object is the request it sends. A collection during the exec released the handle under the running extension exec, and the cancel test read the release notify where it expected the cancel (red: TestUserBashOperationsStayAliveWhileTheirExecRuns).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: b9db06c809 (2026-10-21)
  • Tests: None recorded.

BF-ba8b6b7

/logout reads stored credentials with Pi's 15-second signal, so an unreadable auth.json reports "Could…

  • Source: PiG's own defect
  • Symptom: /logout reads stored credentials with Pi's 15-second signal, so an unreadable auth.json reports "Could not read stored credentials" instead of "No stored credentials to remove".
  • Root cause: interactive-mode.ts:5836 calls listCredentials({ signal: AbortSignal.timeout(15_000) }); with a signal auth-storage.ts:431-432 readLatestData rethrows a failed reload instead of returning the last snapshot, and /logout shows "Could not read stored credentials" (interactive-mode.ts:6025-6029). PiG listed with context.Background(), took the unsignalled fallback and reported no stored credentials.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ba8b6b7030 (2026-10-13)
  • Tests: internal/codingagent/logout_provider_order_test.go::TestLogoutProviderOptionsReportAnUnreadableAuthFile
  • Release note: /logout reads stored credentials with Pi's 15-second signal, so an unreadable auth.json reports "Could not read stored credentials" instead of "No stored credentials to remove".

BF-ba9c37b

Starting pig no longer waits for the daily cleanup of the extension cache

  • Source: PiG's own defect
  • Symptom: Starting pig no longer waits for the daily cleanup of the extension cache.
  • Root cause: The prune loop checks for cancellation only before each entry. A busy failure that arrived after the cancellation counted as skipped, and the last entry's classification could be cut short with nothing to remove, so the collection returned success and the automatic collector wrote its marker for a run that did not finish. The collection now checks for cancellation before it reports success.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ba9c37b247 (2026-10-06, #171)
  • Tests: coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleBusyEntriesAreSkippedNotErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledBeforeStartTouchesNothing, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledDuringTheLastEntryReportsTheCancellation, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleEntryLeasedAfterClassificationIsSkippedNotAnError, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleOtherErrorsStayErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleStopsWhenItsContextIsCancelled, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestIsBusyErrorLooksThroughWrappedJoins, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestRemoveTreeContextContinuesPastBusyFile and 1 more in docs/bug-ledger.json
  • Release note: Starting pig no longer waits for the daily cleanup of the extension cache. The cleanup now runs in the background after the session is running, stops when pig exits, and skips entries that are in use or that the file system reports busy (such as NFS .nfs* files) instead of failing and repeating at every start. pig extensions cache prune reports the skipped entries, and pig extensions cache stats shows the last background failure.

BF-bada2af

Session and crash timestamps are read like Date.parse of a JavaScript string: U+00A0 and U+FEFF between…

  • Source: PiG's own defect
  • Symptom: Session and crash timestamps are read like Date.parse of a JavaScript string: U+00A0 and U+FEFF between tokens are white space, and U+2028 and U+2029 are not, as in V8.
  • Root cause: DateParse scanned the UTF-8 bytes of its argument, a rule written for a header ByteString, so a U+00A0 or U+FEFF between date tokens (two bytes) made a timestamp NaN where V8 treats it as one white space unit; session and crash-log timestamps (session-manager.ts:795, crash-log) are full JavaScript strings. DateParse now scans utf16.Encode of the string, DateParseHeader keeps the byte rule for the Last-Modified header (remote_catalog_provider). V8's keyword scan stops only at IsWhiteSpace, so U+2028/U+2029 start a keyword and invalidate the date, and its white space token is one unit.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: bada2af6dc (2026-10-11)
  • Tests: ai/js_date_parse_random_oracle_test.go::TestDateParseHeaderMatchesNodeOnRandomBytes, ai/js_date_parse_random_oracle_test.go::TestDateParseMatchesNodeOnRandomStrings
  • Release note: Session and crash timestamps are read like Date.parse of a JavaScript string: U+00A0 and U+FEFF between tokens are white space, and U+2028 and U+2029 are not, as in V8.

BF-bb324f7

A JavaScript or TypeScript extension loads when its registerCommand options carry a member Pi does not…

  • Source: PiG's own defect
  • Symptom: A JavaScript or TypeScript extension loads when its registerCommand options carry a member Pi does not define, such as args.
  • Root cause: TestRegistrationDeclarationFieldsReachTheRegistry fills each field of ToolDecl, CommandDecl, ShortcutDecl and FlagDecl on its own and requires Host.buildExtension to register something different, so a field the host drops between the wire declaration and the registry fails. It found CommandDecl.Args ("argument hint shown in /help"): no SDK sends it, the host never read it, and Pi's registerCommand has no such option (command registration is name, description, getArgumentCompletions, handler). Deleted per the caller-free rule.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: bb324f78bc (2026-10-09), d3b72472fe (2026-10-09)
  • Tests: coding/extension/host/subprocess/command_extra_option_test.go::TestNodeCommandWithAnUnknownOptionLoadsLikePi, coding/extension/host/subprocess/register_declaration_fields_wiring_test.go::TestRegistrationDeclarationFieldsReachTheRegistry
  • Release note: A JavaScript or TypeScript extension loads when its registerCommand options carry a member Pi does not define, such as args. Pi ignores such members; PiG forwarded args to the host, which rejected a value that was not a string, and the extension failed to load.

BF-bb3d661

An extension served in-process now refuses to serve a second connection while it serves one, with the error…

  • Source: PiG's own defect
  • Symptom: A second serve of the same sdk.Extension used to overwrite the first one's state and leave the second host waiting for a registration that never came.
  • Root cause: An Extension holds one connection, run context, tool lock and event bus. A second RunWithConn while the first ran overwrote that state and left the second host waiting for a register that never came: BenchmarkLoadInProcessProviders reused one Extension across a Host per iteration without waiting for the previous serve to end, and died with 'expected ready message, got shutdown' and then hung. The failure reproduces on origin/main with the same benchmark, so it is not the provider-queue change. The SDK now returns an error for the overlap; the benchmark waits for each serve to end.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: bb3d661967 (2026-10-25)
  • Tests: extensions/sdk/extension_serve_overlap_test.go::TestExtensionRefusesASecondConcurrentServe
  • Release note: An extension served in-process now refuses to serve a second connection while it serves one, with the error this extension is already serving a connection; an Extension serves one connection at a time, so build a new one per connection. A second serve of the same sdk.Extension used to overwrite the first one's state and leave the second host waiting for a registration that never came.

BF-bb888ae

The /model picker now keeps its model list when the catalog refresh times out, as in Pi

  • Source: PiG's own defect
  • Symptom: The /model picker now keeps its model list when the catalog refresh times out, as in Pi.
  • Root cause: model-catalog-refresh.ts races the refresh with the selector's signal, so the 15 second timeout rejects the call: the picker shows the timeout and keeps the list it had. PiG waited for the refresh to return and then reloaded the snapshot, showing models the aborted refresh added, and a refresh that ignored the abort never timed out.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: bb888aeef6 (2026-10-13)
  • Tests: tui/model_selector_refresh_oracle_test.go::TestModelSelectorRefreshModelsMatchesPi, tui/model_selector_runtime_test.go::TestModelSelectorConstructorKeepsTheListAfterATimedOutRefresh
  • Release note: The /model picker now keeps its model list when the catalog refresh times out, as in Pi. It showed models the aborted refresh had added, and a refresh that ignored the 15 second timeout kept the picker on "Refreshing model catalogs…" until it ended.

BF-bb9e9d8

fix(extensions): parallel Node runtime linking reports the walk-order first failure under every schedule

  • Source: PiG's own defect
  • Symptom: fix(extensions): parallel Node runtime linking reports the walk-order first failure under every schedule
  • Root cause: When i would also have failed, the reported error was the later one, not the walk-order first one the commit claims. The new TestLinkInWalkOrderRunsEveryIndexBelowTheFirstFailure fails under that rule in 2 of 3 -race runs and passes 5/5 -race runs with this fix (TestNodeRuntimeLinkReportsTheWalkOrderFirstFailure stays green under both). The workers now record the lowest failing index (a CAS minimum) and skip only indices above it, so every index below the lowest failure runs and its error is the first in errs. The loop moves into linkInWalkOrder so the schedule is testable without a filesystem; TestLinkInWalkOrderStopsOnACancelledContext pins the context check.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: bb9e9d83d1 (2026-10-11)
  • Tests: coding/extension/host/subprocess/node_runtime_cache_test.go::TestLinkInWalkOrderRunsEveryIndexBelowTheFirstFailure, coding/extension/host/subprocess/node_runtime_cache_test.go::TestLinkInWalkOrderStopsOnACancelledContext, coding/extension/host/subprocess/node_runtime_cache_test.go::TestNodeRuntimeLinkReportsTheWalkOrderFirstFailure

BF-bc24321

fix(mem M3): arm the idle collection at run end and run it after 500 ms

  • Source: PiG's own defect
  • Symptom: fix(mem M3): arm the idle collection at run end and run it after 500 ms
  • Root cause: A run's end now arms the D172 idle collection, so a run shorter than one spinner tick collects too. The arm survives wakes and clears when the collection fires, so input restarts the wait instead of dropping it. The delay follows Pi-Bolt's IDLE_GC_DELAY_MS (idle-gc.ts): 500 ms.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: bc243218dd (2026-10-13)
  • Tests: internal/codingagent/interactive_layout_idle_test.go::TestIdleHeapCollectionAfterRun

BF-bc4cd62

toPrecision rounds a value just below a power of ten (1e-11 to 16 digits, 9.999999999999998e9 to 16 digits)…

  • Source: PiG's own defect
  • Symptom: toPrecision rounds a value just below a power of ten (1e-11 to 16 digits, 9.999999999999998e9 to 16 digits) to the digits Node prints (9.999999999999999e-12, 9999999999.999998) instead of carrying it up to the power of ten.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: bc4cd62404 (2026-10-10)
  • Tests: internal/jsstring/toprecision_oracle_test.go::TestToPrecisionAgainstNodeCorpus, internal/jsstring/toprecision_oracle_test.go::TestToPrecisionKeepsTheExponentOfAValueJustBelowAPowerOfTen
  • Release note: toPrecision rounds a value just below a power of ten (1e-11 to 16 digits, 9.999999999999998e9 to 16 digits) to the digits Node prints (9.999999999999999e-12, 9999999999.999998) instead of carrying it up to the power of ten.

BF-bc8d37f

FileModelsStore roots and index keys: new live-Pi oracle…

  • Source: PiG's own defect
  • Symptom: FileModelsStore roots and index keys: new live-Pi oracle TestFileModelsStoreNonObjectRootsAndIndexKeysMatchPi (ai/models_store_file_oracle_test.go; 900 models-store.json files x 1-5 operations; null, array, number and boolean roots, integer-index provider ids, valid files holding null/number/string/array entries; TypeError text compared).
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), bc8d37f470 (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/models_store_file_oracle_test.go::TestFileModelsStoreNonObjectRootsAndIndexKeysMatchPi and 5 more in docs/bug-ledger.json
  • Release note: FileModelsStore roots and index keys: new live-Pi oracle TestFileModelsStoreNonObjectRootsAndIndexKeysMatchPi (ai/models_store_file_oracle_test.go; 900 models-store.json files x 1-5 operations; null, array, number and boolean roots, integer-index provider ids, valid files holding null/number/string/array entries; TypeError text compared). Real fixes: PiG rejected every non-object root where Pi accepts JSON arrays (read as an index-keyed object, written back as an array: a deleted or unset element is null, an index write extends the array) and numbers/booleans (read as empty, delete rewrites the scalar, write throws Cannot create property 'x' on number '5'), and a null root now throws the per-operation TypeErrors (Cannot read properties of null (reading 'x'), Cannot set properties of null (setting 'x'), Cannot convert undefined or null to object). TestFileModelsStoreRejectsInvalidJSONAndCancelledContexts asserted the old []-is-corrupt behaviour; it now uses corrupt text. 11 mutations red (+1 untestable: the 2^32-1 array-index bound). Not compared: a stored value that is not a catalog reads back as itself in Pi (PiG's typed entry cannot hold it, so such files are only written and deleted), string-valued roots, V8 JSON parse text.

BF-bcc2046

A nested list whose last item is empty but for spaces after its marker is followed by an empty row before the…

  • Source: PiG's own defect
  • Symptom: A nested list whose last item is empty but for spaces after its marker is followed by an empty row before the item's next text (- - \n a).
  • Root cause: '- - \n a' lexes as list, space ' \n', paragraph in marked, so Pi renders an empty row between the nested bullet and the text; '- -\n a' has a one-character remainder and no row. 9 new rows in TestMarkdownRenderMatchesPi (7 red before).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): bcc20464ec (2026-10-10)
  • Tests: tui/markdown_render_oracle_test.go::TestMarkdownRenderMatchesPi
  • Release note: A nested list whose last item is empty but for spaces after its marker is followed by an empty row before the item's next text (- - \n a).

BF-bd3e8ab

On arm64 and other CPUs with a fused multiply-add, Kimi, Radius and xAI OAuth credentials store the expires…

  • Source: PiG's own defect
  • Symptom: The expiry could differ in the last digit and the minutes by one at a half-minute boundary.
  • Root cause: Go may fuse xy + z into one multiply-add on arm64, ppc64le, s390x and riscv64, also across statements and through an inlined return; JavaScript rounds every product and return value first. An explicit float64() conversion now rounds: - expectedSavings = continuationProbability missCost - warmCost (cache-warmer.ts:390), which the decision event, /session and the warm threshold use; - the Radius shimmer offset (elapsedMs / 1000) * 10 before index - offset (radius-login-selector.ts:21-24); - smooth()'s return value, textFade and the fly-home arc of the easter egg (easter-egg-3d.ts:266-270, 1007, 1151-1152).
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a00dbe6901 (2026-10-13), bd3e8ab24b (2026-10-13)
  • Tests: ai/device_code_poll_oracle_test.go::TestDeviceCodePollDeadlineRoundsTheProductLikePi, ai/oauth_expiry_arithmetic_test.go::TestBuiltinOAuthExpiryUsesJavaScriptArithmetic, ai/openai_codex_http_error_test.go::TestCodexHTTPErrorResetMinutesRoundTheProductFirst, internal/codingagent/cache_warmer_test.go::TestCacheWarmingExpectedSavingsRoundsTheProductLikePi, internal/codingagent/pig_egg3d_test.go::TestPig3dFlightHomeOffsetsRoundLikePi, internal/codingagent/radius_login_oracle_test.go::TestRadiusLoginMenuRendersLikePi
  • Release note: On arm64 and other CPUs with a fused multiply-add, Kimi, Radius and xAI OAuth credentials store the expires time Pi computes for a fractional expires_in, the device-code login deadline matches Pi's, and the ChatGPT usage-limit message names the same "Try again in ~N min." as Pi: each x * 1000 product is rounded before it is added, as JavaScript does. Before, the expiry could differ in the last digit and the minutes by one at a half-minute boundary.

BF-bd3e8ab-2

On arm64 and other CPUs with a fused multiply-add, the cache-warming expected savings (shown by /session…

  • Source: PiG's own defect
  • Symptom: They could differ from Pi in the last digit, which changed some shimmer colors.
  • Root cause: Go may fuse xy + z into one multiply-add on arm64, ppc64le, s390x and riscv64, also across statements and through an inlined return; JavaScript rounds every product and return value first. An explicit float64() conversion now rounds: - expectedSavings = continuationProbability missCost - warmCost (cache-warmer.ts:390), which the decision event, /session and the warm threshold use; - the Radius shimmer offset (elapsedMs / 1000) * 10 before index - offset (radius-login-selector.ts:21-24); - smooth()'s return value, textFade and the fly-home arc of the easter egg (easter-egg-3d.ts:266-270, 1007, 1151-1152).
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: a00dbe6901 (2026-10-13), bd3e8ab24b (2026-10-13)
  • Tests: ai/device_code_poll_oracle_test.go::TestDeviceCodePollDeadlineRoundsTheProductLikePi, ai/oauth_expiry_arithmetic_test.go::TestBuiltinOAuthExpiryUsesJavaScriptArithmetic, ai/openai_codex_http_error_test.go::TestCodexHTTPErrorResetMinutesRoundTheProductFirst, internal/codingagent/cache_warmer_test.go::TestCacheWarmingExpectedSavingsRoundsTheProductLikePi, internal/codingagent/pig_egg3d_test.go::TestPig3dFlightHomeOffsetsRoundLikePi, internal/codingagent/radius_login_oracle_test.go::TestRadiusLoginMenuRendersLikePi
  • Release note: On arm64 and other CPUs with a fused multiply-add, the cache-warming expected savings (shown by /session, passed to extensions and compared with the $0.05 threshold), the "Sign in with Radius" shimmer and the 3D easter egg's blocks flying home match Pi: each product is rounded before it is added, as JavaScript does. Before, they could differ from Pi in the last digit, which changed some shimmer colors.

BF-bdb7ad8

A single server-sent-event line larger than 32 MiB (for example a base64 image chunk) no longer fails the…

  • Source: PiG's own defect
  • Symptom: A single server-sent-event line larger than 32 MiB (for example a base64 image chunk) no longer fails the stream with token too long on the providers that use PiG's SSE decoder.
  • Root cause: An earlier change replaced the invented 32 MiB cap with math.MaxInt, so a server that never ends a line grows the buffer until the process runs out of memory. Pi is bounded: each of its SSE readers holds the line in a JavaScript string, and Node cannot make one longer than buffer.constants.MAX_STRING_LENGTH (2^29-24; the OpenAI SDK LineDecoder throws 'Cannot create a string longer than 0x1fffffe8 characters', string buffers throw 'RangeError: Invalid string length'). sseLineLimit is that bound.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: bdb7ad8292 (2026-10-09), c715193fab (2026-10-09)
  • Tests: ai/sse_long_line_test.go::TestSSEDecoderAcceptsALineLargerThan32MiB, ai/sse_long_line_test.go::TestSSEDecoderBoundsALine, ai/sse_long_line_test.go::TestSSELineLimitIsNodesMaxStringLength
  • Release note: A single server-sent-event line larger than 32 MiB (for example a base64 image chunk) no longer fails the stream with token too long on the providers that use PiG's SSE decoder. A line is now bounded where Pi's runtime bounds it, at Node's maximum string length (512 MiB), so a server that never ends a line cannot exhaust memory (D134).

BF-beeb36a

Running with no home directory now fails the way Pi's package manager does, instead of reading the project's…

  • Source: PiG's own defect
  • Symptom: With no HOME and no entry for your user in the system's user database (a container run as a user its passwd file does not list), pig used to take the home directory to be empty, so the .agents/skills directory of the current project was listed as the user's ~/.agents/skills, with user scope and without the project-trust prompt.
  • Root cause: PiG's PackageManagerHomeDir ported process.env.HOME || homedir() (package-manager.ts:233-235, the pin's getHomeDir) with a failing branch that returned "" where Node's os.homedir() throws SystemError [ERR_SYSTEM_ERROR]: A system error occurred: uv_os_homedir returned ENOENT (no such file or directory). Pi's callers are resolvePath (:2213, :2217) and addAutoDiscoveredResources (:2464, the first statement that matters in resolve()), so Pi's resolve() rejects there; PiG's collectors ignore errors and rely on ValidateAutoDiscoveredOverrides, which runs first in startup, reload and the config command (validateConfiguredResourceEntries), to report what Pi's resolve() rejects with. That…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: beeb36afc7 (2026-10-24)
  • Tests: coding/cli/no_home_directory_test.go::TestResourceLoadingFailsWhereNoHomeDirectoryExists, coding/cli/no_home_directory_test.go::TestStartupFailsWhereNoHomeDirectoryExists, coding/cli/no_home_directory_test.go::TestStartupFollowsNodeWhenThereIsNoHomeDirectory, coding/cli/package_agents_skills_upstream_test.go::TestPackageManagerHomeDirFallsBackToSystemHome, internal/codingagent/home_dir_test.go::TestNoHomeDirectoryLeavesTheProjectsAgentsSkillsTheProjectsOnly, internal/codingagent/home_dir_test.go::TestPackageManagerHomeDirThrowsWhereNodesHomedirDoes, internal/codingagent/home_dir_test.go::TestValidateAutoDiscoveredOverridesRejectsWhereNoHomeDirectoryExists, internal/nodehome/nodehome_test.go::TestDirIsNodesOSHomedir and 1 more in docs/bug-ledger.json
  • Release note: Running with no home directory now fails the way Pi's package manager does, instead of reading the project's .agents/skills as your own. With no HOME and no entry for your user in the system's user database (a container run as a user its passwd file does not list), pig used to take the home directory to be empty, so the .agents/skills directory of the current project was listed as the user's ~/.agents/skills, with user scope and without the project-trust prompt. Startup in print and RPC mode, a reload and pig config now stop with A system error occurred: uv_os_homedir returned ENOENT (no such file or directory) and exit status 1, which is what Node's os.homedir(), and so Pi, reports there. Pi stops even earlier, at load, whatever the mode; PiG, which can run without a home directory by setting PIG_HOME, stops when it loads resources.

BF-bf45320

Go embedding hosts can bind extension appends through coding.Session.AppendExtensionEntry, retaining…

  • Source: PiG's own defect
  • Symptom: Previously, calling AppendCustomEntry allocated a new identity instead of retaining the subprocess ID and timestamp because the public Session API lacked a way to pass them through, leaving duplicate entries in the Node session mirror.
  • Root cause: Previously, calling AppendCustomEntry allocated a new identity instead of retaining the subprocess ID and timestamp because the public Session API lacked a way to pass them through, leaving duplicate entries in the Node session mirror.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: bf453203b6 (2026-10-23)
  • Tests: coding/session_custom_entries_test.go::TestAppendExtensionEntryPreservesIdentityAndEventSemantics, coding/session_custom_entries_test.go::TestAppendExtensionEntryRejectsWithoutChangingTheLog, coding/session_custom_entries_test.go::TestAppendExtensionEntrySharesTheNativeAppendLock, coding/session_custom_entries_test.go::TestAppendExtensionEntryThroughNodeHost
  • Release note: Go embedding hosts can bind extension appends through coding.Session.AppendExtensionEntry, retaining subprocess IDs and timestamps through the native Session writer and preserving entry_appended semantics. Previously, calling AppendCustomEntry allocated a new identity instead of retaining the subprocess ID and timestamp because the public Session API lacked a way to pass them through, leaving duplicate entries in the Node session mirror.

BF-c1055c4

Package manifest globs match as Node's fs.globSync does in Pi: brace sets and sequences, extglobs such as…

  • Source: PiG's own defect
  • Symptom: Package manifest globs match as Node's fs.globSync does in Pi: brace sets and sequences, extglobs such as *.@(md|txt), POSIX classes, . and empty path segments, and case-insensitive wildcard segments on Windows and macOS.
  • Root cause: 17 fail a test; the other lets the last combine's shortcut ignore dropEmpties, which is equivalent because the values loop already drops those empties. The port still equals Pi's copy on the 200,000-pattern corpus.
  • User impact: Behavior differed from Pi
  • Platform: Windows, macOS
  • Fixed in: 0.5.0
  • Fix commits: 4401fa4630 (2026-10-09), 4cc5ef904b (2026-10-09), 6584435bb6 (2026-10-09), 6e6b9132c2 (2026-10-09), 926f5d2e63 (2026-10-09), aff3d7266f (2026-10-09), d586d71f85 (2026-10-09), ef007a4e5e (2026-10-09), 6edaafdfe2 (2026-10-10), 6f8d83861a (2026-10-10), c1055c449e (2026-10-10), fce99d43d7 (2026-10-10)
  • Tests: coding/cli/cli_package_glob_test.go::TestCLIGitSourceRefreshesOncePerLoad, coding/cli/cli_package_glob_test.go::TestCLIPackageSourceGlobThrowFirstSourceWins, coding/cli/cli_package_glob_test.go::TestCLIPackageSourceGlobThrowFollowsPi, coding/cli/cli_package_glob_test.go::TestCLIPackageSourceInstallFailureStopsStartup, coding/cli/settings_file_url_upstream_test.go::TestNpmPackageManifestGlobThrowRejectsResolution, coding/cli/settings_file_url_upstream_test.go::TestSettingsPatternErrorsFollowPi, coding/cli/startup_init_budget_test.go::TestLazyRegexpPatternsAllCompile, coding/cli/startup_init_budget_test.go::TestLinkedPackagesCompileNoRegexpAtInit and 52 more in docs/bug-ledger.json
  • Release note: Package manifest globs match as Node's fs.globSync does in Pi: brace sets and sequences, extglobs such as *.@(md|txt), POSIX classes, . and empty path segments, and case-insensitive wildcard segments on Windows and macOS.

BF-c10ae73

In RPC mode, runs that an extension command's handler sends with sendMessage or sendUserMessage start in…

  • Source: PiG's own defect
  • Symptom: In RPC mode, runs that an extension command's handler sends with sendMessage or sendUserMessage start in the order it sent them, as in Pi, when the handler suspends or returns; before, two such runs could start in either order.
  • Root cause: A transport report released the starts an extension command held on one goroutine each, so two sends from one handler could start their runs in either order. Pi queues them on its one event loop in send order. Run them in order on one goroutine.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), c10ae7385e (2026-10-13)
  • Tests: coding/cli/rpc_command_runs_test.go::TestRPCCommandRunsReleaseHeldStartsInOrder, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-switch.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-start.toml
  • Release note: In RPC mode, runs that an extension command's handler sends with sendMessage or sendUserMessage start in the order it sent them, as in Pi, when the handler suspends or returns; before, two such runs could start in either order.

BF-c122088

A durable nested call keeps its arguments in the member order the caller wrote them, as Pi does: the stored…

  • Source: PiG's own defect
  • Symptom: A durable nested call keeps its arguments in the member order the caller wrote them, as Pi does: the stored call and execute intent, the pi.live nested slot, tool events, validation messages, and code mode's "took a different path" message (tools.name({...})) no longer sort them.
  • Root cause: codemode earlier() named the call an earlier attempt made with its arguments sorted, where Pi codemode.ts:783 writes JSON.stringify(made.arguments) in the script's member order. The order was lost before that: ExecuteTool takes a Go map, and startNestedCall stored a ToolCall with no member order, so every nested call's stored call, slot, execute intent, validation message and start event listed its arguments sorted. Session's copyTaskRecord also decoded each task record into Go maps, so a staged record lost the order of its input and checkpoint (Pi copyJson keeps it).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: c122088b8c (2026-10-13)
  • Tests: ai/validate_arguments_order_test.go::TestValidateToolArgumentsReportsTheArgumentsInTheCallsOrder, durable/codemode/codemode_test.go::TestDurableCodeMode, durable/codemode/models_test.go::TestCodeModeModels, durable/core/driver/harness_nested_slots_core_test.go::TestCoreANestedSlotShowsTheArgumentsTheCallRunsWith, durable/harness/harness_tasks_scheduling_test.go::TestTaskClose, durable/harness/tool_nested_test.go::TestANestedCallKeepsTheCallersArgumentOrder, durable/harness/tool_nested_test.go::TestANestedCallSlotShowsTheArgumentsItRunsWith
  • Release note: A durable nested call keeps its arguments in the member order the caller wrote them, as Pi does: the stored call and execute intent, the pi.live nested slot, tool events, validation messages, and code mode's "took a different path" message (tools.name({...})) no longer sort them. Code mode's models.* calls no longer store an extra order argument.

BF-c1ed918

fix(sdk-rs): give the request-start wire test's RequestMsg the batch, child_session, tool_call_index and…

  • Source: PiG's own defect
  • Symptom: fix(sdk-rs): give the request-start wire test's RequestMsg the batch, child_session, tool_call_index and args_raw fields
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: c1ed91817e (2026-10-21)
  • Tests: None recorded.

BF-c1fedc2

The durable read tool reads a file that grows while it is read, such as an active log, instead of failing…

  • Source: PiG's own defect
  • Symptom: The durable read tool reads a file that grows while it is read, such as an active log, instead of failing with "changed while it was read".
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): c1fedc2790 (2026-10-05)
  • Tests: None recorded.
  • Release note: The durable read tool reads a file that grows while it is read, such as an active log, instead of failing with "changed while it was read".

BF-c1fedc2-2

Bounded durable tool output drops a byte-order mark only at the start of byte output, and keeps a U+FEFF at…

  • Source: PiG's own defect
  • Symptom: Bounded durable tool output drops a byte-order mark only at the start of byte output, and keeps a U+FEFF at the start of the retained tail.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): c1fedc2790 (2026-10-05)
  • Tests: None recorded.
  • Release note: Bounded durable tool output drops a byte-order mark only at the start of byte output, and keeps a U+FEFF at the start of the retained tail.

BF-c225c97

fix(rpc): compaction_start precedes a refused replacement's error; D148 names the extra compaction_end; D141…

  • Source: PiG's own defect
  • Symptom: fix(rpc): compaction_start precedes a refused replacement's error; D148 names the extra compaction_end; D141 end-to-end evidence
  • Root cause: The D148 text names PiG's extra compaction_end(aborted). The request-time abort check keeps Pi's getAuth reason and its comment says why a caller-owned provider is not called after an abort.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: c225c976e5 (2026-10-10)
  • Tests: coding/cli/rpc_compaction_thinking_only_test.go::TestRPCCompactionSurvivesThinkingOnlyMessagesTheRequestsOmit, coding/cli/rpc_replacement_order_test.go::TestRPCRefusedReplacementLeavesTheCompactionOfItsBatchAlone

BF-c229e1c

A Pi Durable SQLite file written by PiG can be read by Pi, and the reverse, when a task kind, request ID, or…

  • Source: PiG's own defect
  • Symptom: A lookup by kind, request ID or document address missed rows written by the other implementation.
  • Root cause: The SQLite differential read each file back only through the storages, and its kinds, keys and request IDs were plain ASCII. encodeIndexedString and encodeJSON used encoding/json, which escapes <, >, & and U+2028/U+2029; Pi's sqlite/storage.ts uses JSON.stringify, which writes them literally. An indexed lookup compares the stored text, so scanTasks by kind, submissionByRequest, findDocument and scanDocuments by kind missed rows the other implementation wrote. This happened in both directions: Pi read Pig's file and Pig read Pi's. Both now use jsstring.MarshalJSON. The test now dumps each file through node:sqlite right after the writes and compares the schema and every row. That exposed a…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 97e178f7f6 (2026-10-09), c229e1cae8 (2026-10-10)
  • Tests: ai/streaming_json_oracle_test.go::TestStreamingJSONMatchesPiOnSeededInput, durable/codecs_test.go::TestDocumentRecordMarshalsInPiStorageOrder, durable/storage/memory_pi_oracle_test.go::TestMemoryStorageMatchesPiOnSeededPrograms, durable/storage/sqlite_pi_oracle_test.go::TestSqliteStorageMatchesPiOnSeededPrograms
  • Release note: A Pi Durable SQLite file written by PiG can be read by Pi, and the reverse, when a task kind, request ID, or document kind or key contains <, >, &, U+2028 or U+2029. PiG now stores indexed columns and records as JSON.stringify writes them. Before, a lookup by kind, request ID or document address missed rows written by the other implementation. Stored document records list createdAt and retiredAt after the other members, as Pi stores them.

BF-c2b2b8b

fix(tui): the inline memo is used only with tui's own style functions

  • Source: PiG's own defect
  • Symptom: fix(tui): the inline memo is used only with tui's own style functions
  • Root cause: Tests: - TestStreamedMarkdownWithAForeignThemeCallsItsStylesAgain and TestStreamedMarkdownWithAForeignTextColorCallsItAgain use style functions that read a changing generation. - TestStreamedMarkdownFollowsTheDefaultStyle changes one input per step. - TestStreamedMarkdownEqualsAFreshRender now streams with the default theme. Its reference reuses nothing, because one of its styles is wrapped, so a key without the lexer state fails it. - Mutants red: no purity check, no style flags, no default italic, a foreign Color allowed, no context comparison, no lexer state in the key.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: c2b2b8b860 (2026-10-10)
  • Tests: tui/markdown_streaming_reuse_test.go::TestStreamedMarkdownEqualsAFreshRender, tui/markdown_streaming_reuse_test.go::TestStreamedMarkdownFollowsTheDefaultStyle, tui/markdown_streaming_reuse_test.go::TestStreamedMarkdownRendersOnlyChangedInlineText, tui/markdown_streaming_reuse_test.go::TestStreamedMarkdownWithAForeignTextColorCallsItAgain, tui/markdown_streaming_reuse_test.go::TestStreamedMarkdownWithAForeignThemeCallsItsStylesAgain

BF-c350117

Fixed incorrect MCP reconnect behavior after cancellation during transport cleanup

  • Source: PiG's own defect
  • Symptom: Previously, reconnect still started a replacement connection after the caller had canceled.
  • Root cause: Reconnect did not check caller cancellation after closing the previous client, so it could still start a replacement connection.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: c3501172ab (2026-10-23)
  • Tests: coding/mcpext/reconnect_cancel_test.go::TestMCPReconnectDoesNotOpenAfterCancellationDuringCleanup
  • Release note: Fixed incorrect MCP reconnect behavior after cancellation during transport cleanup. Previously, reconnect still started a replacement connection after the caller had canceled. Reconnect did not check caller cancellation after closing the previous client, so it could still start a replacement connection. Reconnect now completes cleanup and returns the original cancellation cause before starting a replacement.

BF-c3a6eca

Markdown rendering follows Pi for lines made only of Unicode spaces (U+00A0, U+3000, U+2003 and the others…

  • Source: PiG's own defect
  • Symptom: Markdown rendering follows Pi for lines made only of Unicode spaces (U+00A0, U+3000, U+2003 and the others JavaScript's trim removes) in and after lists, a list item whose first line is one such space, a heading line holding U+2028 or U+2029, a table whose last line is such a space at a narrow width, and a $$ or \[ block whose text is only whitespace.
  • Root cause: Go's strings.TrimSpace strips U+0085 and keeps U+FEFF; JavaScript's trim() does the opposite, and marked's blank line, /^[ \t]*$/, is neither: a line of U+00A0, U+3000, U+2003 or U+2028 is text. Pig used TrimSpace for Pi's text.trim() === "" and for blank lines: Text rendered a BOM-only string as a row where Pi renders nothing, an assistant text block of U+FEFF was shown, the model selector took <U+0085>d as a prefix of "default", and a full-width space line (common between CJK paragraphs) ended a paragraph, a code block, a table or a heading's spacing where Pi keeps it as text.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: c3a6eca380 (2026-10-11)
  • Tests: tui/assistant_message_render_oracle_test.go::TestAssistantMessageComponentRenderMatchesPi, tui/basic_components_oracle_test.go::TestBasicComponentsRenderLikePi, tui/markdown_unicode_space_oracle_test.go::TestMarkdownUnicodeSpaceLinesMatchPi, tui/model_selector_oracle_test.go::TestModelSelectorInputMatchesPi, tui/user_message_render_oracle_test.go::TestUserMessageComponentRenderMatchesPi
  • Release note: Markdown rendering follows Pi for lines made only of Unicode spaces (U+00A0, U+3000, U+2003 and the others JavaScript's trim removes) in and after lists, a list item whose first line is one such space, a heading line holding U+2028 or U+2029, a table whose last line is such a space at a narrow width, and a $$ or \[ block whose text is only whitespace.

BF-c416945

fix(ai): tool-argument member order keeps a key with an escaped lone surrogate

  • Source: PiG's own defect
  • Symptom: fix(ai): tool-argument member order keeps a key with an escaped lone surrogate
  • Root cause: readSchemaObjectOrder read keys through encoding/json, which turns an escaped lone surrogate into U+FFFD, while the decoded object (extensions/sdk/json) keeps the surrogate as JSON.parse does; the order named a key the object did not have, so ArgumentsJSON wrote that member last. The key is now decoded from its literal with the faithful decoder.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: c416945cd5 (2026-10-10)
  • Tests: None recorded.

BF-c47bafd

A fused Go extension's built-in tool execute without an extension context (`createBashTool(cwd).execute(id…

  • Source: PiG's own defect
  • Symptom: A fused Go extension's built-in tool execute without an extension context (createBashTool(cwd).execute(id, params) and the other create<Tool>Tool forms) no longer fails with "extension stopped while waiting for builtinTool.execute response" when another extension in the same process stops while the tool runs.
  • Root cause: The sibling of the createLocalBashOperations().exec fix: createXTool(cwd, options).execute(id, params) without an extension context sent builtinTool.execute over runningExtension()'s connection, the first running extension in the process, so it ended with that extension's shutdown ("extension stopped while waiting for builtinTool.execute response").
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.3
  • Fix commits: c47bafd99d (2026-10-22)
  • Tests: test/extension-conformance/builtin_local_exec_fused_test.go::TestConformance_FusedBuiltinToolWithoutContextIsNotCarriedByAnotherExtension, test/extension-conformance/builtin_local_exec_fused_test.go::TestConformance_FusedLocalExecIsNotCarriedByAnotherExtension, test/extension-conformance/builtin_tool_delegate_test.go::TestConformance_BuiltinToolDelegationAcrossSDKs, test/extension-conformance/builtin_tool_delegate_test.go::TestConformance_BuiltinToolOverrideDelegatesAcrossSDKs
  • Release note: A fused Go extension's built-in tool execute without an extension context (createBashTool(cwd).execute(id, params) and the other create<Tool>Tool forms) no longer fails with "extension stopped while waiting for builtinTool.execute response" when another extension in the same process stops while the tool runs. The call has no Context, so it was sent over the connection of the first running extension in the process. The host now runs the tool for a fused extension directly, reaching back into the extension in the same process for the functions of its options and its streamed updates.

BF-c4ad739

Extension shortcut conflict warnings name both extensions ("registered by both A and B

  • Source: PiG's own defect
  • Symptom: Extension shortcut conflict warnings name both extensions ("registered by both A and B.
  • Root cause: Pi's buildGroups uses a Map (insertion order) and a stable sort; Pig collected groups from a Go map, so groups tying on origin, scope and source (auto resources from different baseDirs) came out in random order.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6d9b403c0a (2026-10-11), c4ad739eb8 (2026-10-11)
  • Tests: coding/extension/host/inproc/registries_test.go::TestShortcuts_DiagnosticMessageMatchesUpstream, coding/extension/host/inproc/shortcuts_oracle_test.go::TestRunnerShortcutsMatchPi, tui/config_selector_test.go::TestBuildResourceGroupsTiesKeepFirstSeenOrder
  • Release note: Extension shortcut conflict warnings name both extensions ("registered by both A and B. Using B."), and shortcut keys are lower-cased with JavaScript rules so İ no longer collides with i (coding/extension/host/inproc/runner.go Shortcuts, runner.ts:673-716; live-Pi oracle TestRunnerShortcutsMatchPi, 800 probes; mutations on the message, both ToLower sites and the reserved-wins rule fail it).

BF-c4d51d5

The OpenAI Codex login reads the account id from the access token the way Pi does: the token payload is…

  • Source: PiG's own defect
  • Symptom: The OpenAI Codex login reads the account id from the access token the way Pi does: the token payload is decoded as standard base64 with Pi's padding and whitespace rules (a payload using the URL-safe - or _ characters is not accepted), and the decoded bytes are read as Latin-1 characters, so a non-ASCII account id comes out as Pi's text.
  • Root cause: A differential test runs Pi's private decodeJwt and getAccountId in Node (testdata/codexaccount_id.mjs cuts them out of the pinned source) over 583 tokens and found 78 differences. Pi decodes the payload with atob(): the standard alphabet only, forgiving padding and ASCII whitespace, and one character per decoded byte, then JSON.parse. Pig used base64url (raw, then padded): it refused a payload with whitespace Pi reads, accepted '-' and '' that make atob throw, and read a UTF-8 account id as UTF-8 where Pi reads its bytes as Latin-1. jsAtob now implements the WHATWG forgiving-base64 decode. The length-1-mod-4 and alphabet checks were equivalent mutants (the unpadded standard decoder…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2c04c1445a (2026-10-09), c4d51d5c1a (2026-10-09)
  • Tests: ai/authorization_input_oracle_test.go::TestAuthorizationInputParsersMatchPi, ai/codex_account_id_oracle_test.go::TestCodexAccountIDMatchesPi
  • Release note: The OpenAI Codex login reads the account id from the access token the way Pi does: the token payload is decoded as standard base64 with Pi's padding and whitespace rules (a payload using the URL-safe - or _ characters is not accepted), and the decoded bytes are read as Latin-1 characters, so a non-ASCII account id comes out as Pi's text.

BF-c4fae28

Provider request bodies write a lone UTF-16 surrogate as its escape, as JSON.stringify does, instead of…

  • Source: PiG's own defect
  • Symptom: Provider request bodies write a lone UTF-16 surrogate as its escape, as JSON.stringify does, instead of U+FFFD for each byte of its Go form.
  • Root cause: marshalRequestBody used encoding/json, which writes U+FFFD for each of the three WTF-8 bytes a Go string holds for a lone UTF-16 surrogate, where JSON.stringify writes the unit's escape. Pi leaves some strings unsanitized (the Codex instructions, openai-codex-responses.ts:552; an assistant message's reasoning fields), so PiG sanitized the first and corrupted the second. A body with a U+FFFD character is now encoded with the JSON.stringify encoder (jsstring.MarshalJSON), also inside the request types' own MarshalJSON; any other body encodes as before, so the request allocation gates are unchanged.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: c4fae282a7 (2026-10-11)
  • Tests: ai/message_text_surrogate_test.go::TestMessageTextSanitizesLoneSurrogatesOnTheWire, ai/openai_message_marshal_test.go::TestOpenAIMessageEncodesAsItsMarshalJSON, ai/request_body_lone_surrogate_test.go::TestRequestBodyWritesALoneSurrogateAsItsEscape
  • Release note: Provider request bodies write a lone UTF-16 surrogate as its escape, as JSON.stringify does, instead of U+FFFD for each byte of its Go form. It matters for the strings Pi does not sanitize: the Codex instructions, now sent as Pi sends them, and the reasoning fields of an assistant message. A body with no U+FFFD character encodes as before.

BF-c4fef6c

Streaming long code blocks no longer grows memory with every frame: the syntax-highlight cache keeps one copy…

  • Source: PiG's own defect
  • Symptom: Streaming long code blocks no longer grows memory with every frame: the syntax-highlight cache keeps one copy of a streamed block and is bounded in bytes.
  • Root cause: The main screen asks the root for the lines kept since the revision it drew last. Line resets and the differential scan start at the first line that can differ: the earlier of the kept prefix and both frames' cursor rows. The per-line reuse of earlier reset results stays, but only while the previous input array is intact: it was composed with overlays, or the root has rendered at most once since. Overlay frames keep nothing, and a frame after RestoreRenderState scans everything. Mouse dispatch reads child heights from the window.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 10aebd5c05 (2026-10-09), 136e3703be (2026-10-10), 79eadcf623 (2026-10-10), ab5be97bbe (2026-10-10), c4fef6c21a (2026-10-10)
  • Tests: ai/complexity_scaling_norace_test.go::TestScalingProviderRequestPerTurn, ai/complexity_scaling_norace_test.go::TestScalingProviderStreamPerDelta, coding/cli/complexity_scaling_norace_test.go::TestScalingExtensionEventStatePush, coding/complexity_scaling_norace_test.go::TestScalingSessionTurn, coding/extension/host/subprocess/complexity_scaling_norace_test.go::TestScalingMessageUpdatePayload, internal/codingagent/compaction/complexity_scaling_norace_test.go::TestScalingCompactionAndContextEstimate, internal/codingagent/complexity_scaling_norace_test.go::TestScalingSessionLogOperations, internal/codingagent/tools/complexity_scaling_norace_test.go::TestScalingTools and 11 more in docs/bug-ledger.json
  • Release note: Streaming long code blocks no longer grows memory with every frame: the syntax-highlight cache keeps one copy of a streamed block and is bounded in bytes.

BF-c51c77e

An extension that calls registerTool({}) or registerTool(null) now fails to load with Pi's error text…

  • Source: PiG's own defect
  • Symptom: An extension that calls registerTool({}) or registerTool(null) now fails to load with Pi's error text (the missing parameter schema, or the TypeError of reading parameters), not a PiG-only message.
  • Root cause: The Node runtime threw its own 'registerTool requires a definition with name' before Pi's checks. Pi's loader.ts:289-295 reads tool.parameters first, so {} fails the object-schema check (Tool "undefined" ...) and null throws the TypeError of the property read.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: c51c77e0b5 (2026-10-11)
  • Tests: coding/extension/host/subprocess/upstream_tool_schema_test.go::TestRegisterToolWithoutANameFailsLikePisLoader
  • Release note: An extension that calls registerTool({}) or registerTool(null) now fails to load with Pi's error text (the missing parameter schema, or the TypeError of reading parameters), not a PiG-only message.

BF-c58ceed

chord/host.Forbidden("") keeps the empty message, as Pi's forbidden("") does; Forbidden() still says…

  • Source: PiG's own defect
  • Symptom: chord/host.Forbidden("") keeps the empty message, as Pi's forbidden("") does; Forbidden() still says "forbidden".
  • Root cause: New package chord/host (the runtime of Chord's root export; the shared base stays in chord so the durable core links none of it) with packages/chord/src/schema.ts (canonicalJson with UTF-16 key order, hash64, deepEqual, assertValid, normalize, canonicalIdentity, fits), contract.ts (method, opaque, remote, local, coverage, bind, descriptions), errors.ts (ChordError, isChordError, forbidden, invalid) and wire.ts (the Frame union as a sealed interface, FrameSchema, isFrame, toWireError, fromWireError).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: c58ceeda89 (2026-10-12), fa905766d4 (2026-10-12)
  • Tests: ai/validate_formats_oracle_test.go::TestTypeboxFormatsMatchPiOnSeededStrings, chord/host/boundary_upstream_test.go::TestPackageBoundaryDoesNotDependOnPiPackagesOrFilesOutsideChord, chord/host/contract_upstream_test.go::TestContractsBindOtherVersionsOnlyThroughAcceptsAdapters, chord/host/contract_upstream_test.go::TestContractsCompareOpaqueMembersByNameVersionAndFunctionMembersStructurally, chord/host/contract_upstream_test.go::TestContractsCoverAdditiveChangesAndRefuseBreakingOnes, chord/host/contract_upstream_test.go::TestContractsFingerprintsAreStableAndDescriptionBased, chord/host/contract_upstream_test.go::TestContractsNormalizeInputAndOmitDefaultsFromTheIdentity, chord/host/contract_upstream_test.go::TestContractsRejectReservedMemberNamesAndOpaqueRemoteMembers and 12 more in docs/bug-ledger.json
  • Release note: chord/host.Forbidden("") keeps the empty message, as Pi's forbidden("") does; Forbidden() still says "forbidden". IsChordError takes the code as an optional argument and, like ToWireError, no longer looks through wrappers, so an aggregate of ChordErrors goes on the wire as unavailable, as in Pi. ChordError.Name is "ChordError".

BF-c5ce588

Gemini 4 and later models send thinkingLevel instead of thinkingBudget, fixing Gemini 4 Argon on Vertex…

  • Source: PiG's own defect
  • Symptom: Gemini 4 and later models send thinkingLevel instead of thinkingBudget, fixing Gemini 4 Argon on Vertex and the ignored thinking level on the Gemini API.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: c5ce588c58 (2026-10-14, #10890)
  • Tests: ai/google_thinking_level_map_upstream_test.go::TestGoogleThinkingLevelMapRegression
  • Release note: Gemini 4 and later models send thinkingLevel instead of thinkingBudget, fixing Gemini 4 Argon on Vertex and the ignored thinking level on the Gemini API. Carried forward from Pi 1692bba6 (earendil-works/pi#10890), past the 1.2.0+3daa2028 pin.

BF-c625bb4

The durable powershell tool fails with "No command to run" when its program list is empty, instead of…

  • Source: PiG's own defect
  • Symptom: The durable powershell tool fails with "No command to run" when its program list is empty, instead of reporting success.
  • Root cause: Pi's runCommand throws "No command to run" when no command form ran, which createPowerShellTool({ programs: [] }) reaches (packages/durable/src/tools/bash.ts runCommand). PiG returned success without running anything.
  • User impact: An operation failed or was rejected
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: c625bb4536 (2026-10-05)
  • Tests: durable/tools/powershell_upstream_test.go::TestPowerShellFailsWithoutAProgramToTry
  • Release note: The durable powershell tool fails with "No command to run" when its program list is empty, instead of reporting success.

BF-c6c2cf3

An env request whose context is already cancelled is now always sent to the daemon and then cancelled, as…

  • Source: PiG's own defect
  • Symptom: An env request whose context is already cancelled is now always sent to the daemon and then cancelled, as in Pi, instead of failing about half the time with a local aborted error before it was sent.
  • Root cause: env/daemon: an overflow during open (a switch in the first sync, e.g. ENOSPC) went out as {"kind":"change","overflow":true} without the mode, because the watcher was not stored yet; watch.rs:583 always sends "mode":"polling" with it, since only switch_to_polling reports overflow. The event mapping is watchEvent; TestWatchEventsMatchThe RustDaemon pins the three event shapes (not red-proven: the function is new; the pre-fix failure is the missing mode during open, and dropping the mode kills the test).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1f5a782c0a (2026-10-08), 5dd595fb87 (2026-10-08), beefcd709e (2026-10-09), f68aa400e5 (2026-10-09), ffe6a53a07 (2026-10-09), c6c2cf3239 (2026-10-10)
  • Tests: chord/delta/apply_immutable_upstream_test.go::TestCheckedImmutableOperationApplication, chord/delta/delta_upstream_test.go::TestDeltaCanonicalStrings, chord/delta/delta_upstream_test.go::TestDeltaPathAndPrototypeSafety, chord/delta/json_kind_message_test.go::TestCloneJSONReportsPisMessageForEachGoKind, chord/delta/normalize_error_order_test.go::TestDiffCandidateReportsTheFirstInvalidPropertyDeterministically, chord/delta/retention_upstream_test.go::TestDeltaTrackerRetentionAcrossGCJobs, chord/delta/retention_upstream_test.go::TestTrackerReleasesADroppedOpenChange, chord/delta/tracker_delta_lifecycle_upstream_test.go::TestDeltaImmutableTrackerLifecycle and 8 more in docs/bug-ledger.json
  • Release note: An env request whose context is already cancelled is now always sent to the daemon and then cancelled, as in Pi, instead of failing about half the time with a local aborted error before it was sent. A request cancelled while the daemon is starting now waits for the start, as in Pi.

BF-c7ebb23

A cancelled prompt in a config.oauth login now rejects with Pi's Login cancelled in every SDK (it was…

  • Source: PiG's own defect
  • Symptom: A cancelled prompt in a config.oauth login now rejects with Pi's Login cancelled in every SDK (it was oauth prompt cancelled, and Node's onSelect resolved undefined), so /login returns to the menu instead of reporting a failed login.
  • Root cause:
    • the registry composed the provider's auth from the built-in catalog, not from the object, so its OAuth method, loginLabel and own API-key login were never offered. Pi's composeProvider takes the native provider as the base (model-runtime.ts:301-303 nativeExtensionProviders.get(id) ?? builtins.get(id)); registryAuthConfig now does the same, so the methods come from the retained provider object on every register, refresh and publication path; - the host carrier did not map auth.apiKey.login, auth.oauth.login or loginLabel. It now does, and their prompt and notify reverse calls decode into the closed AuthPrompt and AuthEvent unions (auth/types.ts); any other type fails the login with an…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 08bf0761c9 (2026-10-09, #201), 26d302a2d7 (2026-10-09, #201), c7ebb23d87 (2026-10-09)
  • Tests: coding/extension/host/subprocess/native_provider_login_test.go::TestDecodeNativeLoginCallback, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeAuthInteractionAnswersTheLoginDialog, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeProviderCarrierCarriesDeclaredLogins, coding/extension/host/subprocess/native_provider_oauth_test.go::TestNativeProviderOAuthLoginCallbacks, coding/extension/host/subprocess/oauth_bridge_test.go::TestOAuthCallbackInitiationFollowsRealCallbackEntry, coding/extension/host/subprocess/oauth_legacy_callbacks_test.go::TestOAuthProxy_LoginRelaysTheLegacyCallbackSurface, coding/extension/host/subprocess/oauth_legacy_callbacks_test.go::TestOAuthProxy_ManualCodeInputShowsPisPromptMessage, internal/codingagent/config_oauth_login_cancel_test.go::TestConfigOAuthLoginSelectCancelReturnsToMenu and 6 more in docs/bug-ledger.json
  • Release note: A cancelled prompt in a config.oauth login now rejects with Pi's Login cancelled in every SDK (it was oauth prompt cancelled, and Node's onSelect resolved undefined), so /login returns to the menu instead of reporting a failed login.

BF-c7ebb23-2

A config.oauth login's onManualCodeInput asks "Paste the authorization code", as Pi does, and a provider…

  • Source: PiG's own defect
  • Symptom: A config.oauth login's onManualCodeInput asks "Paste the authorization code", as Pi does, and a provider object's manual_code prompt shows its own message and placeholder.
  • Root cause:
    • the registry composed the provider's auth from the built-in catalog, not from the object, so its OAuth method, loginLabel and own API-key login were never offered. Pi's composeProvider takes the native provider as the base (model-runtime.ts:301-303 nativeExtensionProviders.get(id) ?? builtins.get(id)); registryAuthConfig now does the same, so the methods come from the retained provider object on every register, refresh and publication path; - the host carrier did not map auth.apiKey.login, auth.oauth.login or loginLabel. It now does, and their prompt and notify reverse calls decode into the closed AuthPrompt and AuthEvent unions (auth/types.ts); any other type fails the login with an…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 08bf0761c9 (2026-10-09, #201), 26d302a2d7 (2026-10-09, #201), c7ebb23d87 (2026-10-09)
  • Tests: coding/extension/host/subprocess/native_provider_login_test.go::TestDecodeNativeLoginCallback, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeAuthInteractionAnswersTheLoginDialog, coding/extension/host/subprocess/native_provider_login_test.go::TestNativeProviderCarrierCarriesDeclaredLogins, coding/extension/host/subprocess/native_provider_oauth_test.go::TestNativeProviderOAuthLoginCallbacks, coding/extension/host/subprocess/oauth_bridge_test.go::TestOAuthCallbackInitiationFollowsRealCallbackEntry, coding/extension/host/subprocess/oauth_legacy_callbacks_test.go::TestOAuthProxy_LoginRelaysTheLegacyCallbackSurface, coding/extension/host/subprocess/oauth_legacy_callbacks_test.go::TestOAuthProxy_ManualCodeInputShowsPisPromptMessage, internal/codingagent/config_oauth_login_cancel_test.go::TestConfigOAuthLoginSelectCancelReturnsToMenu and 6 more in docs/bug-ledger.json
  • Release note: A config.oauth login's onManualCodeInput asks "Paste the authorization code", as Pi does, and a provider object's manual_code prompt shows its own message and placeholder.

BF-c8a90a6

Pressing the login dialog's cancel key again cancels again, and Enter after a cancelled prompt leaves the…

  • Source: PiG's own defect
  • Symptom: Pressing the login dialog's cancel key again cancels again, and Enter after a cancelled prompt leaves the field unchanged, as in Pi.
  • Root cause: (1) login-dialog.ts handleInput has no done guard: a second cancel key aborts and calls onComplete again; Pig ignored every key once cancelled. (2) The Input's onSubmit acts only while an input resolver is pending (replaceInputWithSubmittedText + resolve); after cancel Pig still replaced the field with '> text'. HandleInput drops the done guard and OnSubmit returns when no input is pending.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: c8a90a61db (2026-10-07)
  • Tests: tui/login_dialog_oracle_test.go::TestLoginDialogInputMatchesPi
  • Release note: Pressing the login dialog's cancel key again cancels again, and Enter after a cancelled prompt leaves the field unchanged, as in Pi.

BF-c8e3d86

Settings submenus: the type-to-filter box no longer shows a hardware cursor marker (Pi never focuses it), and…

  • Source: PiG's own defect
  • Symptom: Settings submenus: the type-to-filter box no longer shows a hardware cursor marker (Pi never focuses it), and the highlight callback (theme preview) now fires on every up/down like Pi's SelectList, including when the highlight stays on the same item.
  • Root cause: The assignment is removed. SelectSubmenuComponent now has OnSelectionChange (set by newSelectSubmenuStep, its production caller) fired after up/down. Frames compare as text (Pi bold/fg closers vs Pig SGR 0 differ, open renderer question). Overlapping rebinds (a key shared by cancel and up) are not probed: FilterableList checks cancel before up unlike Pi's SelectList; follow-up.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d1f258f335 (2026-10-06), 56a0400b9c (2026-10-07), c8e3d86378 (2026-10-07), ccaade27af (2026-10-07), f2a2b3409b (2026-10-07)
  • Tests: internal/codingagent/settings_submenu_oracle_test.go::TestSettingsSubmenusMatchPi, internal/codingagent/thinking_selector_oracle_test.go::TestThinkingSelectorInputMatchesPi, internal/codingagent/tree_row_format_test.go::TestFormatTreeRow_AllEntryTypes, internal/codingagent/tree_selector_oracle_test.go::TestTreeSelectorInputMatchesPi, test/parity/scenarios/settings/06-thinking-selector-search-editing.toml, test/parity/scenarios/settings/08-settings-search-editing.toml, tui/editor_bindings_oracle_test.go::TestEditorRebindingsMatchPi, tui/input_oracle_test.go::TestInputHandleInputMatchesPi and 10 more in docs/bug-ledger.json
  • Release note: Settings submenus: the type-to-filter box no longer shows a hardware cursor marker (Pi never focuses it), and the highlight callback (theme preview) now fires on every up/down like Pi's SelectList, including when the highlight stays on the same item.

BF-c98e9f9

Without HOME set, the project trust check takes the home directory from the user database, as Pi's…

  • Source: PiG's own defect
  • Symptom: Without HOME set, the project trust check takes the home directory from the user database, as Pi's os.homedir() does, instead of treating the working directory as the home directory.
  • Root cause: hasTrustRequiringProjectResources canonicalizes process.env.HOME || homedir() (trust-manager.ts:187), and Node's os.homedir() reads the user database when HOME is unset or empty. PiG fell back to os.UserHomeDir, which reads only HOME, and resolved the empty result against the working directory: the working directory then counted as the user's home, its .agents/skills never required project trust, and every trust check realpathed the working directory twice.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 02c405c179 (2026-10-13), c98e9f9bbd (2026-10-13)
  • Tests: coding/cli/path_resolve_ports_test.go::TestPathPortsReportAnUnreadableWorkingDirectory, coding/cli/path_resolve_ports_test.go::TestSamePathAndAncestorSkillDirsResolveLikeNode, internal/codingagent/trust_manager_test.go::TestHasTrustRequiringProjectResourcesWithoutHOMEStillChecksTheWorkingDirectory
  • Release note: Without HOME set, the project trust check takes the home directory from the user database, as Pi's os.homedir() does, instead of treating the working directory as the home directory. A working directory's .agents/skills now requires project trust in that case too.

BF-c9acbea

A Session listener that queues more messages than the Session's event buffer holds (64 by default), while it…

  • Source: PiG's own defect
  • Symptom: A Session listener that queues more messages than the Session's event buffer holds (64 by default), while it runs on the goroutine that emitted its event, publishes its queue_update events in order.
  • Root cause: The Session's event goroutine waited for execMu, a sync.Mutex, while a producer ran a callback inline under it. testing/synctest does not count a Mutex waiter as durably blocked, so synctest.Wait never returned while the callback was held: TestSessionObservationCloseJoinsClaimedCallback hung on its first run (the executor's shutdown waited for execMu), and TestCompactionFailureWaitsForEndListeners needed a synctest.Wait before compaction to dodge the same wait. execMu is now a lock whose waiters block on a channel receive; taking a free lock is one atomic add. The compaction test's workaround is removed.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 375de4a98c (2026-10-12), c9acbead45 (2026-10-13)
  • Tests: coding/cli/strip_providers_binary_test.go::TestStripBedrockConverseStreamBuildOmitsAWSSDK, coding/session_compaction_event_order_test.go::TestCompactionFailureWaitsForEndListeners, coding/session_queue_publication_test.go::TestSessionMessageSubscriberQueueBurstPreservesPublicationOrder, coding/session_queue_publication_test.go::TestSessionQueuePublicationOverflowIsReleasedOnClose, coding/session_rpc33_observation_test.go::TestSessionObservationCloseJoinsClaimedCallback, coding/session_rpc33_observation_test.go::TestSessionObservationReservationCanceledBeforeAdmission, third_party/awssdk/internal/endpoints/v2/lazy_test.go::TestLazyRegexpMatchesLikeRegexp
  • Release note: A Session listener that queues more messages than the Session's event buffer holds (64 by default), while it runs on the goroutine that emitted its event, publishes its queue_update events in order. An update could reach Events ahead of the one published just before it, so a subscriber saw a queue with one message too many. Every event published after such a burst now runs after it.

BF-c9acbea-2

A goroutine that waits to run Session events, such as the Session's event goroutine while a producer runs a…

  • Source: PiG's own defect
  • Symptom: A goroutine that waits to run Session events, such as the Session's event goroutine while a producer runs a listener itself, now blocks on a channel receive instead of a sync.Mutex, so testing/synctest counts it as durably blocked.
  • Root cause: The Session's event goroutine waited for execMu, a sync.Mutex, while a producer ran a callback inline under it. testing/synctest does not count a Mutex waiter as durably blocked, so synctest.Wait never returned while the callback was held: TestSessionObservationCloseJoinsClaimedCallback hung on its first run (the executor's shutdown waited for execMu), and TestCompactionFailureWaitsForEndListeners needed a synctest.Wait before compaction to dodge the same wait. execMu is now a lock whose waiters block on a channel receive; taking a free lock is one atomic add. The compaction test's workaround is removed.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 375de4a98c (2026-10-12), c9acbead45 (2026-10-13)
  • Tests: coding/cli/strip_providers_binary_test.go::TestStripBedrockConverseStreamBuildOmitsAWSSDK, coding/session_compaction_event_order_test.go::TestCompactionFailureWaitsForEndListeners, coding/session_queue_publication_test.go::TestSessionMessageSubscriberQueueBurstPreservesPublicationOrder, coding/session_queue_publication_test.go::TestSessionQueuePublicationOverflowIsReleasedOnClose, coding/session_rpc33_observation_test.go::TestSessionObservationCloseJoinsClaimedCallback, coding/session_rpc33_observation_test.go::TestSessionObservationReservationCanceledBeforeAdmission, third_party/awssdk/internal/endpoints/v2/lazy_test.go::TestLazyRegexpMatchesLikeRegexp
  • Release note: A goroutine that waits to run Session events, such as the Session's event goroutine while a producer runs a listener itself, now blocks on a channel receive instead of a sync.Mutex, so testing/synctest counts it as durably blocked. A synctest test that waited while a listener was held no longer hangs until the package timeout.

BF-c9f1cff

The experimental client takes Pi's ByteTransportFactory shape: ClientOptions.TransportFactory returns a…

  • Source: PiG's own defect
  • Symptom: The experimental client takes Pi's ByteTransportFactory shape: ClientOptions.TransportFactory returns a ByteTransport whose Send(ctx, chunk) returns when the write settles.
  • Root cause: Production keeps Pi's semantics through the Pi shape: a handler call made during the factory is live (the handler gate is removed; the unix transport starts reading when the Connection has taken it, readStarter), a transport that admits sends itself (Submit) is handed each frame before Connection.Send returns (finding 22; pipelining, finding 23), and a failed Connect joins the transport's lifetime. The package experimental seam and binding-guard tests use the Pi shape.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 4789fa51f0 (2026-10-08), 5dbfed1576 (2026-10-08), 8655ff6ae5 (2026-10-08), c9f1cffbd5 (2026-10-08)
  • Tests: ai/faux_duration_test.go::TestFauxScriptedDurationMsReachesEveryMessage, ai/faux_scripted_fields_oracle_test.go::TestFauxScriptedMessageMembersMatchPi, coding/extension/host/subprocess/runtime_node_user_bash_operations_test.go::TestNodeRuntimeUserBashOperations, coding/extension/host/subprocess/user_bash_operations_test.go::TestUserBashOperationsExecAbortsWithTheContext, coding/extension/host/subprocess/user_bash_operations_test.go::TestUserBashOperationsReleaseTheExtensionHandleWhenTheHostDropsThem, coding/extension/host/subprocess/user_bash_operations_test.go::TestUserBashOperationsReplyWithResultOrWithoutHandleIsLeftRaw, extensions/sdk-py/tests/test_user_bash_operations.py, extensions/sdk/user_bash_operations_test.go::TestUserBashOperationsStayInTheExtensionUntilTheHostReleasesThem and 3 more in docs/bug-ledger.json
  • Release note: The experimental client takes Pi's ByteTransportFactory shape: ClientOptions.TransportFactory returns a ByteTransport whose Send(ctx, chunk) returns when the write settles. The unix transport uses it, and a transport that admits sends itself (Submit) is handed each frame before Connection.Send returns.

BF-c9fbfcc

Inline images render in Herdr again: TERM_PROGRAM=herdr detects the Kitty graphics protocol, as in Pi 1.2.0

  • Source: PiG's own defect
  • Symptom: Inline images render in Herdr again: TERM_PROGRAM=herdr detects the Kitty graphics protocol, as in Pi 1.2.0.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: c9fbfccced (2026-10-12, #10774)
  • Tests: tui/terminal_image_upstream_test.go::TestUpstreamTerminalImageCapabilities
  • Release note: Inline images render in Herdr again: TERM_PROGRAM=herdr detects the Kitty graphics protocol, as in Pi 1.2.0. Hyperlinks stay on, and truecolor still follows COLORTERM/TERM.

BF-ca06a0b

Tool arguments that TypeBox's Convert wraps in an array (an object where the schema asks for an array, such…

  • Source: PiG's own defect
  • Symptom: Tool arguments that TypeBox's Convert wraps in an array (an object where the schema asks for an array, such as edits) keep the member order of the arguments' text, in the returned arguments and in the order of the validation errors, as in Pi.
  • Root cause: TypeBox's Convert (FromArray) wraps a value that is not an array in one when the schema asks for an array: {"edits":{...}} becomes {"edits":[{...}]}. Pi's wrapped object keeps the member order of the arguments' text.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ca06a0b4fd (2026-10-11)
  • Tests: ai/validate_wrapped_order_oracle_test.go::TestValidateToolArgumentsKeepsTheSourceOrderOfWrappedObjectsLikePi
  • Release note: Tool arguments that TypeBox's Convert wraps in an array (an object where the schema asks for an array, such as edits) keep the member order of the arguments' text, in the returned arguments and in the order of the validation errors, as in Pi. They came out byte-sorted.

BF-ca27ba6

The Go extension SDK accepts a native Provider object with no auth methods, as Pi's ProviderAuth (both…

  • Source: PiG's own defect
  • Symptom: It used to panic at load with "Provider requires auth", while the Node, Python and Rust SDKs accepted it.
  • Root cause: Pi's ProviderAuth (packages/ai/src/auth/types.ts:252-255) makes apiKey and oauth both optional. The Go SDK rejected such a Provider at load with 'Provider requires auth' while the Node, Python and Rust SDKs accepted it. Found by the new scenario rows; providers-auth/provider-without-auth-methods-registers was red for Go (rig now observes a failed load) and is green after the fix. Also: model registry hasConfiguredAuth/isUsingOAuth/getProvider rows for all SDKs.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ca27ba6e1d (2026-10-09)
  • Tests: ai/anthropic_oauth_test.go::TestAnthropicHistoryToolNames
  • Release note: The Go extension SDK accepts a native Provider object with no auth methods, as Pi's ProviderAuth (both apiKey and oauth optional) allows. It used to panic at load with "Provider requires auth", while the Node, Python and Rust SDKs accepted it.

BF-ca675e0

A list item whose text is [ ] or [x] with nothing after the spaces on the line is plain text, not a…

  • Source: PiG's own defect
  • Symptom: A list item whose text is [ ] or [x] with nothing after the spaces on the line is plain text, not a task item (marked's listIsTask needs a non-whitespace character after the spaces), so - [ ] renders as - [ ] like Pi.
  • Root cause: marked's listIsTask is /^[[ xX]] +\S/. splitListTaskMarker accepted [ ] followed by the end of the text or a newline (- [ ], - [ ] \n c), so the item rendered with a checkbox marker and a continuation indent of six; Pi keeps [ ] as the item's text under a plain bullet.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ca675e0af0 (2026-10-11)
  • Tests: tui/markdown_task_item_oracle_test.go::TestMarkdownTaskItemFirstLineIsTextMatchesPi, tui/markdown_task_item_oracle_test.go::TestMarkdownTaskItemNeedsATextAfterTheSpacesMatchesPi
  • Release note: A list item whose text is [ ] or [x] with nothing after the spaces on the line is plain text, not a task item (marked's listIsTask needs a non-whitespace character after the spaces), so - [ ] renders as - [ ] like Pi.

BF-cb00273

OpenRouter's signature-only reasoning.text delta for Claude is kept and replayed with its reasoning block…

  • Source: A Pi defect that PiG fixed first (divergence D122)
  • Symptom: OpenRouter's signature-only reasoning.text delta for Claude is kept and replayed with its reasoning block (D122, pi-mono #9534).
  • Root cause: Pi openai-completions.ts:144-148 isOpenAIReasoningDetail requires text, so OpenRouter's signature delta is dropped and the replayed reasoning_details lose the signature.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/9534
  • Removed when Pi fixes it: Pi keeps signature-only reasoning.text deltas.
  • Fix commits: cb002735d6 (2026-10-09)
  • Tests: ai/openai_reasoning_signature_only_test.go::TestOpenAICompletionsKeepsSignatureOnlyReasoningText
  • Release note: OpenRouter's signature-only reasoning.text delta for Claude is kept and replayed with its reasoning block (D122, pi-mono #9534).

BF-cb21ee0

Markdown resolves link reference definitions as Pi does: [text][label], [label][] and [label] link to a…

  • Source: PiG's own defect
  • Symptom: Markdown resolves link reference definitions as Pi does: [text][label], [label][] and [label] link to a [label]: url defined anywhere in the message, and the definition line is no longer printed.
  • Root cause: Markdown now ports marked 18.0.11's link reference definitions. The def block tokenizer runs after HTML blocks. Definitions go into a links map for the whole document, where the first definition of a tag wins. Reference, collapsed and shortcut links resolve against that map, and references to defined tags are masked for emphasis, including the link-in-text check. marked registers every definition before it lexes any inline text. So a document whose definition follows a "[" is rendered a second time with the full map. A definition renders no lines, as in Pi: blanks around it are kept, and text that renders nothing returns [""].
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: c5796c60b1 (2026-10-05), b2a820387a (2026-10-06), cb21ee0405 (2026-10-06), d90c2f41b7 (2026-10-08)
  • Tests: test/parity/scenarios/interactive-rendering/44-assistant-underscore-emphasis.toml, tui/highlight_strip_test.go::TestStripSyntaxHighlightBuildOmitsHljs, tui/markdown_autolink_test.go::TestAutoLinkTokensMatchMarked, tui/markdown_autolink_test.go::TestAutoLinkURLRuleAtTokenStarts, tui/markdown_blockquote_style_test.go::TestMarkdownBlockquoteDefaultStyleMatchesUpstreamBytes, tui/markdown_blockquote_style_test.go::TestMarkdownEmphasisAndSetextMatchUpstreamBytes, tui/markdown_blockquote_style_test.go::TestMarkdownParagraphSpacingBeforeInterruptingBlockMatchesUpstream, tui/markdown_marked_corpus_test.go::TestMarkdownMarkedCorpusMatchesPi and 6 more in docs/bug-ledger.json
  • Release note: Markdown resolves link reference definitions as Pi does: [text][label], [label][] and [label] link to a [label]: url defined anywhere in the message, and the definition line is no longer printed. A list item whose text starts more than four spaces after its marker, as after a tab, renders as an indented code block.

BF-cc9370b

A Node extension's setWidget, setHeader and setFooter factories receive the same tui as a…

  • Source: PiG's own defect
  • Symptom: A Node extension's setWidget, setHeader and setFooter factories receive the same tui as a ctx.ui.custom factory, as in Pi: showOverlay, hideOverlay, setFocus and terminal work there, and tui.requestRender() also redraws the overlays the extension shows.
  • Root cause: TestCustomOverlayFocusChangesReachSDKs (bridge in place, SDK changes and pack reverted): every cell (subprocess-go, -node, -node-packed, -rust, -python, fused-go) "the component did not render focused=false after it lost focus".
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1848633092 (2026-10-13), 2a055a7a3a (2026-10-13), a9058bf992 (2026-10-13), ab0ee713e2 (2026-10-13), cc9370b309 (2026-10-13), d87b70c54c (2026-10-13), ec2080a75f (2026-10-13)
  • Tests: coding/extension/host/subprocess/host_calls_test.go::TestReadLoopWidgetPushOrdersAgainstQueuedSlotCalls, coding/extension/host/subprocess/node_library_bundle_test.go::TestNodeLibraryBundlesKeepSharedIdentitiesWithoutRawCatalogLoads, coding/extension/host/subprocess/runtime_node_overlay_handle_test.go::TestNodeFactoryTuiShowOverlayHandleIsSynchronous, coding/extension/host/subprocess/runtime_node_overlay_handle_test.go::TestNodeSurfaceFactoryTuiShowsOverlays, coding/extension/host/subprocess/runtime_node_prompt_end_test.go::TestNodePromptEndWaitEndsWhenAnotherProcessTurnsTheLoop, coding/extension/host/subprocess/ui_bridge_raw_overlay_test.go::TestRawOverlayEndsWithItsConnection, coding/extension/host/subprocess/ui_bridge_raw_overlay_test.go::TestRawOverlayShowsAtOnceAndEndsWithItsHide, coding/extension/host/subprocess/ui_bridge_raw_overlay_test.go::TestRawOverlayWithoutTUIIsNotShown and 22 more in docs/bug-ledger.json
  • Release note: A Node extension's setWidget, setHeader and setFooter factories receive the same tui as a ctx.ui.custom factory, as in Pi: showOverlay, hideOverlay, setFocus and terminal work there, and tui.requestRender() also redraws the overlays the extension shows.

BF-ccaade2

The /thinking selector edits its search like Pi's input, selects with the input's submit key, and no longer…

  • Source: PiG's own defect
  • Symptom: The /thinking selector edits its search like Pi's input, selects with the input's submit key, and no longer repeats a selection or cancel on the next navigation key.
  • Root cause: (1) tui.input.submit (ctrl+s here) must select through the search Input's onSubmit -> selectList.handleInput("\r"); Pig's host-owned TextInput only marked itself done. (2) Pig re-fired onSelect/onCancel on every later navigation key because it read the list's sticky Done state; Pi fires once per confirm/cancel key (new FilterableList.Reset, called by the selector after it dispatches). Scenario settings/06 (/thinking, m Down Left d -> '> dm') is red on the old selector ('> md') and byte-equal now.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d1f258f335 (2026-10-06), 0ae3fcf17c (2026-10-07), ccaade27af (2026-10-07)
  • Tests: internal/codingagent/extension_dialog_remapping_test.go::TestExtensionDialogRemappingThroughOwnerLoop, internal/codingagent/thinking_selector_oracle_test.go::TestThinkingSelectorInputMatchesPi, test/parity/scenarios/settings/06-thinking-selector-search-editing.toml, test/parity/scenarios/settings/08-settings-search-editing.toml, tui/cancellable_loader_input_test.go::TestCancellableLoaderCancelFollowsTheBindingAndCallsOnAbortEachPress, tui/cancellable_loader_input_test.go::TestCancellableLoaderCancelWithoutOnAbortOnlyAborts, tui/extension_dialog_bindings_test.go::TestExtensionDialogsMatchPiBindingsAndPalette, tui/oauth_selector_input_test.go::TestOAuthSelectorInputMatchesPi and 7 more in docs/bug-ledger.json
  • Release note: The /thinking selector edits its search like Pi's input, selects with the input's submit key, and no longer repeats a selection or cancel on the next navigation key.

BF-cd7fa1c

A read-only config root no longer prints warnings at every start

  • Source: PiG's own defect
  • Symptom: A read-only config root no longer prints warnings at every start.
  • Root cause: A sync keeps the four most recently used bundles.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 395c46cbf2 (2026-10-09), 69b9811164 (2026-10-09), 852652e3ae (2026-10-09), da99854d1b (2026-10-09), cd7fa1ca25 (2026-10-10)
  • Tests: coding/cli/piglet_verify_cache_startup_test.go::TestSignedPigletBinaryRunsWhenItsVerifyCacheCannotBeWritten, coding/cli/readonly_config_root_binary_test.go::TestReadOnlyConfigRootStartsQuietlyAndNamesOnlyDocsThatExist, coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, coding/extension/pigsdk/pigsdk_version_share_test.go::TestWarmStartRecordsTheUseOfThisBuildsSDKDirectories, internal/codingagent/auth_guidance_test.go::TestNoTextNamesADocsBundleStartupCouldNotWrite, internal/configroot/configroot_test.go::TestDocsDirIsThePigdocsBundleUnderTheConfigRoot, internal/contentdirs/contentdirs_test.go::TestEvictKeepsTheMostRecentlyUsedAndTheCallersOwn, internal/contentdirs/contentdirs_test.go::TestNameRequiresAHexHash and 7 more in docs/bug-ledger.json
  • Release note: A read-only config root no longer prints warnings at every start. The extension SDK staging warning stays silent there, because each build reads only its own staged SDK directories and nothing another build staged can stand in for them, and a signed Piglet Binary no longer warns that it cannot write its verification cache; its next start checks it in full again. Other write errors still warn. The agent directory must stay writable, as in Pi.

BF-cda6e3a

PiG opens a session file that holds a valid JSON line that is not an entry (an array, true, a string) or an…

  • Source: PiG's own defect
  • Symptom: PiG opens a session file that holds a valid JSON line that is not an entry (an array, true, a string) or an entry whose id or parentId is not a string, as Pi does, instead of failing with json: cannot unmarshal … SessionEntryBase.
  • Root cause:
    • sessionentry keeps an id or parentId that is not a string as the key of its JavaScript value. The key starts with a 0xFF byte, which no decoded string holds. An absent id or parentId is undefined, and a null id is null. Each parsed object or array gets a key of its own, because Map compares objects by identity. - Truthy is JavaScript truthiness. - ValueJSON writes the value back. A child's parentId is that value, and is omitted when undefined. - WireID is the leaf as a host-call string. - A LazyEntry keeps the keys it was indexed with. - The load no longer re-decodes the base of an undescribed record. Its leaf is the record's id, or null when that id is null. - getBranch, the context…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), cda6e3a715 (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_file_entry_union_test.go::TestSessionKeepsRecordsThatAreNotEntriesAsPiDoes, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain, internal/sessionentry/lazy_test.go::TestLazyEntryKeepsTheKeysOfObjectValuedIDs and 3 more in docs/bug-ledger.json
  • Release note: PiG opens a session file that holds a valid JSON line that is not an entry (an array, true, a string) or an entry whose id or parentId is not a string, as Pi does, instead of failing with json: cannot unmarshal … SessionEntryBase. The leaf, the branch, the context and the parentId of the next entry follow Pi: after a line with no id the next entry has no parentId member, after an entry whose id is 5 it has "parentId":5, and a falsy parentId ends a branch.

BF-cdf0be7

Markdown rendering matches Pi on blank lines at the start and end of the text, empty headings and closing #…

  • Source: PiG's own defect
  • Symptom: Markdown rendering matches Pi on blank lines at the start and end of the text, empty headings and closing # runs, and unterminated code fences: a text that starts with a blank line begins with one empty row, a trailing line of spaces after a list, table or quote adds one, a closing run of # is dropped from a heading, an empty heading keeps its row, and an unterminated fence does not show the empty line its final newline splits off.
  • Root cause: TestParseMatchesTheLibraryOnMutatedCorpus and TestLexerMatchesTheLibraryOnMutatedCorpus compare eemeli/yaml 2.9.0 with Parse/lex on 6000 mutated corpus texts (40 differed before, 15-17 per 20000 after the first fixes). Fixed: an unterminated quote (buffer.substring(0, -1) is empty: the error is at the end of input, not after the first line); a line starting with a non-ASCII character reduced the continuation indent because the lexer looked at the second byte, not the second UTF-16 unit (charAt(1)); Invalid escape sequence raw text and the 1024-unit implicit key limit counted bytes; anchors reject JavaScript whitespace (RE2 \s is narrower); -0 is 0 as an omap/set key; Buffer.from(.., base64)…
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: b5b7b43f82 (2026-10-10), cdf0be76e4 (2026-10-10)
  • Tests: coding/rpcclient/jsonl_reader_oracle_test.go::TestJSONLReaderMatchesPi, internal/codingagent/session_load_oracle_test.go::TestLoadEntriesFromFileMatchesPiOnCorruptedFiles, internal/codingagent/tools/edit_tool_oracle_test.go::TestEditToolMatchesPi, internal/yaml12/parse_mutation_test.go::TestLexerMatchesTheLibraryOnMutatedCorpus, internal/yaml12/parse_mutation_test.go::TestParseMatchesTheLibraryOnMutatedCorpus, internal/yaml12/parse_mutation_test.go::TestParseMatchesTheLibraryOnUTF16Offsets, internal/yaml12/parse_test.go::TestParseMatchesTheLibrary, tui/markdown_render_oracle_test.go::TestMarkdownRenderMatchesPi
  • Release note: Markdown rendering matches Pi on blank lines at the start and end of the text, empty headings and closing # runs, and unterminated code fences: a text that starts with a blank line begins with one empty row, a trailing line of spaces after a list, table or quote adds one, a closing run of # is dropped from a heading, an empty heading keeps its row, and an unterminated fence does not show the empty line its final newline splits off.

BF-cdf2887

fix(ai): keep a reused-id tool result in its own turn during the D48 repair

  • Source: PiG's own defect
  • Symptom: fix(ai): keep a reused-id tool result in its own turn during the D48 repair
  • Root cause: RepairToolFlow moved the result of the newest turn back to an earlier unanswered call that shares its id (providers that number calls per response), so the model saw its current call answered 'No result provided' and its real result attached to an older turn. Pi's request for that history is intact. A result now does not move back past a turn that reopens its id, and the drop pass counts open calls per turn.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: cdf2887af3 (2026-10-10)
  • Tests: ai/tool_flow_repair_property_test.go::TestRepairToolFlowChangesOnlyBrokenRequests, ai/tool_flow_repair_test.go::TestRepairToolFlow

BF-ce0f352

createPowerShellTool now uses the operations option

  • Source: A fix Pi shipped that PiG ported
  • Symptom: createPowerShellTool now uses the operations option.
  • Root cause: Pi: powershell.ts:53-56 passes options.operations (CreatePowerShellTool dropped it); session-manager.ts:1025-1054 setSessionFile; theme.ts:1135 getSelectListTheme; dynamic-border.ts:14 color callback; slash-commands.ts:4 SlashCommandSource "skill".
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: 63b4fc80e7 (2026-10-05), ebc1768e5f (2026-10-06), ce0f352637 (2026-10-07)
  • Tests: durable/env/node/conformance_cases_test.go::TestCreateEnvConformanceDetectsABrokenEnvironment, chord/output/output_test.go::TestOutputBuffer, chord/output/output_test.go::TestToolOutputBounds, durable/harness/harness_registry_test.go::TestSettingsKeepTheDefaultOfAProgressIntervalGivenAsUndefined, durable/tools/powershell_upstream_test.go::TestPowerShellFallsBackToWindowsPowerShellAndReportsTheLastStartFailure, durable/tools/powershell_upstream_test.go::TestPowerShellRunsRealPowerShellWithUTF8Output, durable/tools/powershell_upstream_test.go::TestPowerShellRunsTheCommandWithPwshAsOneArgumentForcingUTF8Output, durable/tools/powershell_upstream_test.go::TestReadReadsALogThatGrowsWhileItIsRead and 5 more in docs/bug-ledger.json
  • Release note: createPowerShellTool now uses the operations option. The PowerShell tool dropped it and always ran commands through the local PowerShell.

BF-ce0f352-2

The editor's autocomplete list takes its colors from one getSelectListTheme equivalent, the same function…

  • Source: PiG's own defect
  • Symptom: The editor's autocomplete list takes its colors from one getSelectListTheme equivalent, the same function the other select lists use.
  • Root cause: Pi: powershell.ts:53-56 passes options.operations (CreatePowerShellTool dropped it); session-manager.ts:1025-1054 setSessionFile; theme.ts:1135 getSelectListTheme; dynamic-border.ts:14 color callback; slash-commands.ts:4 SlashCommandSource "skill".
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 38b0f0c036 (2026-10-05), ce0f352637 (2026-10-07)
  • Tests: durable/env/node/conformance_cases_test.go::TestCreateEnvConformanceDetectsABrokenEnvironment, internal/codingagent/session_set_file_test.go::TestSetSessionFileOnAnInMemoryManagerDoesNotPersist, internal/codingagent/session_set_file_test.go::TestSetSessionFileSwitchesThePersistedManager, internal/codingagent/slash_command_source_test.go::TestSlashCommandCatalogListsPromptAndSkillSources, internal/codingagent/tools/bash_tool_options_test.go::TestCreatePowerShellToolRunsCustomOperationsAfterTheSpawnHook, tui/highlight_test.go::TestCliHighlightThemeColorsSubstitutionsWithTheTextColor, tui/highlight_test.go::TestHighlightCode_ColorsEachLineOfPythonDocstringsIndependently, tui/select_list_theme_test.go::TestGetSelectListThemeColorsFromTheActiveTheme and 2 more in docs/bug-ledger.json
  • Release note: The editor's autocomplete list takes its colors from one getSelectListTheme equivalent, the same function the other select lists use.

BF-ce366dd

fix(coding): resume restores the transcript's tools only for the unchanged default selection…

  • Source: PiG's own defect
  • Symptom: fix(coding): resume restores the transcript's tools only for the unchanged default selection; --no-builtin-tools wins [SIGNOFF-PENDING]
  • Root cause: The guard checked AllowedTools/NoTools, but the CLI's plain --no-builtin-tools sets SkipBuiltinTools with NoTools "", so a resume under -nbt reactivated the transcript's extension tools (opt_tool) where the explicit empty selection should win. The guard is now usesDefaultTools(opts) (sdk.ts:472) without +name/-name entries. The BuildContext projection before restore is dropped: restoreToolsFromTranscript is a no-op without a system message, so a resume no longer projects the branch twice.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: ce366ddf14 (2026-10-10)
  • Tests: coding/cli/rpc_resume_tools_test.go::TestRPCResumeKeepsDynamicallyActivatedTools, coding/session_resume_tools_test.go::TestResumeRestoresTheTranscriptsActiveTools

BF-ce42877

durable/images.CreatePhotonImages, and durable/images/node.CreateNodePhotonImages with Worker: false…

  • Source: PiG's own defect
  • Symptom: Concurrent calls prepared side by side.
  • Root cause: Pi's createPhotonImages runs prepare's synchronous work on the calling thread, so calls are prepared one after another in call order. The turn-taking queue moves from node's Worker false path into durable/images (sequential.go) around the native preparer, and node's Worker false returns CreatePhotonImages again, so the order lives in one place. A caller cancelled while it waits returns its context error and the calls after it keep their order. The node tests pin the worker's own order; ledger rows and the changelog fragment name both.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 8435c98530 (2026-10-13), ce42877426 (2026-10-13)
  • Tests: durable/images/node/worker_test.go::TestTheWorkerPreparesImagesInCallOrder, durable/images/node/worker_test.go::TestTheWorkerPreparesOneImageAtATime, durable/images/node/worker_test.go::TestWorkerFalsePreparesWithThePlainProcessor, durable/images/sequential_test.go::TestCreatePhotonImagesPreparesImagesInCallOrder, durable/images/sequential_test.go::TestCreatePhotonImagesPreparesOneImageAtATime, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation and 4 more in docs/bug-ledger.json
  • Release note: durable/images.CreatePhotonImages, and durable/images/node.CreateNodePhotonImages with Worker: false, which returns it, prepare images one at a time in call order on the caller's goroutine, as Pi's createPhotonImages does on its calling thread. Before, concurrent calls prepared side by side. A caller whose context is cancelled while it waits returns the context's error and does not hold up or reorder the calls after it.

BF-ce8587e

On macOS, reading an image or file paths from the clipboard and writing text to it no longer risk passing…

  • Source: PiG's own defect
  • Symptom: On macOS, reading an image or file paths from the clipboard and writing text to it no longer risk passing AppKit an address in freed memory.
  • Root cause: ### Problem
  • User impact: Behavior differed from Pi
  • Platform: macOS
  • Fixed in: 0.5.0
  • Fix commits: ce8587ef40 (2026-10-07, #179)
  • Tests: internal/nativeplatform/clipboard_darwin_filepaths_test.go::TestDarwinPasteboardFilePaths, internal/nativeplatform/clipboard_darwin_stack_test.go::TestDarwinPasteboardBuffersSurviveAStackMove, internal/nativeplatform/clipboard_darwin_stack_test.go::TestDarwinPasteboardTextRoundTrip
  • Release note: On macOS, reading an image or file paths from the clipboard and writing text to it no longer risk passing AppKit an address in freed memory. When Go moved the goroutine stack during the call, AppKit could read a stale array or text buffer. These buffers now stay in place for the call.

BF-ced3a9c

RPC model objects of built-in models carry "type":"chat" (get_available_models, get_state, set_model…

  • Source: PiG's own defect
  • Symptom: RPC model objects of built-in models carry "type":"chat" (get_available_models, get_state, set_model, cycle_model), as Pi's do, and so do Radius models; a models.json model has no type, even when its definition writes one.
  • Root cause: Row 20: set Type chat in the shared GeneratedModel.ToModel, which also converts models.json entries (nativeModelFromEntry), so a typeless custom model reported "type":"chat" where Pi's has none (provider-composer.ts createCustomModel spreads the definition), and the startup model (coding/model.go modelFromEntry) still dropped it, so get_state had no type. ModelEntry now carries the registry type: chat for a catalog entry, the definition's value for a models.json or native entry. The deleted TestRPCModelProjectionCarriesModelType is restored, and the unit test no longer pins "type" first: Pi's gpt-4o object has it last.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ced3a9c4d4 (2026-10-10)
  • Tests: coding/cli/rpc_model_type_binary_test.go::TestRPCModelObjectsCarryTheRegistryModelType, coding/cli/rpc_model_type_test.go::TestRPCBuiltinChatModelCarriesType, coding/cli/rpc_model_type_test.go::TestRPCModelProjectionCarriesModelType, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode, mcp/oauth/parse_url_node_test.go::TestParseURLMatchesNodeNewURL
  • Release note: RPC model objects of built-in models carry "type":"chat" (get_available_models, get_state, set_model, cycle_model), as Pi's do, and so do Radius models; a models.json model has no type, even when its definition writes one.

BF-ced3a9c-2

A URL of a non-special scheme keeps an explicit port 0 (ssh://git@host:0/repo, an MCP OAuth redirect such…

  • Source: PiG's own defect
  • Symptom: A URL of a non-special scheme keeps an explicit port 0 (ssh://git@host:0/repo, an MCP OAuth redirect such as myapp://host:0/cb), as Node's new URL does.
  • Root cause: Row 20: set Type chat in the shared GeneratedModel.ToModel, which also converts models.json entries (nativeModelFromEntry), so a typeless custom model reported "type":"chat" where Pi's has none (provider-composer.ts createCustomModel spreads the definition), and the startup model (coding/model.go modelFromEntry) still dropped it, so get_state had no type. ModelEntry now carries the registry type: chat for a catalog entry, the definition's value for a models.json or native entry. The deleted TestRPCModelProjectionCarriesModelType is restored, and the unit test no longer pins "type" first: Pi's gpt-4o object has it last.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): ced3a9c4d4 (2026-10-10)
  • Tests: coding/cli/rpc_model_type_binary_test.go::TestRPCModelObjectsCarryTheRegistryModelType, coding/cli/rpc_model_type_test.go::TestRPCBuiltinChatModelCarriesType, coding/cli/rpc_model_type_test.go::TestRPCModelProjectionCarriesModelType, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode, mcp/oauth/parse_url_node_test.go::TestParseURLMatchesNodeNewURL
  • Release note: A URL of a non-special scheme keeps an explicit port 0 (ssh://git@host:0/repo, an MCP OAuth redirect such as myapp://host:0/cb), as Node's new URL does.

BF-cf4fff8

A project whose only configuration is a project mcp.json (.pig/mcp.json, or .pi/mcp.json with…

  • Source: PiG's own defect
  • Symptom: A project whose only configuration is a project mcp.json (.pig/mcp.json, or .pi/mcp.json with PIG_USE_PI_DIRS=1, D2) now requires project trust, as Pi's TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES does.
  • Root cause: A real-binary test runs print, print --no-session, JSON and RPC modes in a project whose only configuration is a project mcp.json with a stdio server. The server process must not start and its tool must not reach the model; a saved trust decision starts it. Without "mcp.json" in the trust list every untrusted mode starts the server.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): cf4fff8f78 (2026-10-10)
  • Tests: coding/cli/mcp_builtin_binary_test.go::TestBuiltinMCPExtensionStartsProjectServersOnlyInATrustedProject, test/parity/scenarios/project-trust/22-mcp-json-only-project-requires-trust.toml
  • Release note: A project whose only configuration is a project mcp.json (.pig/mcp.json, or .pi/mcp.json with PIG_USE_PI_DIRS=1, D2) now requires project trust, as Pi's TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES does. PiG left mcp.json out of that list, so such a project counted as having nothing to trust and the commands of its stdio MCP servers started without a prompt in every mode. Interactive mode now asks for trust. Print, JSON and RPC modes, without a saved decision or defaultProjectTrust: "always", now ignore the file, as they do for other project configuration.

BF-cf648c7

A restarted extension, isolated or in a recovered packed cell, now offers the commands, flags, shortcuts and…

  • Source: PiG's own defect
  • Symptom: A restarted extension, isolated or in a recovered packed cell, now offers the commands, flags, shortcuts and tools its new process registered; before, it kept the first registration's.
  • Root cause: The register message carried a command argument hint (args, sent only by the Node runtime) and a per-handler can_block flag (sent by Node, Python and Rust). The host read neither, and Pi has neither concept: RegisteredCommand has no argument hint (types.ts:1541-1547, loader.ts:303) and every handler may return a result (runner.ts). The Go and Node SDK APIs never exposed can_block; Rust on_event/on_event_result and Python on_event did, so the SDK interfaces differed for no behaviour.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: cf648c7b8b (2026-10-09)
  • Tests: coding/extension/host/subprocess/host_test.go::TestHost_BuildExtension_Tools, coding/extension/host/subprocess/host_test.go::TestProtocol_RegisterRoundTrip, coding/extension/host/subprocess/registration_fields_wiring_test.go::TestRegistrationDeclarationsKeepEveryField
  • Release note: A restarted extension, isolated or in a recovered packed cell, now offers the commands, flags, shortcuts and tools its new process registered; before, it kept the first registration's. Shortcuts keep their registration order.

BF-cf7f368

A local file listed as a Package with autoload: false still loads as one enabled extension, and when a…

  • Source: PiG's own defect
  • Symptom: A local file listed as a Package with autoload: false still loads as one enabled extension, and when a project lists a Package with autoload: false, every user-settings entry for that Package is kept, as in Pi's resolveLocalExtensionSource and dedupePackages.
  • Root cause: TestLoadMcpConfigMatchesPiOnRandomFiles loads 1500 random global and project mcp.json files through Pi's loadMcpConfig and PiG's LoadMcpConfig: 187 differed, among them every file with a numeric server name, because Object.entries lists array-index keys first and orderedjson.Object kept file order. Object.Set now inserts an array-index key at its ascending place before the other keys (Keys, MarshalJSON, Clone follow), with TestObjectEnumeratesArrayIndexKeysFirst. Dependent packages (codingagent, extension, mcpext, mcp, packagecontent) pass; the failures left in tools and the node-backed subprocess tests are the fd/rg and node-shim environment ones.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1429665458 (2026-10-09), 24232ef878 (2026-10-09), 2b3403045b (2026-10-09), 5ceebfdae9 (2026-10-09), 667604151e (2026-10-09), 3def2a7bd8 (2026-10-10), 7e1a05ccfd (2026-10-10), a46f930200 (2026-10-10), cf7f36805a (2026-10-10), d8bed22101 (2026-10-10)
  • Tests: ai/anthropic_messages_response_oracle_test.go::TestAnthropicMessagesResponseMatchesPi, ai/auth_load_oracle_test.go::TestAuthStorageOnCorruptedFileMatchesPi, ai/auth_opaque_entries_test.go::TestAuthStorageKeepsEntriesItCannotTypeAcrossWrites, ai/google_generative_ai_payload_oracle_test.go::TestGoogleGenerativeAIPayloadMatchesPi, ai/google_tool_call_id_test.go::TestGoogleConvertMessages_EmitsToolCallIdWhenRequired, ai/google_tool_call_id_test.go::TestGoogleConvertMessages_TruncatesToolCallIdTo64, ai/openai_completions_payload_oracle_test.go::TestOpenAICompletionsPayloadMatchesPi, ai/openai_completions_response_oracle_test.go::TestOpenAICompletionsResponseMatchesPi and 15 more in docs/bug-ledger.json
  • Release note: A local file listed as a Package with autoload: false still loads as one enabled extension, and when a project lists a Package with autoload: false, every user-settings entry for that Package is kept, as in Pi's resolveLocalExtensionSource and dedupePackages.

BF-d0c2727

fix(codingagent): decode a Node request's client identity from its fields; regenerate the normalization…

  • Source: PiG's own defect
  • Symptom: fix(codingagent): decode a Node request's client identity from its fields; regenerate the normalization inventory
  • Root cause: It reads userAgent, version and attribution from the decoded fields with the same errors. The test's Pi user-agent pattern no longer uses (?i).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: d0c2727053 (2026-10-21)
  • Tests: None recorded.

BF-d11ca55

pig piglet build --format binary from a Pig checkout with a go.work builds a Piglet whose fused Go…

  • Source: PiG's own defect
  • Symptom: The build stopped with go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay, because workspace mode needs checksums that go mod tidy does not write to the extension's go.sum.
  • Root cause: Before, the build stopped with go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay, because workspace mode needs checksums that go mod tidy does not write to the extension's go.sum.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: da99854d1b (2026-10-09), 8a5162f352 (2026-10-10), d11ca55cdb (2026-10-10)
  • Tests: coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, coding/pigletbuild/testmain_test.go::TestMain, coding/pigletbuild/workspace_sums_test.go::TestAppendMissingLinesKeepsBaseAndAddsOnlyNewLines, coding/pigletbuild/workspace_sums_test.go::TestBuildNativeArtifactCompletesWorkspaceChecksumsOffline, coding/pigletbuild/workspace_sums_test.go::TestCompleteWorkspaceSumsSkipsAModuleCacheSource, coding/pigletbuild/workspace_sums_test.go::TestScratchWorkFileAnchorsRelativePaths
  • Release note: pig piglet build --format binary from a Pig checkout with a go.work builds a Piglet whose fused Go extension imports an external module, such as an a2a-go v2 package. Before, the build stopped with go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay, because workspace mode needs checksums that go mod tidy does not write to the extension's go.sum. The build now adds them to its own overlay and still never writes the checkout.

BF-d133e3d

Codemode scripts that patch built-ins, for example Array.prototype.toJSON = ..., no longer break the tool…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: Codemode scripts that patch built-ins, for example Array.prototype.toJSON = ..., no longer break the tool call: built-ins are frozen before the script runs, and a malformed message from the sandbox fails the run with a sandbox error.
  • Root cause: Regenerate shims/pi-dist from the pinned @earendil-works packages (automation/gen/vendor-pi-dist.sh), runtime.mjs getPromptGuidelines and hiddenTools, runtime-node.zip and its digest (go generate).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 03d9bbfce9 (2026-10-05), d133e3d912 (2026-10-05)
  • Tests: codemode/bridge_test.go::TestAcceptsWellFormedBridgePayloads, codemode/bridge_test.go::TestReportsABrokenBridgeAsASandboxError, codemode/bridge_test.go::TestReportsADuplicateCallIDAsASandboxError, codemode/sandbox_test.go::TestEmbeddedSourcesMatchTheirRecordedHashes, codemode/sandbox_test.go::TestFreezesIntrinsicsThatAreOnlyReachableFromInstances, codemode/sandbox_test.go::TestIgnoresPatchesToBuiltinsAndBuiltinGlobals, codemode/sandbox_test.go::TestReportsErrorsWhoseNameOrMessageIsNotAString, codemode/sandbox_test.go::TestStillLetsInstancesOverridePropertiesOfFrozenPrototypes
  • Release note: Codemode scripts that patch built-ins, for example Array.prototype.toJSON = ..., no longer break the tool call: built-ins are frozen before the script runs, and a malformed message from the sandbox fails the run with a sandbox error.

BF-d133e3d-2

MCP OAuth sign-in no longer fails with invalid_redirect_uri on OpenID Connect servers: client registration…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: MCP OAuth sign-in no longer fails with invalid_redirect_uri on OpenID Connect servers: client registration sends application_type, native for loopback and custom-scheme redirects.
  • Root cause: Regenerate shims/pi-dist from the pinned @earendil-works packages (automation/gen/vendor-pi-dist.sh), runtime.mjs getPromptGuidelines and hiddenTools, runtime-node.zip and its digest (go generate).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: affc944ad8 (2026-10-05), d133e3d912 (2026-10-05)
  • Tests: ai/assistant_retry_test.go::TestIsRetryableAssistantErrorClassification, mcp/oauth/oauth_test.go::TestMCPOAuthRegistersWithAnApplicationTypeDerivedFromTheRedirectURIsUnlessOneIsSet
  • Release note: MCP OAuth sign-in no longer fails with invalid_redirect_uri on OpenID Connect servers: client registration sends application_type, native for loopback and custom-scheme redirects.

BF-d1cbde3

The pi-messages, Cloudflare Workers AI and TypeSafe System One, and OpenAI decisions request URLs are built…

  • Source: PiG's own defect
  • Symptom: The pi-messages, Cloudflare Workers AI and TypeSafe System One, and OpenAI decisions request URLs are built with the WHATWG URL parser, as Pi's new URL does: hosts are normalized, a base URL with a query or fragment resolves against its path, and debug=1 is added to an existing query in order and in form encoding instead of with sorted keys.
  • Root cause: The previous net/url code differed on 3529 of 4500 (red before, measured with a scratch copy of the old functions): JoinPath on a base with a query or fragment, hosts and userinfo left unnormalized, url.Values.Encode sorting and re-escaping the existing query where URLSearchParams keeps order and form encoding, and invalid URLs accepted. pi_messages.go and system_one.go now parse with nodeurl.ParseURL; new nodeurl URL.ResolveRelativePath (path-relative reference, empty path as '/', Node's drive-letter-prefix rule for file: bases), URL.WithSearchParam (URLSearchParams.set on the ordered pair list) and FormURLEncode, which replaces ai's private copy. The pi-messages error body now reports…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 31f4517fbe (2026-10-09), 90882933f6 (2026-10-09), b5356c4269 (2026-10-09), d1cbde3321 (2026-10-09), d80c1cd150 (2026-10-09)
  • Tests: ai/azure_config_oracle_test.go::TestAzureBaseURLNormalizationMatchesPi, ai/estimate_oracle_test.go::TestTokenEstimatesMatchPiOnSeededTranscripts, ai/radius_urls_oracle_test.go::TestRadiusURLsMatchPiOnSeededProbes, ai/service_urls_oracle_test.go::TestProviderServiceURLsMatchPiOnSeededProbes, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode, internal/nodeurl/url_oracle_test.go::TestParseMatchesNodeURL
  • Release note: The pi-messages, Cloudflare Workers AI and TypeSafe System One, and OpenAI decisions request URLs are built with the WHATWG URL parser, as Pi's new URL does: hosts are normalized, a base URL with a query or fragment resolves against its path, and debug=1 is added to an existing query in order and in form encoding instead of with sorted keys.

BF-d1f258f

The /settings search edits like Pi's search input: the cursor moves with the arrow keys, Ctrl+W, Ctrl+K…

  • Source: PiG's own defect
  • Symptom: The /settings search edits like Pi's search input: the cursor moves with the arrow keys, Ctrl+W, Ctrl+K, Ctrl+Y, paste and the other input keys work, and any key the list does not own returns the selection to the first match.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d1f258f335 (2026-10-06)
  • Tests: test/parity/scenarios/settings/08-settings-search-editing.toml, tui/settings_list_input_test.go::TestSettingsListAnyDelegatedKeyResetsTheSelectionToTheFirstMatch, tui/settings_list_input_test.go::TestSettingsListBoundActionsRunInUpstreamOrder, tui/settings_list_input_test.go::TestSettingsListEmptyAndSingletonNavigationBoundaries, tui/settings_list_input_test.go::TestSettingsListSearchDelegatesEditingKeysToTheInput, tui/settings_list_input_test.go::TestSettingsListSubmenuReceivesEveryKeyIncludingCancel, tui/settings_list_input_test.go::TestSettingsListWithoutSearchIgnoresOtherKeysAndKeepsSelection, tui/settings_list_keys_test.go::TestSettingsListPageAndHomeEndKeysFollowSearchState
  • Release note: The /settings search edits like Pi's search input: the cursor moves with the arrow keys, Ctrl+W, Ctrl+K, Ctrl+Y, paste and the other input keys work, and any key the list does not own returns the selection to the first match. Confirm and cancel keys that a user binds to the same key as up or down now run up or down first, as Pi does.

BF-d212cdf

pig --piglet piglets/standard/pig-standard.yaml no longer warns on every start that its piglogin…

  • Source: PiG's own defect
  • Symptom: pig --piglet piglets/standard/pig-standard.yaml no longer warns on every start that its piglogin extension registers /sprite, so the built-in pig-login extension was not loaded.
  • Root cause: pig --piglet piglets/standard/pig-standard.yaml no longer warns on every start that its piglogin extension registers /sprite, so the built-in pig-login extension was not loaded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): d212cdf056 (2026-10-13)
  • Tests: coding/cli/standard_piglet_startup_test.go::TestStandardPigletStartsWithoutReplacedBuiltinWarning
  • Release note: pig --piglet piglets/standard/pig-standard.yaml no longer warns on every start that its piglogin extension registers /sprite, so the built-in pig-login extension was not loaded. PiG Standard now strips the built-in pig-login (strip.extensions: [pig-login]), and its own piglogin extension owns /sprite.

BF-d249c70

SDK: Session.Dispose now releases the Session as Pi's dispose does

  • Source: PiG's own defect
  • Symptom: SDK: Session.Dispose now releases the Session as Pi's dispose does.
  • Root cause: Pi's AgentSession.dispose (agent-session.ts:1389-1416) is for "when completely done with the session". It drops every listener and disconnects from the agent, so nothing of the Session stays live. PiG's Dispose cleared the listeners but left two things running until Close: - the event forwarder goroutine: the Events stream stayed open, and with no reader, producers blocked once the buffers filled; - the model registry observer. The services outlive a Session across replacement, so the registry observer kept a disposed Session reachable. An embedder that ends a Session with Dispose alone, as with Pi, leaked both.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d249c707fe (2026-10-10)
  • Tests: coding/session_dispose_leak_test.go::TestSessionDisposeReleasesEventProducers, coding/session_dispose_leak_test.go::TestSessionDisposeReleasesTheSession, coding/session_rpc33_observation_test.go::TestSessionObservationCloseJoinsClaimedCallback, coding/session_rpc33_observation_test.go::TestSessionObservationReservationCanceledBeforeAdmission
  • Release note: SDK: Session.Dispose now releases the Session as Pi's dispose does. Its Events stream closes, publishing and flushing events stop waiting, and the model registry no longer keeps a disposed Session reachable.

BF-d26d494

fix(tui): Editor.SetText resets the scroll offset

  • Source: PiG's own defect
  • Symptom: fix(tui): Editor.SetText resets the scroll offset
  • Root cause: Pi's setText ends in setTextInternal, which sets scrollOffset = 0; Go kept the old offset.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: d26d494f78 (2026-10-10)
  • Tests: tui/editor_oracle_test.go::TestEditorMatchesPiOnFreshSequences, tui/editor_oracle_test.go::TestEditorMatchesPiOracle

BF-d276c00

pig piglet schema lists the apis strip list

  • Source: PiG's own defect
  • Symptom: pig piglet schema lists the apis strip list.
  • Root cause: pig piglet show lists the slot, the schema gains slots and removeSpec.slots, and a Binary's embedded Piglet carries no slots because the member is compiled in. The Binary record keeps the member's tree digest as a content-addressed frontend input.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f5190b9c48 (2026-10-04), 591f095784 (2026-10-05), 613c997d21 (2026-10-05), d1c9dfddbb (2026-10-05), 171aa56d63 (2026-10-06), c20aeb351d (2026-10-06), ca7160566a (2026-10-06), 1f98a7c315 (2026-10-07), c0addf92cc (2026-10-07), 44f92ab2d8 (2026-10-08), d276c00c53 (2026-10-08)
  • Tests: ai/bedrock_option_fields_upstream_test.go::TestBedrockBearerTokenOptionPrecedence, ai/bedrock_option_fields_upstream_test.go::TestBedrockThinkingDisplayOption, ai/bedrock_provider_options_wire_test.go::TestBedrockAppliesPreparedHeadersAndRequestEnvironment, ai/bedrock_provider_options_wire_test.go::TestBedrockReservedHeaderMiddlewarePreservesSDKValues, ai/bedrock_provider_options_wire_test.go::TestBedrockReservedHeadersCannotBeOverriddenOrDeleted, ai/bedrock_provider_response_test.go::TestBedrockResponseHeadersUpstream, ai/bedrock_stream_events_upstream_test.go::TestBedrockProviderStreamEventsUpstream, ai/bedrock_transport_error_test.go::TestBedrockMidStreamTransportFailureMatchesTerminated and 85 more in docs/bug-ledger.json
  • Release note: pig piglet schema lists the apis strip list.

BF-d2b1fc1

GitHub Copilot login reads an enterprise domain the way Pi does: an IPv6 host keeps its brackets, an…

  • Source: PiG's own defect
  • Symptom: GitHub Copilot login reads an enterprise domain the way Pi does: an IPv6 host keeps its brackets, an internationalized name becomes its xn-- form, a numeric host is normalized (1.2.3 is 1.2.0.3) or refused when out of range, a backslash ends the host, and a port above 65535 refuses the domain.
  • Root cause:
    • nodeurl.Parse is new URL(raw) without a base for any scheme (protocol, hostname, encoded query): special schemes through the WHATWG special authority, file through the file parser (localhost is empty), other schemes through the non-special authority, host and port states (forbidden host code points, IPv6, port digits and 65535, credentials or port without a host) and the opaque-host percent-encoding. - parseAuthorizationInput (anthropic.ts:29, openai-codex.ts:69, openrouter.ts:30) and Copilot's normalizeDomain (github-copilot.ts:41) used Go's url.Parse for schemes other than http/https: ws/wss/ftp/file and custom schemes accepted ports above 65535, '<' in a host, 'u@' without a host…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 15a5c0b670 (2026-10-09), 23aa78b173 (2026-10-09), 2c04c1445a (2026-10-09), 7c4d874438 (2026-10-09), d2b1fc10a1 (2026-10-09)
  • Tests: ai/anthropic_pasted_state_test.go::TestAnthropicLoginSendsAnEmptyPastedStateAsPiDoes, ai/authorization_input_oracle_test.go::TestAuthorizationInputParsersMatchPi, ai/githubcopilot_oracle_test.go::TestGitHubCopilotHelpersMatchPi, ai/trim_boundaries_test.go::TestOAuthPastedInputUsesECMAScriptTrim, ai/trusted_url_oracle_test.go::TestTrustedVerificationURLsMatchPi, internal/nodeurl/url_oracle_test.go::TestParseMatchesNodeURL
  • Release note: GitHub Copilot login reads an enterprise domain the way Pi does: an IPv6 host keeps its brackets, an internationalized name becomes its xn-- form, a numeric host is normalized (1.2.3 is 1.2.0.3) or refused when out of range, a backslash ends the host, and a port above 65535 refuses the domain. A domain given as a URL of a scheme other than http, https, ws, wss or ftp keeps its host as new URL reports it: the case as written, non-ASCII characters percent-encoded, and an empty host for file://localhost. The Copilot token's proxy-ep= claim is found anywhere in the token and used as written, as Pi does, instead of only at the start of a ;-separated part and trimmed.

BF-d2b1fc1-2

Pasting an OAuth authorization into the Anthropic, OpenAI Codex or OpenRouter login now reads it the way Pi…

  • Source: PiG's own defect
  • Symptom: Pasting an OAuth authorization into the Anthropic, OpenAI Codex or OpenRouter login now reads it the way Pi does.
  • Root cause:
    • nodeurl.Parse is new URL(raw) without a base for any scheme (protocol, hostname, encoded query): special schemes through the WHATWG special authority, file through the file parser (localhost is empty), other schemes through the non-special authority, host and port states (forbidden host code points, IPv6, port digits and 65535, credentials or port without a host) and the opaque-host percent-encoding. - parseAuthorizationInput (anthropic.ts:29, openai-codex.ts:69, openrouter.ts:30) and Copilot's normalizeDomain (github-copilot.ts:41) used Go's url.Parse for schemes other than http/https: ws/wss/ftp/file and custom schemes accepted ports above 65535, '<' in a host, 'u@' without a host…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 15a5c0b670 (2026-10-09), 2c04c1445a (2026-10-09), d2b1fc10a1 (2026-10-09)
  • Tests: ai/anthropic_pasted_state_test.go::TestAnthropicLoginSendsAnEmptyPastedStateAsPiDoes, ai/authorization_input_oracle_test.go::TestAuthorizationInputParsersMatchPi, ai/trim_boundaries_test.go::TestOAuthPastedInputUsesECMAScriptTrim, ai/trusted_url_oracle_test.go::TestTrustedVerificationURLsMatchPi, internal/nodeurl/url_oracle_test.go::TestParseMatchesNodeURL
  • Release note: Pasting an OAuth authorization into the Anthropic, OpenAI Codex or OpenRouter login now reads it the way Pi does. A code#state paste keeps only the text up to the next # as the state. A bare query string with a leading ?, a ;, or a malformed % escape keeps the code instead of dropping it. A URL of any scheme that new URL refuses, such as one whose port is above 65535, is read as a plain code, not as a callback URL. An empty pasted state (code# or state=) reaches Anthropic's token request as an empty state, as in Pi, instead of being replaced by the login's own state.

BF-d2d4384

A streamed write card no longer slows down as its content grows

  • Source: PiG's own defect
  • Symptom: A streamed write card no longer slows down as its content grows.
  • Root cause: Since lw-tui2 every frame of a streaming write card encoded the whole partial argument object (ArgumentMembers) and decoded it twice, once for the header and once for the body, and the body split and normalised the whole content again: O(n) per frame, O(n^2) per stream (pauses write:400 6.6 -> 15 s CPU, 60 -> 395 MB own memory).
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 9d5750c403 (2026-10-13), d2d4384c0e (2026-10-13)
  • Tests: ai/request_string_providers_test.go::TestProvidersSendTheSameBodyForEveryLargeStringOfARequest, internal/codingagent/interactive_events_write_frame_cost_norace_test.go::TestStreamedWriteCardFrameCostIsFlat, internal/codingagent/tool_builtin_renderers_write_equivalence_test.go::TestWriteRenderCallMatchesTheEagerRendererAtEveryPrefix, tui/tool_execution_streamed_args_test.go::TestStreamedArgsDrawTheHeaderOfACardWithoutDefinition, tui/tool_execution_streamed_args_test.go::TestStreamedArgsReachAFieldsReaderDecoded
  • Release note: A streamed write card no longer slows down as its content grows. Since streamed tool cards started drawing their partial arguments, every frame encoded the whole argument object and decoded it twice, and the write renderer split the whole content again, so a 400 KB write cost O(n²) (pauses write:400: 15 s CPU and 395 MB own memory, against 6.6 s and 60 MB before). The card now hands the built-in write renderer the object the streaming parser already holds, and the renderer extends its line count and highlighted lines from the previous frame, highlighting only the lines a frame draws. A frame allocates 77 KB at 400 KB received, as at 40 KB (3.1 MB before, 6.4 times the 40 KB frame). A settled write card keeps no highlighted copy of its content.

BF-d305b76

A Go extension whose go.mod replace (or a Rust extension whose pig-sdk path dependency) points at an SDK…

  • Source: PiG's own defect
  • Symptom: A Go extension whose go.mod replace (or a Rust extension whose pig-sdk path dependency) points at an SDK an earlier pig staged under its configuration root, for example through a link made with pig reload --sdk-path go before updating pig, now builds against the SDK the current pig staged. pig never deletes the SDK directories older versions staged, so such a link kept resolving and the extension compiled against the old SDK, failing at load with decode register payload: json: unknown field ....
  • Root cause: A go.mod replace (or Cargo path dependency) that resolves inside <config root>/state/pigsdk/ but outside the SDK this pig stages now points at an SDK an earlier pig staged; pig never deletes those directories, so a link made with pig reload --sdk-path go kept resolving after an update and the extension compiled against the old SDK, failing at register with decode register payload: json: unknown field "can_block" and making pig extension upgrade type-check against the same old SDK. Treat such a replace like a missing one in the packed, isolated and upgrade-analysis paths, Go and Rust; a replace anywhere else still wins.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d305b76bda (2026-10-12)
  • Tests: coding/extension/host/runtimecell/go_sdk_override_test.go::TestGoSDKPathOverrideIgnoresRetiredStagedSDK, coding/extension/host/runtimecell/go_sdk_override_test.go::TestGoSDKPathOverrideKeepsCurrentStagedSDK, coding/extension/host/runtimecell/go_sdk_override_test.go::TestRustSDKPathOverrideIgnoresRetiredStagedSDK, coding/extension/host/subprocess/builder_test.go::TestGoNeedsStagedSDK, coding/extension/host/subprocess/builder_test.go::TestRustNeedsStagedSDK
  • Release note: A Go extension whose go.mod replace (or a Rust extension whose pig-sdk path dependency) points at an SDK an earlier pig staged under its configuration root, for example through a link made with pig reload --sdk-path go before updating pig, now builds against the SDK the current pig staged. pig never deletes the SDK directories older versions staged, so such a link kept resolving and the extension compiled against the old SDK, failing at load with decode register payload: json: unknown field .... An extension written for an older SDK now gets the written for an older SDK report instead. A replace that points anywhere else, such as your own SDK checkout or copy, still wins.

BF-d36b329

The edit tool no longer accepts a JSON-string edits value that starts or ends with non-JSON whitespace such…

  • Source: PiG's own defect
  • Symptom: The edit tool no longer accepts a JSON-string edits value that starts or ends with non-JSON whitespace such as a vertical tab or a no-break space; Pi's JSON.parse rejects it.
  • Root cause: prepareEditArguments JSON.parses a string edits (edit.ts:117-127), and JSON.parse accepts only space, tab, LF and CR around the value. strings.TrimSpace also stripped \v, \f, U+0085 and U+00A0, so Pig accepted edits Pi leaves as a string.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d36b329a4c (2026-10-06)
  • Tests: internal/codingagent/tools/edit_diff_test.go::TestEditPrepareArgumentsKeepsMemberOrder
  • Release note: The edit tool no longer accepts a JSON-string edits value that starts or ends with non-JSON whitespace such as a vertical tab or a no-break space; Pi's JSON.parse rejects it.

BF-d37c769

A replacement request body from a before_provider_request hook on Anthropic Messages, and every OpenAI…

  • Source: PiG's own defect
  • Symptom: A replacement request body from a before_provider_request hook on Anthropic Messages, and every OpenAI Codex WebSocket response.create message, now leave in Pi's member order (type first for the WebSocket message) instead of sorted keys.
  • Root cause: The Anthropic replacement path split the body into a map and the WebSocket path built {type, ...requestBody} from a map, so both wrote sorted keys where Pi's JSON.stringify writes insertion order (type first, then the body's members; a cached continuation replaces input in place and adds previous_response_id last). Both now edit the body member by member (ai/ordered_json_object.go). Byte parity, no divergence.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d37c76932f (2026-10-10)
  • Tests: ai/request_body_member_order_test.go::TestAnthropicReplacementBodyKeepsItsMemberOrder, ai/request_body_member_order_test.go::TestCodexWebSocketMessageKeepsTheRequestBodyMemberOrder
  • Release note: A replacement request body from a before_provider_request hook on Anthropic Messages, and every OpenAI Codex WebSocket response.create message, now leave in Pi's member order (type first for the WebSocket message) instead of sorted keys.

BF-d39069d

Tool cards rebuilt when a session is resumed now follow the showImages and imageWidthCells settings

  • Source: PiG's own defect
  • Symptom: Tool cards rebuilt when a session is resumed now follow the showImages and imageWidthCells settings.
  • Root cause: Fixes resumed tool cards ignoring terminal.showImages/imageWidthCells (interactive-mode.ts:3997 passes both; renderSessionEntries built the card without them).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d39069da65 (2026-10-06)
  • Tests: internal/codingagent/interactive_tool_execution_options_test.go::TestResumedToolCardsUseTheImageSettings, internal/codingagent/interactive_tool_execution_options_test.go::TestToolExecutionOptionsDefaultWhenUndefined, internal/imageprocessing/image_processing_upstream_test.go::TestFormatDimensionNoteUpstream, internal/imageprocessing/input_limits_test.go::TestResizeModelProfileJPEGQualityAndSizeOnlyHint
  • Release note: Tool cards rebuilt when a session is resumed now follow the showImages and imageWidthCells settings.

BF-d3b3618

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: d3b36181ab (2026-10-21)
  • Tests: None recorded.

BF-d42aebb

ai.ValidateToolArguments and ai.ValidateToolCall list a failed validation's errors in the order the…

  • Source: PiG's own defect
  • Symptom: ai.ValidateToolArguments and ai.ValidateToolCall list a failed validation's errors in the order the tool's schema declares its properties, as Pi does, instead of sorted by property name.
  • Root cause: Pi validates tool.parameters itself (validation.ts:317-341), so TypeBox lists property errors in the schema's member order. ValidateToolArguments encoded tool.Parameters with json.Marshal and listed them sorted; it now validates ToolSchema.ParametersJSON.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d42aebb5a0 (2026-10-13)
  • Tests: ai/validate_schema_order_test.go::TestValidateToolArgumentsListsErrorsInTheSchemasMemberOrder
  • Release note: ai.ValidateToolArguments and ai.ValidateToolCall list a failed validation's errors in the order the tool's schema declares its properties, as Pi does, instead of sorted by property name.

BF-d449f74

tool_search and the Durable searchTools() ranker compute the same BM25 scores as Pi on arm64: the length…

  • Source: PiG's own defect
  • Symptom: tool_search and the Durable searchTools() ranker compute the same BM25 scores as Pi on arm64: the length normalization k1 * (1 - b + b * length / averageLength) is rounded before the term count is added, instead of being fused into one multiply-add, so scores no longer differ from Pi's in the last bit and documents that tie in Pi keep their order (Pi 1.2.0 tool-search/tool.ts Bm25Ranker, durable/src/search.ts rankDocuments).
  • Root cause: On arm64 the Go compiler fused k1*(...) + count into one FMADD, so tool_search and searchTools() scores differed from Pi's in the last bit and could reorder ties. TestToolSearchMatchesPi had 65 such mismatches.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34c4d8e058 (2026-10-12), d449f7433b (2026-10-13)
  • Tests: coding/extension/builtin/toolsearch/toolsearch_oracle_test.go::TestToolSearchMatchesPi, durable/internal/search/search_test.go::TestRankDocumentsRoundsEveryOperationLikeJavaScript
  • Release note: tool_search and the Durable searchTools() ranker compute the same BM25 scores as Pi on arm64: the length normalization k1 * (1 - b + b * length / averageLength) is rounded before the term count is added, instead of being fused into one multiply-add, so scores no longer differ from Pi's in the last bit and documents that tie in Pi keep their order (Pi 1.2.0 tool-search/tool.ts Bm25Ranker, durable/src/search.ts rankDocuments).

BF-d490a8b

Tool argument validation errors follow Pi: at most eight errors are listed (TypeBox's maxErrors), and…

  • Source: PiG's own defect
  • Symptom: Tool argument validation errors follow Pi: at most eight errors are listed (TypeBox's maxErrors), and errors for additional properties, pattern properties and property names come in the arguments' member order instead of sorted order.
  • Root cause: validateToolArguments (packages/ai/src/utils/validation.ts, with typebox 1.3.27 Compile/Value.Convert/Check/Errors from internal/experimental/protocol/testdata/typebox, testdata/validate_tool_args.mjs) runs on 12000 seeded schemas (scalars, type unions, enum, const, bounds, patterns, objects with required/additionalProperties/patternProperties/dependentRequired, arrays, anyOf/oneOf/allOf, defaults) and argument values (right types, stringified numbers and booleans, nulls, wrong shapes, big numbers). The coerced result or the whole error message must match. 118 of 6000 differed before (red): (1) TypeBox's error context keeps the first 8 errors (Settings.maxErrors) and Go listed all; (2)…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d490a8b161 (2026-10-09)
  • Tests: ai/validate_tool_args_oracle_test.go::TestValidateToolArgumentsMatchesPiOnSeededSchemas
  • Release note: Tool argument validation errors follow Pi: at most eight errors are listed (TypeBox's maxErrors), and errors for additional properties, pattern properties and property names come in the arguments' member order instead of sorted order.

BF-d4ddb14

The /scoped-models search input stays editable after a submit key reaches it (LF under the Kitty keyboard…

  • Source: PiG's own defect
  • Symptom: The /scoped-models search input stays editable after a submit key reaches it (LF under the Kitty keyboard protocol, or Enter with tui.select.confirm rebound): it was a host-owned input that finished on submit and ignored all later typing; Pi builds new Input() with no onSubmit (tui/scoped_models_list.go; TestScopedModelsSelectorMatchesPi now also runs 336 Kitty-protocol probes, red before; regression TestScopedModelsSearchStaysEditableAfterASubmitKey).
  • Root cause: The list built its search with NewTextInput (host-owned: submit marks it done and later input is ignored); Pi builds new Input() with no onSubmit. Under the Kitty protocol LF is not select.confirm, so it reached the input and the query froze; the same happens when select.confirm is rebound.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 33fe10d58e (2026-10-11), c4ad739eb8 (2026-10-11), d4ddb14da2 (2026-10-11), fac470319e (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/models_error_oracle_test.go::TestModelsErrorMessageMatchesPi, coding/extension/host/inproc/registries_test.go::TestShortcuts_DiagnosticMessageMatchesUpstream, coding/extension/host/inproc/shortcuts_oracle_test.go::TestRunnerShortcutsMatchPi, coding/mcpext/manager_view_oracle_test.go::TestMcpManagerViewMatchesPi and 12 more in docs/bug-ledger.json
  • Release note: The /scoped-models search input stays editable after a submit key reaches it (LF under the Kitty keyboard protocol, or Enter with tui.select.confirm rebound): it was a host-owned input that finished on submit and ignored all later typing; Pi builds new Input() with no onSubmit (tui/scoped_models_list.go; TestScopedModelsSelectorMatchesPi now also runs 336 Kitty-protocol probes, red before; regression TestScopedModelsSearchStaysEditableAfterASubmitKey).

BF-d50ec25

The TypeScript extension runtime no longer depends on Node-only behaviour: its entry modules do not await the…

  • Source: PiG's own defect
  • Symptom: The TypeScript extension runtime no longer depends on Node-only behaviour: its entry modules do not await the admission loop at top level (Bun never reaches beforeExit while an entry module's top-level await is pending, so the quit drain after RPC input ended hung), it hooks v8.Serializer.prototype.writeValue only where the runtime has it, it keeps a native method when the runtime froze built-ins, and it loads its lazy modules through one list that a bundling runtime can serve.
  • Root cause: retryOnNextRuntime retried every error that was not a *LoadError, including a provider registration the host rejected after the register handshake; with a slot runtime selected the factory then ran again on the next runtime over its partial registration. It now retries only a *LoadError with a spawn, exit or register failure code, as its comment states. TestTypeScriptRuntimeRetriesOnlyStartFailures failed before the fix (provider registration error case).
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 086bed4866 (2026-10-10), 3f3ebefabc (2026-10-10), 5a2b6b9941 (2026-10-10), 9bc13c0910 (2026-10-10), d50ec25dee (2026-10-10)
  • Tests: coding/cli/startup_alloc_budget_test.go::TestStartupAllocationBudget, coding/cli/startup_init_budget_test.go::TestLinkedPackagesCompileNoRegexpAtInit, coding/cli/startup_init_budget_test.go::TestPackageInitWorkBudget, coding/extension/host/subprocess/node_cell_failure_test.go::TestNodeLauncherServeRejectionExitsOne, coding/extension/host/subprocess/node_jiti_cache_test.go::TestNodeJitiCachePersistsWithoutStaleSource, coding/extension/host/subprocess/runtime_modules_test.go::TestRuntimeModulesListsEveryLazyLoad, coding/extension/host/subprocess/ts_runtime_test.go::TestTypeScriptRuntimeRetriesAFailedStartOnTheNextRuntime, coding/extension/host/subprocess/ts_runtime_test.go::TestTypeScriptRuntimeRetriesOnlyStartFailures and 1 more in docs/bug-ledger.json
  • Release note: The TypeScript extension runtime no longer depends on Node-only behaviour: its entry modules do not await the admission loop at top level (Bun never reaches beforeExit while an entry module's top-level await is pending, so the quit drain after RPC input ended hung), it hooks v8.Serializer.prototype.writeValue only where the runtime has it, it keeps a native method when the runtime froze built-ins, and it loads its lazy modules through one list that a bundling runtime can serve. On Node, a serve failure still ends the extension process with exit code 1; a runtime process whose event loop empties while it waits for the next admission now exits 0 instead of Node's unsettled-top-level-await code 13 with its warning.

BF-d514b3d

In a Node extension's before_agent_start handler, event.systemPrompt and ctx.getSystemPrompt() now…

  • Source: PiG's own defect
  • Symptom: In a Node extension's before_agent_start handler, event.systemPrompt and ctx.getSystemPrompt() now render the current options, as in Pi.
  • Root cause: The Node runtime gave event.systemPrompt as the string the host rendered before the handler ran, and ctx.getSystemPrompt() returned the Session's prompt.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d514b3de97 (2026-10-13)
  • Tests: coding/extension/host/inproc/before_agent_start_render_test.go::TestBeforeAgentStartPromptFollowsEveryOptionsEdit, coding/extension/host/subprocess/render_system_prompt_test.go::TestUIBridgeRenderSystemPromptRendersTheOptionsItIsSent, test/parity/closure/known_gaps_030_test.go::TestApproved030GapsRemainProvisionalNotWaived, test/parity/scenarios/extensions-runtime/82-before-agent-start-current-system-prompt.toml
  • Release note: In a Node extension's before_agent_start handler, event.systemPrompt and ctx.getSystemPrompt() now render the current options, as in Pi. The handler's own edits to event.systemPromptOptions and an earlier handler's returned systemPrompt both show.

BF-d53cd09

A tool call whose arguments are not an object is written back to each provider as Pi writes it: Anthropic and…

  • Source: PiG's own defect
  • Symptom: A tool call whose arguments are not an object is written back to each provider as Pi writes it: Anthropic and Google send {} for null (arguments ?? {}), Mistral sends {} for null, 0, false and "" (arguments || {}), Bedrock sends the value itself; a Gemini function call with "args": null is the empty object, and the agent proxy keeps Pi's parseStreamingJson(...) || {} and the streamed arguments of a toolcall_end that omits them.
  • Root cause: An earlier change keeps a non-object parse result ([] null 5 "s") as Pi's parseStreamingJson returns it. The sites that read or write the value then passed it through verbatim, where Pi applies its own ?? {} or || {}. For null that is a regression: before the commit every site wrote {}. - Google ingest: google-generative-ai.ts:207 args ?? {}. "args": null now became the arguments null with delta "null". It is {} again (defaultNullishArguments). - Replay: anthropic-messages.ts:1444 input: arguments ?? {} and google-shared.ts:270 args: arguments ?? {} sent null. mistral-conversations.ts:849 JSON.stringify(arguments || {}) sent "null", "0", "false" and "".…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d53cd09a75 (2026-10-10)
  • Tests: agent/proxy_non_object_arguments_test.go::TestProxyNonObjectToolArgumentsFollowPi, ai/tool_call_non_object_converters_test.go::TestGoogleNullFunctionCallArgsAreTheEmptyObject, ai/tool_call_non_object_converters_test.go::TestNonObjectToolArgumentsReplayAsEachProviderDoes
  • Release note: A tool call whose arguments are not an object is written back to each provider as Pi writes it: Anthropic and Google send {} for null (arguments ?? {}), Mistral sends {} for null, 0, false and "" (arguments || {}), Bedrock sends the value itself; a Gemini function call with "args": null is the empty object, and the agent proxy keeps Pi's parseStreamingJson(...) || {} and the streamed arguments of a toolcall_end that omits them.

BF-d60d422

pi.exec gives the command PiG's environment unchanged, as Pi does, so a command started in a symlinked…

  • Source: PiG's own defect
  • Symptom: pi.exec gives the command PiG's environment unchanged, as Pi does, so a command started in a symlinked directory no longer gets that directory as its PWD; pwd there now prints the physical directory, as in Pi.
  • Root cause: os/exec sets PWD to cmd.Dir when cmd.Env is nil; Node's spawn without an env option passes process.env unchanged (normalizeSpawnArguments). pi.exec's child in a symlinked cwd (macOS /var -> /private/var) then printed the symlinked name from pwd where Pi prints the physical directory (TestExecCommandMatchesPi scenarios 11 and 48).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), d60d422e2e (2026-10-13)
  • Tests: coding/extension/exec_oracle_test.go::TestExecCommandMatchesPi, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, internal/nodespawn/start_other_test.go::TestStartWithoutEnvPassesPiGsEnvironmentAsNodeDoes, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-switch.toml and 1 more in docs/bug-ledger.json
  • Release note: pi.exec gives the command PiG's environment unchanged, as Pi does, so a command started in a symlinked directory no longer gets that directory as its PWD; pwd there now prints the physical directory, as in Pi.

BF-d64f8ed

durable.StorageRequestError and durable.SessionFailed are Pi's error classes of the same names…

  • Source: PiG's own defect
  • Symptom: durable.StorageRequestError and durable.SessionFailed are Pi's error classes of the same names (errors.ts).
  • Root cause: The built-in storages return a StorageRequestError for a read of an unknown conversation, a document without retained history, or a cursor or scan order they did not write; the driver matches both names in a remote error.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f1d3eec6de (2026-10-09), d64f8edf71 (2026-10-10)
  • Tests: ai/anthropic_microtask_trace_test.go::TestAnthropicMicrotaskTrace, ai/credential_expiry_test.go::TestCredentialExpiryMatchesPi, ai/zen_upstream_test.go::TestOpenCodeModelsSmokeUpstream, durable/core/driver/remote_errors_session_test.go::TestCoreErrorMatchesStorageRequestErrorAndSessionFailed, durable/errors_session_failed_test.go::TestStorageRequestErrorAndSessionFailedMatchErrorsTS, durable/storage/storage_request_error_test.go::TestMemoryStorageReadsRejectInvalidRequestsWithStorageRequestError, test/parity/runner/binaries_missing_test.go::TestResolveUpstreamPiBinNeverFallsBackToAMiseInstallWithoutOptIn
  • Release note: durable.StorageRequestError and durable.SessionFailed are Pi's error classes of the same names (errors.ts). The built-in storages return a StorageRequestError for a read of an unknown conversation, a document without retained history, or a cursor or scan order they did not write; the driver matches both names in a remote error.

BF-d6aaf72

A setTimeout(0) or setImmediate callback that an extension queues while an RPC stdin command runs (a…

  • Source: PiG's own defect
  • Symptom: A call that a Go, Rust or Python extension makes through a context whose request returned runs on arrival while another request of the extension is in flight, instead of waiting for a loop turn that waits for that request: a handler that kept an earlier request's context and awaited a call through it used to hang the run.
  • Root cause: A handler that is still awaiting real I/O (a child process, a dialog) is not held back, because Pi's loop turns while it waits and runs the timer.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 463879414a (2026-10-21), 58563ba284 (2026-10-21), 5a182154fc (2026-10-21), 7a4ddb3733 (2026-10-21), 8506081bd3 (2026-10-21), d6aaf72307 (2026-10-21)
  • Tests: extensions/sdk/request_lifetime_race_test.go::TestNormalCompletionPublishedBeforeResponseIsVisible, extensions/sdk/request_lifetime_race_test.go::TestRetainedContextParentSelectionRacesNormalResponse, internal/jsloop/turns_test.go::TestReleaseRunsOnlyTheCallbacksHeldForItsOwner, test/extension-conformance/timer_calls_test.go::TestHostCallAwaitedByARunningHandlerIsNotHeldForTheLoopTurn, test/parity/scenarios/rpc/70-rpc-command-chain-timer-send.toml, test/parity/scenarios/rpc/71-rpc-command-chain-timer-dialog.toml
  • Release note: A setTimeout(0) or setImmediate callback that an extension queues while an RPC stdin command runs (a model_select handler during set_model, say) now reaches the client after the command's response, as in Pi, instead of before it in about one run in ten. Pi runs the command's microtasks and writes its response before its loop runs a timer, so the Node runtime now holds its timer callbacks from the moment the host reads the command until the host has written the response, and the host waits for the runtime's acknowledgement of that hold before it sends the command's first event. A handler that is still awaiting real I/O (a child process, a dialog) is not held back, because Pi's loop turns while it waits and runs the timer. A call that a Go, Rust or Python extension makes through a context whose request returned runs on arrival while another request of the extension is in flight, instead of waiting for a loop turn that waits for that request: a handler that kept an earlier request's context and awaited a call through it used to hang the run.

BF-d6e0dca

The extension SDK surface matrix and the extension API pages say that setEditorComponent and…

  • Source: PiG's own defect
  • Symptom: The extension SDK surface matrix and the extension API pages say that setEditorComponent and getEditorComponent exist in the Go, Rust and Python SDKs and that the getter returns the factory the extension installed.
  • Root cause: The matrix rows and the exception are corrected and the getter is documented for every SDK. Signed-off-by: Michael Kinsy mrkinsy5@gmail.com
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d6e0dca953 (2026-10-09)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestEveryNewDivergenceIsAccountedFor
  • Release note: The extension SDK surface matrix and the extension API pages say that setEditorComponent and getEditorComponent exist in the Go, Rust and Python SDKs and that the getter returns the factory the extension installed. They described the Go getter as always returning nil and the Rust and Python getters as missing.

BF-d7855fd

A subprocess extension's message, entry or tool renderer, or tool renderer resolver, that was busy past the…

  • Source: PiG's own defect
  • Symptom: A subprocess extension's message, entry or tool renderer, or tool renderer resolver, that was busy past the five-second renderer inactivity boundary no longer leaves its card on the fallback until the next resize: the host asks again once the extension answers the abandoned request, and an extension that keeps missing the boundary is asked again after a backoff that starts at five seconds and doubles to one minute.
  • Root cause: A resolve_tool_renderers request the renderer inactivity boundary abandoned recorded next() as the extension's answer, so the tool drew without the resolver's renderers for the rest of the session; Pi's resolvers answer synchronously. An abandoned resolution now records nothing, and renderRetry asks the extension again with the same next() (on the late answer after the first abandonment in a row, or after the backoff), on the goroutine group Shutdown joins. As for the first request, only an answer that differs from next() draws the tool's cards again. awaitRenderRetry also ends without a retry when the connection closes first.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 84e70fc701 (2026-10-13), d7855fd99b (2026-10-14)
  • Tests: coding/extension/host/subprocess/render_retry_backoff_test.go::TestRenderRetryBackoffDoublesUpToItsCap, coding/extension/host/subprocess/render_retry_test.go::TestRenderProxyBacksOffFromARendererThatAlwaysAnswersLate, coding/extension/host/subprocess/render_retry_test.go::TestRenderProxyBacksOffFromAnExtensionThatNeverAnswers, coding/extension/host/subprocess/render_retry_test.go::TestRenderProxyRendersAgainOnceTheExtensionAnswersAnAbandonedRender, coding/extension/host/subprocess/render_retry_test.go::TestToolRendererResolutionAsksAgainAfterAnAbandonedRequest, test/extension-conformance/tool_renderer_resolvers_test.go::TestToolRendererRegisteredAfterLoadingAcrossSDKs, test/extension-conformance/tool_renderer_resolvers_test.go::TestToolRendererResolversAcrossSDKs
  • Release note: A subprocess extension's message, entry or tool renderer, or tool renderer resolver, that was busy past the five-second renderer inactivity boundary no longer leaves its card on the fallback until the next resize: the host asks again once the extension answers the abandoned request, and an extension that keeps missing the boundary is asked again after a backoff that starts at five seconds and doubles to one minute.

BF-d7bfea6

A git source whose ref has no repository (git:#v1) no longer panics, and a ref split out of an opaque-path…

  • Source: PiG's own defect
  • Symptom: A git source whose ref has no repository (git:#v1) no longer panics, and a ref split out of an opaque-path URL (git:bitbucket:git+https://@v1) leaves the repository whole, as Pi's parseGitUrl does.
  • Root cause: A seeded random corpus (6,000 strings from the tokens parseGitUrl branches on) added to TestParseGitMatchesPi found two differences: - hostedgit.go isGitHubShorthand read arg[firstHash-1] when the argument starts with '#', which JavaScript reads as undefined (not '/') and Go panicked on ("git:#v1" gives the hosted candidate "#v1"). - nodeurl.URL.WithPathname replaced the path of a URL with an opaque path ("bitbucket:git+https://@v1"), where the pathname setter does nothing; splitRef then returned a rewritten repository and Pi returns the URL whole.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d7bfea6f68 (2026-10-11)
  • Tests: coding/source/parse_git_oracle_test.go::TestParseGitMatchesPi, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode
  • Release note: A git source whose ref has no repository (git:#v1) no longer panics, and a ref split out of an opaque-path URL (git:bitbucket:git+https://@v1) leaves the repository whole, as Pi's parseGitUrl does.

BF-d7c7f86

fix(runtimecell): start a packed cell's members in order so a later member's factory cannot take the factory…

  • Source: PiG's own defect
  • Symptom: fix(runtimecell): start a packed cell's members in order so a later member's factory cannot take the factory turn from an earlier one
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: d7c7f86a12 (2026-10-21)
  • Tests: None recorded.

BF-d80425c

fix(train): gate repairs after merging train 3 and the ordering stack

  • Source: PiG's own defect
  • Symptom: fix(train): gate repairs after merging train 3 and the ordering stack
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: d80425c2e2 (2026-10-21)
  • Tests: None recorded.

BF-d80c1cd

Where Pi calls new URL(value), Pig now parses a ws:, wss:, ftp: or file: URL, and a bracketed IPv6…

  • Source: PiG's own defect
  • Symptom: Where Pi calls new URL(value), Pig now parses a ws:, wss:, ftp: or file: URL, and a bracketed IPv6 host of any other scheme, instead of refusing it.
  • Root cause: ParseURL (genericurl.go) now also parses ws, wss and ftp through the WHATWG special parser (default ports 80, 443 and 21, shared with http and https via parseSpecialHref), file through the file URL parser (host, path, query, fragment), and a bracketed IPv6 host of a non-special scheme through opaqueHost, and reports Query next to Protocol, Username, Password, Hostname, Pathname and Hash. url.go (AnySchemeURL, ParseAnyScheme, parseNonSpecialAuthority) is removed; opaqueHost moved into genericurl.go and isForbiddenHostCodePoint, now duplicated by it, is gone. Callers updated: ai urlHostname (github-copilot enterprise domain) and authorizationURLQuery (pasted OAuth input) use ParseURL…
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 056bbfb74e (2026-10-09), 15a5c0b670 (2026-10-09), 23aa78b173 (2026-10-09), 2c04c1445a (2026-10-09), 7c4d874438 (2026-10-09), 97e178f7f6 (2026-10-09), c4d51d5c1a (2026-10-09), d80c1cd150 (2026-10-09), da99854d1b (2026-10-09)
  • Tests: ai/authorization_input_oracle_test.go::TestAuthorizationInputParsersMatchPi, ai/codex_account_id_oracle_test.go::TestCodexAccountIDMatchesPi, ai/githubcopilot_oracle_test.go::TestGitHubCopilotHelpersMatchPi, ai/streaming_json_oracle_test.go::TestStreamingJSONMatchesPiOnSeededInput, ai/trusted_url_oracle_test.go::TestTrustedVerificationURLsMatchPi, coding/cli/strip_app_features_test.go::TestStripExportHTMLBuildOmitsExportPackage, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode, internal/nodeurl/url_oracle_test.go::TestParseMatchesNodeURL and 1 more in docs/bug-ledger.json
  • Release note: Where Pi calls new URL(value), Pig now parses a ws:, wss:, ftp: or file: URL, and a bracketed IPv6 host of any other scheme, instead of refusing it. This affects package and repository sources (coding/source), the OAuth paste and GitHub Copilot enterprise-domain inputs.

BF-d8357e2

Cache warmer pure functions against live Pi: new TestCacheWarmerPureFunctionsMatchPi…

  • Source: PiG's own defect
  • Symptom: Cache warmer pure functions against live Pi: new TestCacheWarmerPureFunctionsMatchPi (internal/codingagent/cache_warmer_format_oracle_test.go; getCacheWarmingDelayMs, getPromptCacheTtlMs, isReplayable, formatCacheWarmingStatus, formatCacheWarmingUsage; 6,000 inputs under three process PI_CACHE_RETENTION values; TTLs around the 10 s margin, tier values 0/fractional, empty request env falling through to the process one, adaptive vs budget thinking, every status state with rounding-boundary probabilities and dollars).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): d8357e2802 (2026-10-11)
  • Tests: internal/codingagent/cache_warmer_format_oracle_test.go::TestCacheWarmerPureFunctionsMatchPi
  • Release note: Cache warmer pure functions against live Pi: new TestCacheWarmerPureFunctionsMatchPi (internal/codingagent/cache_warmer_format_oracle_test.go; getCacheWarmingDelayMs, getPromptCacheTtlMs, isReplayable, formatCacheWarmingStatus, formatCacheWarmingUsage; 6,000 inputs under three process PI_CACHE_RETENTION values; TTLs around the 10 s margin, tier values 0/fractional, empty request env falling through to the process one, adaptive vs budget thinking, every status state with rounding-boundary probabilities and dollars). No bug (formatCacheWarmingStatus was previously only reached through the /session oracle). 19 mutations red; 5 equivalent: empty-API guard in IsReplayable, Phase/Action closed-union comparisons, the seconds > 0 clause when no larger unit printed (nextWarmAt > now forces at least 1 s), and the none retention check (a model's promptCache holds only short/long). Not compared: NaN/infinite TTLs.

BF-d87b70c

A second ctx.ui.custom component opened while one is shown now opens over it at once, as in Pi, whether it…

  • Source: PiG's own defect
  • Symptom: A subprocess extension's custom component waited until the shown one closed, and a subprocess custom component kept reading the keys while another custom component shown over it had focus.
  • Root cause: Each SDK now holds the prompt the host rendered for the options the handler received and, once the options differ, asks the host to render them with renderSystemPrompt, reusing the result until they change again: - Go: data["systemPrompt"] is an *sdk.SystemPrompt (Get, MarshalJSON), since a map cannot run a getter; ctx.GetSystemPrompt() returns the same value. - Python: the event dict's "systemPrompt" and ctx.get_system_prompt() render. The Host answers renderSystemPrompt without a UI bridge too (an SDK session): the render is a pure function of the options the call carries. Callers of the Go string form move to Get(); the known gaps are deleted; pig-go.json is regenerated by gointerfaces.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), 007c91dcbb (2026-10-13), 1848633092 (2026-10-13), 2a055a7a3a (2026-10-13), 588884c3e8 (2026-10-13), d87b70c54c (2026-10-13)
  • Tests: coding/extension/host/inproc/specialized_dispatch_test.go::TestEmitBeforeAgentStart_ForcedPromptWinsAndInvalidSectionFailsTheHandler, coding/extension/host/subprocess/host_calls_test.go::TestReadLoopWidgetPushOrdersAgainstQueuedSlotCalls, coding/extension/host/subprocess/ui_bridge_test.go::TestFocusedCustomComponentsStack, coding/extension/host/subprocess/ui_bridge_test.go::TestInteractiveDialogOpensOverFocusedOverlay, coding/extension/host/subprocess/ui_prompt_test.go::TestUIBridgePromptScopeDialogOverCustomAndNoUI, coding/extension/host/subprocess/view_terminal_parity_test.go::TestViewsLeaveTheTerminalBytesUnchanged, coding/extension/host/subprocess/widget_key_namespace_test.go::TestTwoExtensionsSettingOneWidgetKeyShareIt, extensions/sdk-py/tests/test_system_prompt_render.py and 21 more in docs/bug-ledger.json
  • Release note: A second ctx.ui.custom component opened while one is shown now opens over it at once, as in Pi, whether it is an overlay or not and whichever extension process opens it. The component on top takes focus and the keys, and when it closes focus returns to the component below, which receives the next key. Before, a subprocess extension's custom component waited until the shown one closed, and a subprocess custom component kept reading the keys while another custom component shown over it had focus.

BF-d88dca7

harness.ToolTaskInput.Kind and ToolTaskResult.Kind are the named string type harness.ToolTaskKind with…

  • Source: PiG's own defect
  • Symptom: harness.ToolTaskInput.Kind and ToolTaskResult.Kind are the named string type harness.ToolTaskKind with ToolTaskModel and ToolTaskNested, and harness.ToolHookCall carries Type ("toolCall") like Pi's ToolHookCall; build one with harness.NewToolHookCall.
  • Root cause: .upstream/pin names the pinned Pi build: a release tag or a pi-mono commit (version 1.1.0+). pigversion.go is generated from it; coding gains UpstreamRef and UpstreamPackageVersion beside the display version. PIUPSTREAM_REF=<tag|commit> overrides the pin for one run (make variables, a pigversion.go build overlay, PIG_UPSTREAM* in the environment). make pin-bump moves the pin and regenerates what derives from it: SDK, CI-image and Durable package pins, README and ledger version fields, the source mirror, the inventories.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 86fe3dc28b (2026-10-09), f1d3eec6de (2026-10-09), 014c014e74 (2026-10-10), 0a25e09319 (2026-10-10), 2de6fe21b8 (2026-10-10), 69538c209e (2026-10-10), 76d1a85e45 (2026-10-10), 90b48897e1 (2026-10-10), b7199a2ba1 (2026-10-10), c87dea7287 (2026-10-10), d88dca723b (2026-10-10)
  • Tests: ai/validate_value_test.go::TestCheckSchemaValue, automation/pin/test_pin.py, durable/core/docs/addtools_test.go::TestAddToolsExtendsModelToolsAsWell, durable/core/driver/tool_structured_test.go::TestRetainedOutputIsTheOutputKeptSoFar, durable/core/driver/tool_structured_test.go::TestRetainedOutputReportsDroppedOutputAsTruncated, durable/core/turn/generation_test.go::TestToolThrowEndsFailedWithTheErrorInTheResult, durable/core/turn/tool_v2_test.go::TestToolTaskMigratesVersion1, durable/core/turn/tool_v2_test.go::TestToolTaskRecordIsAVersion2ModelCall and 33 more in docs/bug-ledger.json
  • Release note: harness.ToolTaskInput.Kind and ToolTaskResult.Kind are the named string type harness.ToolTaskKind with ToolTaskModel and ToolTaskNested, and harness.ToolHookCall carries Type ("toolCall") like Pi's ToolHookCall; build one with harness.NewToolHookCall.

BF-d8bed22

auth.json keeps every provider's entry as written, as Pi does

  • Source: PiG's own defect
  • Symptom: auth.json keeps every provider's entry as written, as Pi does.
  • Root cause: TestAuthStorageOnCorruptedFileMatchesPi runs 800 scripts (create, list, read, modify, delete, file rewritten between calls) over corrupted auth.json files against the installed AuthStorage: 293 differed. A wrong-typed field in one entry ("key": 5, "env": {"A": 1}) made every read, list and write fail with 'auth: parse: ...' (Pi keeps the value and the other providers work), and an incomplete entry ({"type":"oauth"}, {}) was rewritten into the typed form (empty refresh/access, "type": "") by an unrelated write. An entry now keeps its source when the typed credential cannot hold it exactly (decodeStoredCredential), written canonically unless the caller changed it (sourceUnchanged). Legacy PiG…
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1a29f511e7 (2026-10-09), 2967084da2 (2026-10-09), 81e0e1b24e (2026-10-09), d07038cb99 (2026-10-09), ea72139272 (2026-10-09), d8bed22101 (2026-10-10)
  • Tests: ai/auth_file_bytes_oracle_test.go::TestAuthFileBytesMatchPi, ai/auth_load_oracle_test.go::TestAuthStorageOnCorruptedFileMatchesPi, ai/auth_opaque_entries_test.go::TestAuthStorageKeepsEntriesItCannotTypeAcrossWrites, ai/classifier_answers_oracle_test.go::TestClassifierAnswersMatchPi, ai/oauth_credential_members_test.go::TestOAuthBuildersWriteCredentialMembersInPiOrder, ai/system_one_shared_test.go::TestSystemOneSharedFailureMessages, internal/codingagent/migrations_tree_oracle_test.go::TestRunMigrationsTreeMatchesPi
  • Release note: auth.json keeps every provider's entry as written, as Pi does. An entry the credential types cannot hold (a wrong-typed field such as "key": 5 or "env": {"A": 1}, an OAuth entry without refresh or access, a value that is not an object) no longer makes every credential read, list and write fail with auth: parse: …, and an unrelated login or logout no longer rewrites that entry into the typed form ("type": "", empty refresh and access).

BF-d8d513a

With the Bun TypeScript runtime (PIG_TS_RUNTIME=bun), /reload now evaluates an edited ES module extension…

  • Source: PiG's own defect
  • Symptom: Bun kept the first evaluation until PiG restarted.
  • Root cause: Lead ruling 2026-10-11T20:57Z (2b): port D93 to the Bun runtime so Node and Bun behave the same.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d8d513a98a (2026-10-11)
  • Tests: coding/extension/host/subprocess/runtime_modules_test.go::TestRuntimeModulesListsEveryLazyLoad, coding/extension/host/subprocess/xref_event_bus_test.go::TestXrefEventBusCloneLocalGraphsMatchPi, coding/extension/host/subprocess/xref_event_bus_test.go::TestXrefEventBusCloneMatchesPi, coding/extension/host/subprocess/xref_event_bus_test.go::TestXrefEventBusInspectMatchesPi, coding/extension/host/subprocess/xref_event_bus_test.go::TestXrefEventBusIntegrityLevelsMatchPi, coding/extension/host/subprocess/xref_event_bus_test.go::TestXrefEventBusRejectedWritesMatchPi
  • Release note: With the Bun TypeScript runtime (PIG_TS_RUNTIME=bun), /reload now evaluates an edited ES module extension (.mjs, or .js under "type": "module") and the extension modules that import it again, as it does on Node (D93). Before, Bun kept the first evaluation until PiG restarted.

BF-d90c2f4

Markdown now nests a **strong** run inside *emphasis* as Pi does, renders a line underlined with === or…

  • Source: PiG's own defect
  • Symptom: Markdown now nests a **strong** run inside *emphasis* as Pi does, renders a line underlined with === or --- as a heading, and leaves a blank row between a paragraph and a blockquote, heading, rule or math block that directly follows it.
  • Root cause: Markdown renders a paragraph underlined with === or --- as a heading, follows marked's setext and emphasis delimiter rules, nests a closed strong run inside emphasis, and spaces a paragraph from the block that interrupts it, as marked and markdown.ts do. Expected bytes come from the pinned pi-tui sources run under Node.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: cf7ad2b7a4 (2026-10-06), d90c2f41b7 (2026-10-08)
  • Tests: internal/codingagent/mermaid_interactive_test.go::TestAssistantThinkingUsesThinkingTransformContext, internal/codingagent/mermaid_interactive_test.go::TestInteractiveUserMessageRunsBuiltinMarkdownTransform, internal/codingagent/mermaid_strip_test.go::TestRuntimeStripMermaidLeavesFenceRaw, internal/codingagent/mermaid_strip_test.go::TestStripMermaidBuildOmitsMermaidRenderer, internal/wordsegmenter/strip_test.go::TestRuntimeStripWordDictionariesKeepsRunsWhole, internal/wordsegmenter/strip_test.go::TestStripWordDictionariesBuildOmitsDictionaryEmbeds, tui/components_test.go::TestEditor_WordBoundaryHelpers, tui/highlight_strip_test.go::TestRuntimeStripSyntaxHighlightRendersUnknownLanguagePath and 5 more in docs/bug-ledger.json
  • Release note: Markdown now nests a **strong** run inside *emphasis* as Pi does, renders a line underlined with === or --- as a heading, and leaves a blank row between a paragraph and a blockquote, heading, rule or math block that directly follows it.

BF-d983b74

Anthropic requests whose strict prefer tools exceed a strict tool limit ("The compiled grammar is too…

  • Source: PiG's own defect
  • Symptom: Anthropic requests whose strict prefer tools exceed a strict tool limit ("The compiled grammar is too large", "Too many strict tools", too many union parameters) no longer fail every turn: PiG retries once with the prefer tools non-strict, keeps require tools strict, and records an anthropic_strict_tools_fallback diagnostic, as Pi 1.2.0 does (pi-mono #10409).
  • Root cause: anthropic-messages.ts sends prefer tools with strict: true and has no fallback for 400 'compiled grammar is too large'. Retry once non-strict unless a tool requires strict.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: d983b74821 (2026-10-09)
  • Tests: None recorded.
  • Release note: Anthropic requests whose strict prefer tools exceed a strict tool limit ("The compiled grammar is too large", "Too many strict tools", too many union parameters) no longer fail every turn: PiG retries once with the prefer tools non-strict, keeps require tools strict, and records an anthropic_strict_tools_fallback diagnostic, as Pi 1.2.0 does (pi-mono #10409).

BF-d9886fe

fix(hljs): no magic-literal hits for the resume and dispatch bounds

  • Source: PiG's own defect
  • Symptom: fix(hljs): no magic-literal hits for the resume and dispatch bounds
  • Root cause: maxCheckpointIterations derives from highlightJSIterationLimit (upstream: highlight.min.js iterations; the thrown-error check uses it too). The dispatch mask bound is the bits of its uint64. The resume margin is reach.lines+1, derived instead of a slack constant: an attempt that started on or before the point's line and crossed at most reach.lines line breaks ends before the text does when reach.lines+1 breaks follow the point.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: d9886fe4fa (2026-10-11)
  • Tests: tui/internal/hljs/resume_test.go::TestResumeLimit

BF-da5d32b

fix(extension): theme palette reports the theme's own sourcePath; conformance rows for the rest of…

  • Source: PiG's own defect
  • Symptom: fix(extension): theme palette reports the theme's own sourcePath; conformance rows for the rest of ctx.ui.theme
  • Root cause: ExtensionThemePalette took sourcePath only from the active registry's record for the theme's name, so a Theme object no registry holds (ctx.ui.setTheme takes one) reported none, in every SDK. Theme.SourcePath is the field Pi's createTheme sets, so it wins; the registry record stays as the fallback. 30 matrix cells closed.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: da5d32b050 (2026-10-10)
  • Tests: internal/codingagent/ext_ui_context_test.go::TestExtensionThemePaletteReportsTheThemesOwnSourcePath, test/extension-conformance/theme_test.go::TestThemeQueriesAcrossSDKs

BF-da8a870

Pasting a ChatGPT sign-in callback URL with a ws:, wss: or ftp: scheme, a backslash path or an unusual…

  • Source: PiG's own defect
  • Symptom: Pasting a ChatGPT sign-in callback URL with a ws:, wss: or ftp: scheme, a backslash path or an unusual host gives Pi's message: "Paste the full callback URL from the browser" only where new URL rejects the text, and "The pasted callback URL must start with ..." otherwise.
  • Root cause: authorizationResultFromManualInput, authorizationResultFromCallback and REDIRECT_URI are cut out of Pi's openai-chatgpt.ts (testdata/chatgpt_manual_input.mjs) and run on 4511 seeded pasted inputs (callback URLs with every shape of code/state/client_id/error, other origins, ports, schemes, relative and non-URL text, JS-whitespace padding). Pig's hybrid (ParseHTTPURL plus a net/url error classifier) differed on every ws:, wss: and ftp: paste (rejected by the first parse, then classified by Go's url.Parse): Pi says 'The pasted callback URL must start with ...' for those and Pig said 'Paste the full callback URL' whenever net/url disagreed with the WHATWG parser.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0b4e1e3376 (2026-10-09), 15a5c0b670 (2026-10-09), da8a8704c6 (2026-10-09)
  • Tests: ai/chatgpt_manual_input_oracle_test.go::TestChatGPTManualInputMatchesPiOnSeededProbes, ai/codex_vertex_urls_oracle_test.go::TestCodexWebSocketAndVertexVersionURLsMatchPi, ai/trusted_url_oracle_test.go::TestTrustedVerificationURLsMatchPi, internal/nodeurl/genericurl_oracle_test.go::TestParseURLMatchesNode
  • Release note: Pasting a ChatGPT sign-in callback URL with a ws:, wss: or ftp: scheme, a backslash path or an unusual host gives Pi's message: "Paste the full callback URL from the browser" only where new URL rejects the text, and "The pasted callback URL must start with ..." otherwise.

BF-da907ca

The Go program in the SDK page now compiles

  • Source: PiG's own defect
  • Symptom: The Go program in the SDK page now compiles.
  • Root cause: Each entry has symptom, root cause, user impact, platform, fix commits, guarding tests and a source class (PiG's own defect, Pi defect fixed ahead of Pi with its divergence number, Pi issue and remove-when, or a Pi fix ported). The check fails when a fix has no entry, a fix-titled commit that changes shipped code is neither an entry nor listed in exempt.txt, an entry names a missing test, or the committed ledger is stale. Output: docs/bug-ledger.json and docs/site/docs/bug-ledger.md. D110 and D111 gain a "Pi bug:" line, which marks them as fixed ahead of Pi.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): da907cab0b (2026-10-09)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestBuildLedgerFromHistory, automation/ci/bugledger/bugledger_test.go::TestBuildNeedsTheReleaseCommit, automation/ci/bugledger/bugledger_test.go::TestExplicitFixMarkerNamesTheFixCommit, automation/ci/bugledger/bugledger_test.go::TestFixedBulletsReadOnlyTheFixedSectionOfLaterReleases, automation/ci/bugledger/bugledger_test.go::TestImpactAndPlatformComeFromTheWording, automation/ci/bugledger/bugledger_test.go::TestPublicTextDropsPrivateSentencesVersionsAndHashes, automation/ci/bugledger/bugledger_test.go::TestReleaseNotesDraftComesFromTheLedgers, automation/ci/bugledger/bugledger_test.go::TestRootCausePrefersALabelThenCauseWordsThenTheFirstParagraph and 39 more in docs/bug-ledger.json
  • Release note: The Go program in the SDK page now compiles. It called coding.NewServices and coding.ServicesOptions, which do not exist, and the page named coding.Services for coding.AgentSessionServices. The Go quickstart extension in the extension API page now compiles as well: its tool schema is an sdk.Schema map and its session_start handler returns (any, error).

BF-da907ca-2

The bundled command page (pig docs show commands) and the website's CLI reference now agree with `pig…

  • Source: PiG's own defect
  • Symptom: The bundled command page (pig docs show commands) and the website's CLI reference now agree with pig --help.
  • Root cause: Each entry has symptom, root cause, user impact, platform, fix commits, guarding tests and a source class (PiG's own defect, Pi defect fixed ahead of Pi with its divergence number, Pi issue and remove-when, or a Pi fix ported). The check fails when a fix has no entry, a fix-titled commit that changes shipped code is neither an entry nor listed in exempt.txt, an entry names a missing test, or the committed ledger is stale. Output: docs/bug-ledger.json and docs/site/docs/bug-ledger.md. D110 and D111 gain a "Pi bug:" line, which marks them as fixed ahead of Pi.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): da907cab0b (2026-10-09)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestBuildLedgerFromHistory, automation/ci/bugledger/bugledger_test.go::TestBuildNeedsTheReleaseCommit, automation/ci/bugledger/bugledger_test.go::TestExplicitFixMarkerNamesTheFixCommit, automation/ci/bugledger/bugledger_test.go::TestFixedBulletsReadOnlyTheFixedSectionOfLaterReleases, automation/ci/bugledger/bugledger_test.go::TestImpactAndPlatformComeFromTheWording, automation/ci/bugledger/bugledger_test.go::TestPublicTextDropsPrivateSentencesVersionsAndHashes, automation/ci/bugledger/bugledger_test.go::TestReleaseNotesDraftComesFromTheLedgers, automation/ci/bugledger/bugledger_test.go::TestRootCausePrefersALabelThenCauseWordsThenTheFirstParagraph and 39 more in docs/bug-ledger.json
  • Release note: The bundled command page (pig docs show commands) and the website's CLI reference now agree with pig --help. The bundled page lacked pig mcp, pig piglet prune, pig extensions cache prune --failures and the +name and -name forms of --tools; the website page omitted pig piglet pull, update and publish from its Piglet verbs.

BF-da907ca-3

pig docs show divergences lists D110 and D111, and the README names the pinned Pi release (1.1.0) and its…

  • Source: PiG's own defect
  • Symptom: pig docs show divergences lists D110 and D111, and the README names the pinned Pi release (1.1.0) and its commit.
  • Root cause: Each entry has symptom, root cause, user impact, platform, fix commits, guarding tests and a source class (PiG's own defect, Pi defect fixed ahead of Pi with its divergence number, Pi issue and remove-when, or a Pi fix ported). The check fails when a fix has no entry, a fix-titled commit that changes shipped code is neither an entry nor listed in exempt.txt, an entry names a missing test, or the committed ledger is stale. Output: docs/bug-ledger.json and docs/site/docs/bug-ledger.md. D110 and D111 gain a "Pi bug:" line, which marks them as fixed ahead of Pi.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): da907cab0b (2026-10-09)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestBuildLedgerFromHistory, automation/ci/bugledger/bugledger_test.go::TestBuildNeedsTheReleaseCommit, automation/ci/bugledger/bugledger_test.go::TestExplicitFixMarkerNamesTheFixCommit, automation/ci/bugledger/bugledger_test.go::TestFixedBulletsReadOnlyTheFixedSectionOfLaterReleases, automation/ci/bugledger/bugledger_test.go::TestImpactAndPlatformComeFromTheWording, automation/ci/bugledger/bugledger_test.go::TestPublicTextDropsPrivateSentencesVersionsAndHashes, automation/ci/bugledger/bugledger_test.go::TestReleaseNotesDraftComesFromTheLedgers, automation/ci/bugledger/bugledger_test.go::TestRootCausePrefersALabelThenCauseWordsThenTheFirstParagraph and 39 more in docs/bug-ledger.json
  • Release note: pig docs show divergences lists D110 and D111, and the README names the pinned Pi release (1.1.0) and its commit. The README gave the pin of two releases ago.

BF-da907ca-4

The Termux page's link to the Quickstart reaches its "Authenticate a provider" section

  • Source: PiG's own defect
  • Symptom: The Termux page's link to the Quickstart reaches its "Authenticate a provider" section.
  • Root cause: Each entry has symptom, root cause, user impact, platform, fix commits, guarding tests and a source class (PiG's own defect, Pi defect fixed ahead of Pi with its divergence number, Pi issue and remove-when, or a Pi fix ported). The check fails when a fix has no entry, a fix-titled commit that changes shipped code is neither an entry nor listed in exempt.txt, an entry names a missing test, or the committed ledger is stale. Output: docs/bug-ledger.json and docs/site/docs/bug-ledger.md. D110 and D111 gain a "Pi bug:" line, which marks them as fixed ahead of Pi.
  • User impact: Behavior differed from Pi
  • Platform: Android and Termux
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): da907cab0b (2026-10-09)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestBuildLedgerFromHistory, automation/ci/bugledger/bugledger_test.go::TestBuildNeedsTheReleaseCommit, automation/ci/bugledger/bugledger_test.go::TestExplicitFixMarkerNamesTheFixCommit, automation/ci/bugledger/bugledger_test.go::TestFixedBulletsReadOnlyTheFixedSectionOfLaterReleases, automation/ci/bugledger/bugledger_test.go::TestImpactAndPlatformComeFromTheWording, automation/ci/bugledger/bugledger_test.go::TestPublicTextDropsPrivateSentencesVersionsAndHashes, automation/ci/bugledger/bugledger_test.go::TestReleaseNotesDraftComesFromTheLedgers, automation/ci/bugledger/bugledger_test.go::TestRootCausePrefersALabelThenCauseWordsThenTheFirstParagraph and 39 more in docs/bug-ledger.json
  • Release note: The Termux page's link to the Quickstart reaches its "Authenticate a provider" section.

BF-dac3cd5

A session keeps reading its history as it loaded it when another program rewrites, truncates, replaces or…

  • Source: PiG's own defect
  • Symptom: A read of an entry behind the latest compaction failed or returned only the entry's id, parentId and type.
  • Root cause: Owner decision 5 B. Pi reads a session file into memory once (session-manager.ts setSessionFile, loadEntriesFromFile), so a change another program makes to the file afterwards reaches none of its reads. PiG keeps only the active context resident and read the rest back from the session file, so an in-place rewrite, truncation, replacement or removal made the entries behind the latest compaction unreadable (GetEntries marshalled "unexpected end of JSON input").
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), dac3cd5029 (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, internal/codingagent/session_context_settings_test.go::TestGetSessionContextSettingsBoundaries, internal/codingagent/session_lazy_test.go::TestLazyEntryOfAChangedRecordIsAnError, internal/codingagent/session_messagefor_cache_test.go::TestMessageForReportsAnUnparseableEntryWithoutParsingItAgain, internal/codingagent/session_snapshot_test.go::TestResumedSessionReadsAsLoadedAfterItsFileChanges and 4 more in docs/bug-ledger.json
  • Release note: A session keeps reading its history as it loaded it when another program rewrites, truncates, replaces or removes its session file, as Pi does. PiG reads history from a private snapshot of the session file. The snapshot is a file system clone where the file system supports one (Btrfs, XFS, APFS); elsewhere it is a copy that takes the file's size on disk. Before, a read of an entry behind the latest compaction failed or returned only the entry's id, parentId and type.

BF-dae37be

fix: the input loop never waits for the Session lock; the abort-fix leak and two stale tests

  • Source: PiG's own defect
  • Symptom: fix: the input loop never waits for the Session lock; the abort-fix leak and two stale tests
  • Root cause: TestBeginPromptCompactionCheckDoesNotWaitForTheSessionLock (red with Lock). - abortAfterStartProvider (TestNavigateToUserMessageAfterAbortLoadsEditor) never ended its stream and relied on the pre-cancelled request failing in ModelRuntime.start; since an unknown caller-owned provider receives the aborted signal like Pi's compat path, so the fixture reports an aborted message on a cancelled ctx as faux.ts does. ForwardStream drains the provider by design (lazy.ts forwardStream), the provider owns cancellation. - TestBuildModelContextCancelsOAuthRefresh expects the signal's reason (This operation was aborted) the refresh rejects with since. - test_deadline_budget_test.go: delete a stale…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: dae37be433 (2026-10-10)
  • Tests: coding/cli/model_oauth_refresh_test.go::TestBuildModelContextCancelsOAuthRefresh, coding/session_prompt_compaction_check_test.go::TestBeginPromptCompactionCheckDoesNotWaitForTheSessionLock, coding/session_prompt_compaction_check_test.go::TestBeginPromptCompactionCheckReturnsOnlyADueCompaction, coding/session_prompt_compaction_check_test.go::TestPromptCompactionCheckReplaysAnOverflowedResponse, coding/tree_edit_after_abort_test.go::TestNavigateToUserMessageAfterAbortLoadsEditor

BF-db12780

fix(agent): end the tool flow at custom user messages and hold system messages mid-flow

  • Source: PiG's own defect
  • Symptom: fix(agent): end the tool flow at custom user messages and hold system messages mid-flow
  • Root cause: Pi converts messages to LLM form and then runs transformMessages (ai/src/api/transform-messages.ts), where a branchSummary, compactionSummary, bashExecution or custom message is already a user message and closes the pending tool calls (closePendingToolCalls). Pig normalizes AgentMessages before convertToLLM, so those custom messages did not interrupt the flow and the placeholders landed behind them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: db12780931 (2026-10-09)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, coding/session_tree_wire_oracle_test.go::TestNavigateTreeEveryPointSendsValidToolFlow

BF-db1ee65

A lone UTF-16 surrogate in a tool result's details, in a nested tool call's result, or in a code mode call's…

  • Source: PiG's own defect
  • Symptom: It became three U+FFFD characters (encoding/json has no lone-surrogate string), so the preview bytes in a stored result differed from Pi's.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: db1ee65be2 (2026-10-24)
  • Tests: ai/lone_surrogate_json_pi_oracle_test.go::TestMessagesKeepALoneSurrogateAsPiDoes, coding/cli/lone_surrogate_json_lines_pi_oracle_test.go::TestJSONLinesOfAMessageWithALoneSurrogateAreWhatPisJSONStringifyWrites, durable/codemode/lone_surrogate_preview_test.go::TestAPreviewCutInsideASurrogatePairKeepsItsEscapeLikePi, durable/codemode/lone_surrogate_tool_result_test.go::TestAToolResultWithALoneSurrogateKeepsItThroughTheHarnessAndTheEntry, durable/json_lone_surrogate_test.go::TestToJsonValueAndFromJsonValueKeepALoneSurrogate, internal/codingagent/lone_surrogate_session_pi_oracle_test.go::TestSessionLinesWithALoneSurrogateAreWhatPisJSONStringifyWrites
  • Release note: A lone UTF-16 surrogate in a tool result's details, in a nested tool call's result, or in a code mode call's argument or error preview now stays a lone surrogate through the Durable entry and its stored values, written as the \ud83d escape that Pi's JSON.stringify writes, with <, > and & left as they are. Before, it became three U+FFFD characters (encoding/json has no lone-surrogate string), so the preview bytes in a stored result differed from Pi's. The session file, JSON mode and RPC mode already wrote the escape and are unchanged.

BF-db7007d

A Radius gateway model's samplingParamsByThinkingLevel now reaches the pi-messages model and the composed…

  • Source: PiG's own defect
  • Symptom: A Radius gateway model's samplingParamsByThinkingLevel now reaches the pi-messages model and the composed catalog entry (Pi's getRadiusModelsFromConfig spreads each gateway model); it was dropped before.
  • Root cause:
    • ai/radius_config.go: RadiusGatewayModel.SamplingParamsByThinkingLevel (radius-config.ts:37-45,61-68 spread each gateway model; types.ts:1133), cloned per level; internal/codingagent/radius_models.go radiusModelEntry carries it to the composed ModelEntry that simple-options.ts:30 reads. Closes the getRadiusModelsFromConfig result row. Tests: ai TestGetRadiusModelsFromConfigCarriesSamplingParamsByThinkingLevel, codingagent TestRadiusCatalogEntryCarriesSamplingParamsByThinkingLevel; three mutants (drop the field in radiusModelEntry, share the per-level map, rename the json tag) fail them. - durable/session documentState helper returns durable.DocumentState (types.ts:842,941-951)…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 339445102a (2026-10-06), 77bfe68d1f (2026-10-07), d5d010a275 (2026-10-07), db7007dad8 (2026-10-07)
  • Tests: agent/thinking_level_test.go::TestAgentLoop_RequestsReasoningOnlyForLevelsOtherThanOff, ai/constrained_sampling_upstream_test.go::TestConstrainedSamplingConversionUpstream, ai/openai_responses_foreign_toolcall_test.go::TestResponsesConvertMessages_ForeignCopilotToolCallIDHashedToFcShape, ai/openai_responses_foreign_toolcall_test.go::TestResponsesToolCallID_UnattributedItemIsHashed, ai/openai_responses_shared_oracle_test.go::TestConvertResponsesMessagesMatchesPi, ai/openai_responses_test.go::TestNormalizeResponsesToolCallIDProviderSets, ai/openai_responses_tool_result_output_test.go::TestResponsesConvertMessages_EmptyToolResultUsesPlaceholder, ai/radius_sampling_by_level_test.go::TestGetRadiusModelsFromConfigCarriesSamplingParamsByThinkingLevel and 6 more in docs/bug-ledger.json
  • Release note: A Radius gateway model's samplingParamsByThinkingLevel now reaches the pi-messages model and the composed catalog entry (Pi's getRadiusModelsFromConfig spreads each gateway model); it was dropped before.

BF-db9a315

Codemode peak memory is now measured and guarded through the normal tool path in a fresh process with 40…

  • Source: PiG's own defect
  • Symptom: Codemode peak memory is now measured and guarded through the normal tool path in a fresh process with 40 extension tools exposed.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): db9a31538f (2026-10-14)
  • Tests: coding/extension/builtin/codemode/baseline_rss_test.go::TestPeakRSSOfCodemodeScenarios, coding/extension/builtin/codemode/baseline_rss_test.go::TestRSSScenarioChild
  • Release note: Codemode peak memory is now measured and guarded through the normal tool path in a fresh process with 40 extension tools exposed. Peak RSS is about 62-67 MiB cold (no compilation cache) for a no-op with or without extension tools, about 36 MiB with the compilation cache, about the same for a script passing a 96-record, 18 KB state to a tool, and 41 MiB for the two concurrent scripts the worker limit allows; before the on-demand memory fix a concurrent run reached about 355 MiB. Reported by @kayis-rahman (#211)

BF-dbe1ad3

pig setup now flags a go on PATH older than Go 1.26, the extension SDK minimum, naming the version found…

  • Source: PiG's own defect
  • Symptom: pig setup now flags a go on PATH older than Go 1.26, the extension SDK minimum, naming the version found and the fix, and extension builds use the toolchain pig setup go installed instead of that older go (pig additive, D20).
  • Root cause: pig setup listed a go on PATH older than the extension SDK's Go 1.26 floor without a warning, and after pig setup go PiG still built with that older go, so builds failed under GOTOOLCHAIN=local. pig setup now marks it too old and names the fix, and PiG builds with the toolchain pig setup go installed when the PATH go is below the floor (D20).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: dbe1ad3c2c (2026-10-13)
  • Tests: coding/cli/setup_go_floor_test.go::TestSetupStatusFlagsAPathGoBelowTheSDKFloor, internal/toolchain/resolve_test.go::TestResolveGoKeepsAPathGoMeetingTheFloor, internal/toolchain/resolve_test.go::TestResolveGoPrefersManagedGoOverAPathGoBelowTheFloor, internal/toolchain/toolchain_test.go::TestMinGoVersionIsTheSDKGoDirective
  • Release note: pig setup now flags a go on PATH older than Go 1.26, the extension SDK minimum, naming the version found and the fix, and extension builds use the toolchain pig setup go installed instead of that older go (pig additive, D20).

BF-dbe25ef

CI: make interface-gaps passes on main again

  • Source: PiG's own defect
  • Symptom: The interface mapping ledger had not been regenerated for the chord fork replay test that landed with the previous train, so autobind -check reported that the ledger is not what the detector derives; make interface-gaps-update rewrites the two stale rows.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: CI: make interface-gaps passes on main again. The interface mapping ledger had not been regenerated for the chord fork replay test that landed with the previous train, so autobind -check reported that the ledger is not what the detector derives; make interface-gaps-update rewrites the two stale rows.

BF-dbe25ef-10

The bug fix ledger no longer credits a squash commit's tests to a release note that names no fix commit

  • Source: PiG's own defect
  • Symptom: The bug fix ledger no longer credits a squash commit's tests to a release note that names no fix commit.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: The bug fix ledger no longer credits a squash commit's tests to a release note that names no fix commit. A release note without a <!-- fix: <sha> --> marker had its commits guessed from the history, and the only commit it found for five notes was the 0.5.0 squash, which grew docs/bug-ledger.json from about 2.1 MB to 9.8 MB and pushed the root module past its size budget. make changelog-check now fails a ### Fixed bullet without a marker, a marker or fragment name that names a commit HEAD does not reach, and make bug-ledger refuses, naming the entry, to attribute the tests of a commit that touches more than 500 files.

BF-dbe25ef-11

Two calls of a Go, Python or Rust extension tool in one assistant message that each open a dialog now behave…

  • Source: PiG's own defect
  • Symptom: Two calls of a Go, Python or Rust extension tool in one assistant message that each open a dialog now behave as Pi's one thread makes them behave, as Node extensions already did: the dialogs open in call order (a port's second dialog could open first), and when the user answers them back to back the calls respond, and end, in the order of the answers.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: Two calls of a Go, Python or Rust extension tool in one assistant message that each open a dialog now behave as Pi's one thread makes them behave, as Node extensions already did: the dialogs open in call order (a port's second dialog could open first), and when the user answers them back to back the calls respond, and end, in the order of the answers. The SDKs keep these orders for the calls of one parallel batch, which the host now names on each tool_call request (batch); calls of other batches, other sessions' calls and tools that make no host call take no part and are never held. A handler that blocks on something other than a host call before its first host call holds the first dialogs of the later calls of its batch, while a call that awaits a host call such as exec before its dialog is overtaken by the later calls' dialogs, as in Pi, and a handler whose dialog was answered holds the later-answered handlers' responses until it returns or makes its next host call (recorded as divergence D187: Pi's single thread lets a call that awaits work the SDK cannot see be overtaken).

BF-dbe25ef-12

pig extension upgrade finds and rewrites every changed API of an extension in one run

  • Source: PiG's own defect
  • Symptom: pig extension upgrade finds and rewrites every changed API of an extension in one run.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: pig extension upgrade finds and rewrites every changed API of an extension in one run. Startup offers each Go extension the upgrade would touch, not only those whose first ten compile errors all name an SDK change: a len(ctx.GetAllTools()) or panic(ctx.GetSessionID()) (a builtin that takes one value) is now recognised as the getter's change, and a recorded failure with no classification is read again by type checking. A Context.ConfigHome in a function that cannot return the error is rewritten to report the error through the call's own Context (ctx.Notify) and return, never to discard it; a getter that cannot move before its statement (a && operand, an else if, a loop condition, a call after another call) takes a generated helper in pig_upgrade_getters.go that returns the old getter's zero value on failure.

BF-dbe25ef-13

pig extension upgrade applies the rewrites that have one safe form and lists the rest with file, line and…

  • Source: PiG's own defect
  • Symptom: pig extension upgrade applies the rewrites that have one safe form and lists the rest with file, line and the form to write, instead of restoring every file when one use has no mechanical form.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: pig extension upgrade applies the rewrites that have one safe form and lists the rest with file, line and the form to write, instead of restoring every file when one use has no mechanical form. It exits 1 and reports partly upgraded when uses are left. --dry-run compiles the rewritten extension in a temporary directory (the go command's overlay, with the staged SDK) and reports would upgrade only when it builds; a rewrite that would add a compile error of its own is not written. The load error of a drifted extension names the changed APIs and the exact command. The stale-copy warning also recognises an edited copy by its Go module path.

BF-dbe25ef-14

The Node extension runtime's ctx.modelRegistry.runtime is now a model runtime that a nested Session can…

  • Source: PiG's own defect
  • Symptom: The Node extension runtime's ctx.modelRegistry.runtime is now a model runtime that a nested Session can share.
  • Root cause: Not recorded.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: The Node extension runtime's ctx.modelRegistry.runtime is now a model runtime that a nested Session can share. An extension that passes it to createAgentSession({ modelRuntime }), as the pi-goal-x completion auditor does, failed with this._modelRuntime.hasConfiguredAuth is not a function. The nested Session now uses the parent's providers and credentials, as in Pi.

BF-dbe25ef-15

Starting a child host could wait out its 30-second ready timeout ("child not ready in time") although the…

  • Source: PiG's own defect
  • Symptom: Starting a child host could wait out its 30-second ready timeout ("child not ready in time") although the child was up.
  • Root cause: Node cannot lose them, because an owner registers in the tick that creates the channel.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: Starting a child host could wait out its 30-second ready timeout ("child not ready in time") although the child was up. The Go port of Node's IPC channel read messages in its own goroutine and dropped any that arrived before its owner had registered a listener. Node cannot lose them, because an owner registers in the tick that creates the channel. A child that opened its channel after the parent's init message had been written could miss the init (so it never answered), and a manager descheduled between spawning the child and registering its control listener could miss the child's ready. The channel now keeps the messages received before its first listener and replays them to it in order, and a forked child instance does the same for its control listeners.

BF-dbe25ef-16

Fixed the chord host's Close waiting forever when a hold that a link was still delivering created an object…

  • Source: PiG's own defect
  • Symptom: Fixed the chord host's Close waiting forever when a hold that a link was still delivering created an object after Close had taken its snapshot of the host's objects: that object's resource open kept waiting for an abort that nothing raised.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: Fixed the chord host's Close waiting forever when a hold that a link was still delivering created an object after Close had taken its snapshot of the host's objects: that object's resource open kept waiting for an abort that nothing raised. Close now disposes what is left once the links are closed.

BF-dbe25ef-17

pig update --extensions no longer fails when two Git Packages come from one repository

  • Source: PiG's own defect
  • Symptom: pig update --extensions no longer fails when two Git Packages come from one repository.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: pig update --extensions no longer fails when two Git Packages come from one repository. Packages of one repository share a managed checkout, and their updates ran at once: both fetched into one FETCH_HEAD and both ran git reset --hard, which failed with Unable to create '.../.git/index.lock': File exists and git reset --hard FETCH_HEAD^{commit} failed with code 128. The packages of one checkout now update one at a time, in the order of the settings file, and the last one decides the commit the checkout is left on. Packages from other repositories still update at the same time, and a package that fails does not stop the others.

BF-dbe25ef-18

A Go, Python or Rust extension tool that awaits a host call such as exec before its first dialog no longer…

  • Source: PiG's own defect
  • Symptom: A Go, Python or Rust extension tool that awaits a host call such as exec before its first dialog no longer holds the first dialogs of the later calls of its parallel batch: the SDK passes the call's dialog place at its first host call of any kind, so the later calls' dialogs open while the exec runs and the call's own dialog opens after it, as Pi's single thread does (D187).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: A Go, Python or Rust extension tool that awaits a host call such as exec before its first dialog no longer holds the first dialogs of the later calls of its parallel batch: the SDK passes the call's dialog place at its first host call of any kind, so the later calls' dialogs open while the exec runs and the call's own dialog opens after it, as Pi's single thread does (D187). A handler that blocks on something other than a host call before its first host call still holds them.

BF-dbe25ef-19

Two calls of an extension tool in one assistant message now end in the order their tools answer, as Pi ends…

  • Source: PiG's own defect
  • Symptom: Two calls of an extension tool in one assistant message now end in the order their tools answer, as Pi ends them.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: Two calls of an extension tool in one assistant message now end in the order their tools answer, as Pi ends them. In Pi, two tools that each wait on a dialog (or on timers of equal length) continue in the order their answers arrive, so tool_execution_end for the call whose dialog was answered first comes first. PiG woke the goroutines that waited for the two answers in no order, and on most runs the second call ended first, although its dialog was answered second. The host now records the order where the answers are read (a tool_call response from the extension's process, an RPC extension_ui_response line) and keeps it where the calls continue: the batch's tool_execution_end events, and the replies sent to the extension for its dialog calls. Tool-result messages still follow call order.

BF-dbe25ef-2

CI: the Windows test jobs no longer fail the pig extension upgrade notice tests when the temporary…

  • Source: PiG's own defect
  • Symptom: CI: the Windows test jobs no longer fail the pig extension upgrade notice tests when the temporary directory holds a ~, as a runner's 8.3 short name (RUNNER~1) does.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: Windows
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: CI: the Windows test jobs no longer fail the pig extension upgrade notice tests when the temporary directory holds a ~, as a runner's 8.3 short name (RUNNER~1) does. The notice already quotes such a path for the Windows shell ("C:\Users\RUNNER~1\...") and is unchanged; the tests expected the raw path and now expect the quoted word, as the notice test beside them does.

BF-dbe25ef-20

A compaction, branch summary or bug-report summary request is now sent through the session's own stream…

  • Source: PiG's own defect
  • Symptom: A session whose stream function was the stock one sent these requests straight to the model's provider, so the host never ran before_provider_headers for them.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: A compaction, branch summary or bug-report summary request is now sent through the session's own stream function, as Pi sends it. Before, a session whose stream function was the stock one sent these requests straight to the model's provider, so the host never ran before_provider_headers for them. /compact, automatic and overflow-recovery compaction, /tree summaries and bug-report summaries now emit before_provider_headers, in every mode. They still do not emit before_provider_request, after_provider_response or provider_stream_event: Pi's summary requests carry none of the Agent's payload, response and stream-event callbacks, and neither do PiG's now (agent requests are unchanged and emit all four). Agent.EffectiveStreamFunction is new: the stream function a run sends through, with the default stream in place of the nil StreamFunction returns for it. Reported by a user.

BF-dbe25ef-21

Fixed pig --mode rpc exiting 143 instead of 0 when stdin ended while a termination signal's shutdown was…

  • Source: PiG's own defect
  • Symptom: Fixed pig --mode rpc exiting 143 instead of 0 when stdin ended while a termination signal's shutdown was still running: the first exit of the shutdown now decides the exit status, as Pi's first process.exit does.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: Fixed pig --mode rpc exiting 143 instead of 0 when stdin ended while a termination signal's shutdown was still running: the first exit of the shutdown now decides the exit status, as Pi's first process.exit does.

BF-dbe25ef-3

pig extension upgrade: the command a startup notice or the upgrade offer prints now quotes a POSIX path…

  • Source: PiG's own defect
  • Symptom: The backslash printed bare and the shell read it as an escape, so the command named a different path.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: pig extension upgrade: the command a startup notice or the upgrade offer prints now quotes a POSIX path that holds a backslash. Before, the backslash printed bare and the shell read it as an escape, so the command named a different path. A backslash stays plain in a Windows path, where it separates elements.

BF-dbe25ef-4

RPC mode no longer runs ctx.ui.setWidget component factories or emits setWidget requests carrying their…

  • Source: PiG's own defect
  • Symptom: RPC mode no longer runs ctx.ui.setWidget component factories or emits setWidget requests carrying their rendered lines.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: RPC mode no longer runs ctx.ui.setWidget component factories or emits setWidget requests carrying their rendered lines. Like Pi's rpc-mode.ts, it forwards string arrays and clears only; print and JSON modes ignore a factory too.

BF-dbe25ef-5

pig piglet verify <binary> --source <name|path> and pig piglet lineage <binary> --source <name|path>…

  • Source: PiG's own defect
  • Symptom: pig piglet verify <binary> --source <name|path> and pig piglet lineage <binary> --source <name|path> report Provenance: matches for a Binary built from that unchanged source.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: pig piglet verify <binary> --source <name|path> and pig piglet lineage <binary> --source <name|path> report Provenance: matches for a Binary built from that unchanged source. They compared the Piglet digest the Binary signs, which is the digest of its baked Piglet definition, with the extends graph digest, so every check reported MISMATCH. They now bake the source as the build does. A change to a file the build inlines, such as a system prompt file, is now a Piglet digest mismatch. The JSON provenance object's effectiveDigest field is renamed pigletDigest. Binaries built by 0.5.0 and 0.5.1 need no rebuild.

BF-dbe25ef-6

pig piglet verify accepts --workspace <dir>, and pig piglet lineage --workspace now applies to the…

  • Source: PiG's own defect
  • Symptom: pig piglet verify accepts --workspace <dir>, and pig piglet lineage --workspace now applies to the provenance check too.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: pig piglet verify accepts --workspace <dir>, and pig piglet lineage --workspace now applies to the provenance check too. A Piglet whose agentEnv.devContainer is a workspace: path is checked against the workspace its build used instead of failing to resolve from another directory.

BF-dbe25ef-7

A run that an extension's sendMessage with triggerTurn starts now carries the session's system prompt

  • Source: PiG's own defect
  • Symptom: A run that an extension's sendMessage with triggerTurn starts now carries the session's system prompt.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: A run that an extension's sendMessage with triggerTurn starts now carries the session's system prompt. The first run of a session reached the model with no system prompt, as did any later run that a custom message started after the instructions changed. Pi records the run's prompt sections for a custom starting message as for a prompt.

BF-dbe25ef-8

go install github.com/MichaelKinsy/PiG/cmd/pig@main builds again

  • Source: PiG's own defect
  • Symptom: go install github.com/MichaelKinsy/PiG/cmd/pig@main builds again.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: go install github.com/MichaelKinsy/PiG/cmd/pig@main builds again. The root go.mod required extensions/sdk v0.5.0, which lacks Result.Drift, FileChange.Created and Skipped.Suggest that pig extension upgrade uses, so building the module outside the workspace failed, and make module-size failed on every pull request. Between releases the root go.mod (and piglets/standard and piglets/aot) now require the SDK at the pseudo-version of the main commit that holds the API the root uses; the release replaces it with the tag. make module-publication accepts such a pseudo-version, and proves by downloading it into an empty cache, with the committed checksums, that the commit is public.

BF-dbe25ef-9

A tool that an extension activates with setActiveTools now appears in the system prompt's tool list of the…

  • Source: PiG's own defect
  • Symptom: A tool that an extension activates with setActiveTools now appears in the system prompt's tool list of the next run that a triggerTurn message starts.
  • Root cause: The request already declared the tool, but the prompt left its snippet out, so the model was not told about it.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: dbe25ef5c1 (2026-10-21, #261)
  • Tests: automation/ci/bugledger/bugledger_test.go::TestAFixTitledSquashWithNoNoteIsRefusedNotAttributed, automation/ci/bugledger/bugledger_test.go::TestTheLedgerRefusesToAttributeTheTestsOfASquash, automation/ci/bugledger/bugledger_test.go::TestTheSquashThresholdIsTheCountOfTouchedFiles, automation/ci/changelogfrag/history_test.go::TestAFragmentNamedAfterAPreRebaseCommitFails, automation/ci/changelogfrag/history_test.go::TestAMarkerNamingAnUnreachableCommitFails, automation/ci/changelogfrag/history_test.go::TestAShallowCloneFailsTheCheckInsteadOfGuessing, automation/ci/changelogfrag/history_test.go::TestEveryCommitOfAMarkerMustBeReachable, automation/ci/changelogfrag/history_test.go::TestEveryFixedBulletNeedsAFixMarker and 34 more in docs/bug-ledger.json
  • Release note: A tool that an extension activates with setActiveTools now appears in the system prompt's tool list of the next run that a triggerTurn message starts. The request already declared the tool, but the prompt left its snippet out, so the model was not told about it.

BF-dc53412

A pig_strip_bedrock_converse_stream Piglet Binary no longer links the AWS SDK through the Bedrock retry…

  • Source: PiG's own defect
  • Symptom: A pig_strip_bedrock_converse_stream Piglet Binary no longer links the AWS SDK through the Bedrock retry delay.
  • Root cause: bedrock_retry_backoff.go (, the @smithy retry delay) imports the AWS SDK and smithy but had no build tag, so a Piglet Binary built with pig_strip_bedrock_converse_stream linked 27 github.com/aws/ packages again. Its only caller is bedrock.go, which the tag removes.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: dc53412cc2 (2026-10-11)
  • Tests: coding/cli/strip_providers_binary_test.go::TestStripBedrockConverseStreamBuildOmitsAWSSDK
  • Release note: A pig_strip_bedrock_converse_stream Piglet Binary no longer links the AWS SDK through the Bedrock retry delay.

BF-dca5763

A notification, status or message that an extension queues on setImmediate or setTimeout(0) from inside a…

  • Source: PiG's own defect
  • Symptom: A notification, status or message that an extension queues on setImmediate or setTimeout(0) from inside a tool now reaches an RPC client after agent_settled, as Pi's loop does, instead of before tool_execution_end.
  • Root cause: The Node runtime ran them as soon as the tool answered, because it is another process, and its host calls arrived mid-run.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: 38901751db (2026-10-21), dca5763ac5 (2026-10-21)
  • Tests: coding/extension/host/subprocess/callpayload_shape_test.go::TestCallPayloadKeepsUnkeyedLiteralShape
  • Release note: A notification, status or message that an extension queues on setImmediate or setTimeout(0) from inside a tool now reaches an RPC client after agent_settled, as Pi's loop does, instead of before tool_execution_end. Pi runs those callbacks when its event loop turns, which a run's chain of microtasks never gives it. The Node runtime ran them as soon as the tool answered, because it is another process, and its host calls arrived mid-run. The host now holds such calls while the RPC run chains microtasks and runs them in arrival order at the next real wait: a model response's I/O, a built-in tool's run, a dialog, a compaction or an auto-retry delay, or the end of agent_settled. A process.nextTick callback's notification follows the tool_execution_end of the tool that queued it, as in Pi, and the callbacks of a tool that returns while a sibling tool of its batch still runs reach the client right after its tool_execution_end.

BF-ddd4772

fix(ai): a lone surrogate in a content block stays one code unit, as in JSON.parse

  • Source: PiG's own defect
  • Symptom: fix(ai): a lone surrogate in a content block stays one code unit, as in JSON.parse
  • Root cause: UnmarshalContentBlock and ThinkingContent.UnmarshalJSON decoded with encoding/json, which turns a lone UTF-16 unit into U+FFFD, while the text shorthand of a tool result and the rest of the wire (extensions/sdk/json) keep it as the escape on the wire and WTF-8 in a Go string. A Node or Python tool that returned the block form lost the character; the shorthand kept it. Both forms, and every block a session line carries, now keep it. Also rows for unicode, NUL, U+2028, a 180 KB argument, and Scenario.Unrepresentable for a language limit (Rust strings cannot hold a lone surrogate). Tests: TestUnmarshalContentBlockKeepsALoneSurrogate, TestToolResultKeepsALoneSurrogateInEveryContentForm.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: ddd4772e07 (2026-10-10)
  • Tests: ai/content_block_test.go::TestUnmarshalContentBlockKeepsALoneSurrogate, coding/extension/host/subprocess/tool_result_member_order_test.go::TestToolResultKeepsALoneSurrogateInEveryContentForm, test/extension-conformance/sdkscenario_test.go::TestSDKScenarios

BF-dde6aa6

Cache warming no longer refreshes at once when a model's promptCache lifetime is far larger than the one-hour…

  • Source: PiG's own defect
  • Symptom: Cache warming no longer refreshes at once when a model's promptCache lifetime is far larger than the one-hour safety limit.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: Cache warming no longer refreshes at once when a model's promptCache lifetime is far larger than the one-hour safety limit. The delay is computed in floating point as upstream does, so the run stops with "one-hour safety limit reached" instead of overflowing to a one-millisecond refresh.

BF-dde6aa6-10

On Windows, path normalization and joining follow Node 24's reserved device name handling: a path such as…

  • Source: PiG's own defect
  • Symptom: On Windows, path normalization and joining follow Node 24's reserved device name handling: a path such as CON: or \\?\COM1: keeps its device, path.join leaves a part naming one unnormalized, and a \\?\name or \\.\name device path no longer gains a trailing backslash.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: On Windows, path normalization and joining follow Node 24's reserved device name handling: a path such as CON: or \\?\COM1: keeps its device, path.join leaves a part naming one unnormalized, and a \\?\name or \\.\name device path no longer gains a trailing backslash.

BF-dde6aa6-11

OpenDirReader on a FIFO now fails with not_directory at once instead of waiting for a writer

  • Source: PiG's own defect
  • Symptom: OpenDirReader on a FIFO now fails with not_directory at once instead of waiting for a writer.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: OpenDirReader on a FIFO now fails with not_directory at once instead of waiting for a writer.

BF-dde6aa6-12

Fixed the queued steering and follow-up lines, the dequeue hint and the startup hint separator to use Pi's…

  • Source: PiG's own defect
  • Symptom: Fixed the queued steering and follow-up lines, the dequeue hint and the startup hint separator to use Pi's colours: the pending lines use the dim theme colour (not muted) and both close only the foreground colour (ESC[39m) instead of resetting every attribute.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: Fixed the queued steering and follow-up lines, the dequeue hint and the startup hint separator to use Pi's colours: the pending lines use the dim theme colour (not muted) and both close only the foreground colour (ESC[39m) instead of resetting every attribute.

BF-dde6aa6-13

A session entry timestamp that is not RFC 3339 but that JavaScript Date reads (a space for the T, an RFC 2822…

  • Source: PiG's own defect
  • Symptom: A session entry timestamp that is not RFC 3339 but that JavaScript Date reads (a space for the T, an RFC 2822 date) is read for compaction staleness, summary message times and cache-warm entries, and an unreadable compaction timestamp no longer marks every assistant message as older than the compaction.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: A session entry timestamp that is not RFC 3339 but that JavaScript Date reads (a space for the T, an RFC 2822 date) is read for compaction staleness, summary message times and cache-warm entries, and an unreadable compaction timestamp no longer marks every assistant message as older than the compaction.

BF-dde6aa6-14

Fixed the session selector's selected row after a down or page-down key over an empty list: as in Pi the…

  • Source: PiG's own defect
  • Symptom: Fixed the session selector's selected row after a down or page-down key over an empty list: as in Pi the index becomes -1 and stays out of range after the scope or search changes (no row is highlighted and the position reads (0/N)) instead of snapping to the first row, and enter, delete and rename no longer act on a row that is not selected.
  • Root cause: Not recorded.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: Fixed the session selector's selected row after a down or page-down key over an empty list: as in Pi the index becomes -1 and stays out of range after the scope or search changes (no row is highlighted and the position reads (0/N)) instead of snapping to the first row, and enter, delete and rename no longer act on a row that is not selected. Reloading the list in that state no longer panics.

BF-dde6aa6-15

Fixed a failing tool call's result always opening fully expanded: like Pi, an error result collapses to the…

  • Source: PiG's own defect
  • Symptom: Fixed a failing tool call's result always opening fully expanded: like Pi, an error result collapses to the renderer's preview (a failing bash command shows its last five lines and the expand hint) until the tools are expanded with Ctrl+O or a click.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: Fixed a failing tool call's result always opening fully expanded: like Pi, an error result collapses to the renderer's preview (a failing bash command shows its last five lines and the expand hint) until the tools are expanded with Ctrl+O or a click.

BF-dde6aa6-16

Fixed the tree selector's label prompt drawing an input row one cell too wide in a terminal two columns wide…

  • Source: PiG's own defect
  • Symptom: Fixed the tree selector's label prompt drawing an input row one cell too wide in a terminal two columns wide; the input now gets Pi's width - 2 cells.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: Fixed the tree selector's label prompt drawing an input row one cell too wide in a terminal two columns wide; the input now gets Pi's width - 2 cells.

BF-dde6aa6-17

Fixed the card of a tool call for a tool that does not exist (a name the model made up): it now shows the…

  • Source: PiG's own defect
  • Symptom: Fixed the card of a tool call for a tool that does not exist (a name the model made up): it now shows the tool name, the arguments as indented JSON and the result right below, as Pi does, instead of a compact key:value summary with a blank row before the result.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: Fixed the card of a tool call for a tool that does not exist (a name the model made up): it now shows the tool name, the arguments as indented JSON and the result right below, as Pi does, instead of a compact key:value summary with a blank row before the result.

BF-dde6aa6-2

Fixed context edits replacing the content of a system message: as in Pi's projectContextEntry, only user…

  • Source: PiG's own defect
  • Symptom: Fixed context edits replacing the content of a system message: as in Pi's projectContextEntry, only user, assistant, tool-result and custom messages take the replacement content, so an edit entry that targets a system message leaves it unchanged in the projected context.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: Fixed context edits replacing the content of a system message: as in Pi's projectContextEntry, only user, assistant, tool-result and custom messages take the replacement content, so an edit entry that targets a system message leaves it unchanged in the projected context.

BF-dde6aa6-3

Fixed the edit tool's diff card for files with CRLF line endings: a diff row that ends in a carriage return…

  • Source: PiG's own defect
  • Symptom: Fixed the edit tool's diff card for files with CRLF line endings: a diff row that ends in a carriage return is now wrapped like Pi's Text (which splits on CR as well as LF), so the CR no longer reaches the terminal and the row is followed by Pi's empty wrapped row.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: Fixed the edit tool's diff card for files with CRLF line endings: a diff row that ends in a carriage return is now wrapped like Pi's Text (which splits on CR as well as LF), so the CR no longer reaches the terminal and the row is followed by Pi's empty wrapped row.

BF-dde6aa6-4

Fixed the extension editor dialog to size its text window from the terminal rows (`max(5, floor(rows *…

  • Source: PiG's own defect
  • Symptom: Fixed the extension editor dialog to size its text window from the terminal rows (max(5, floor(rows * 0.3))) as Pi does instead of always showing five lines, and its title and the image fallback line to close only the foreground colour (ESC[39m) rather than resetting every attribute.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: Fixed the extension editor dialog to size its text window from the terminal rows (max(5, floor(rows * 0.3))) as Pi does instead of always showing five lines, and its title and the image fallback line to close only the foreground colour (ESC[39m) rather than resetting every attribute.

BF-dde6aa6-5

Fixed lists drawn by the submenu and filterable selectors (settings submenus, model and thinking lists)…

  • Source: PiG's own defect
  • Symptom: Fixed lists drawn by the submenu and filterable selectors (settings submenus, model and thinking lists) showing one label cell in a terminal narrower than five columns; Pi shows the cursor prefix alone.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: Fixed lists drawn by the submenu and filterable selectors (settings submenus, model and thinking lists) showing one label cell in a terminal narrower than five columns; Pi shows the cursor prefix alone.

BF-dde6aa6-6

Fixed the footer's context percentage keeping the usage of a reply that a context edit had dropped (a…

  • Source: PiG's own defect
  • Symptom: Fixed the footer's context percentage keeping the usage of a reply that a context edit had dropped (a response cut off by the token limit): it now refreshes when the edit is appended, as Pi's footer does on its next render.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: Fixed the footer's context percentage keeping the usage of a reply that a context edit had dropped (a response cut off by the token limit): it now refreshes when the edit is appended, as Pi's footer does on its next render.

BF-dde6aa6-7

A table delimiter row on a lazy blockquote line no longer splits the quoted paragraph above it unless it…

  • Source: PiG's own defect
  • Symptom: A table delimiter row on a lazy blockquote line no longer splits the quoted paragraph above it unless it forms a table with a lazy header of the same cell count.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: A table delimiter row on a lazy blockquote line no longer splits the quoted paragraph above it unless it forms a table with a lazy header of the same cell count.

BF-dde6aa6-8

A setext underline on a lazy line after another lazy line in a blockquote now renders as Pi does, joined to…

  • Source: PiG's own defect
  • Symptom: A setext underline on a lazy line after another lazy line in a blockquote now renders as Pi does, joined to the quoted paragraph indented by four spaces.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: A setext underline on a lazy line after another lazy line in a blockquote now renders as Pi does, joined to the quoted paragraph indented by four spaces.

BF-dde6aa6-9

A line of only spaces and tabs that starts with a tab or four spaces, alone or after a blank line or in a…

  • Source: PiG's own defect
  • Symptom: A line of only spaces and tabs that starts with a tab or four spaces, alone or after a blank line or in a blockquote, is blank in markdown rendering instead of an empty code block, as in Pi.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): dde6aa6a68 (2026-10-12)
  • Tests: agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, test/docs-drift/docs_drift_test.go::TestDocumentedSlashCommandsExist
  • Release note: A line of only spaces and tabs that starts with a tab or four spaces, alone or after a blank line or in a blockquote, is blank in markdown rendering instead of an empty code block, as in Pi.

BF-de0aaa7

RPC mode now writes the PIG_PROFILE files when it exits; it ended the process before they were written

  • Source: PiG's own defect
  • Symptom: RPC mode now writes the PIG_PROFILE files when it exits; it ended the process before they were written.
  • Root cause: Adds the gates the performance scoreboard needs and that did not exist, all count based (allocations, bytes, GC cycles, RSS/PSS, goroutines, descriptors, children, temp entries, binary size), never time:
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: de0aaa7dec (2026-10-09)
  • Tests: ai/perf_gate_norace_test.go::TestProviderStreamPerFrameAllocationGate, ai/perf_gate_norace_test.go::TestStreamBuilderPerDeltaAllocationGate, automation/perf/test_scoreboard.py, coding/extension/host/subprocess/perf_gate_norace_test.go::TestExtensionEventRoundTripAllocationGate, internal/codingagent/compaction/perf_gate_norace_test.go::TestCompactionPreparePerEntryAllocationGate, internal/codingagent/perf_gate_norace_test.go::TestSessionLoadPerLineAllocationGate, internal/codingagent/perf_gate_norace_test.go::TestSessionTurnAllocationGate, internal/perfgate/perfgate_test.go::TestFormat and 18 more in docs/bug-ledger.json
  • Release note: RPC mode now writes the PIG_PROFILE files when it exits; it ended the process before they were written.

BF-de38ce2

fix(ai,durable): a lone UTF-16 surrogate survives the JSON of a tool result, a nested call result and a…

  • Source: PiG's own defect
  • Symptom: fix(ai,durable): a lone UTF-16 surrogate survives the JSON of a tool result, a nested call result and a stored value, as in Pi
  • Root cause: Fix-Commit:
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: de38ce2324 (2026-10-24)
  • Tests: ai/lone_surrogate_json_pi_oracle_test.go::TestMessagesKeepALoneSurrogateAsPiDoes, coding/cli/lone_surrogate_json_lines_pi_oracle_test.go::TestJSONLinesOfAMessageWithALoneSurrogateAreWhatPisJSONStringifyWrites, durable/codemode/lone_surrogate_preview_test.go::TestAPreviewCutInsideASurrogatePairKeepsItsEscapeLikePi, durable/codemode/lone_surrogate_tool_result_test.go::TestAToolResultWithALoneSurrogateKeepsItThroughTheHarnessAndTheEntry, durable/json_lone_surrogate_test.go::TestToJsonValueAndFromJsonValueKeepALoneSurrogate, internal/codingagent/lone_surrogate_session_pi_oracle_test.go::TestSessionLinesWithALoneSurrogateAreWhatPisJSONStringifyWrites

BF-de85b0e

An HTTP MCP client opens its server-to-client stream before it sends the next request after…

  • Source: PiG's own defect
  • Symptom: An HTTP MCP client opens its server-to-client stream before it sends the next request after notifications/initialized, as Pi does.
  • Root cause: streamable-http.ts send() calls startGetStream() synchronously, so the server-to-client GET is issued before the client's next request. The Go transport started a goroutine, which under load lost the race to the next POST; the conformance suite's sse-retry scenario then took that first GET for the reconnection and failed client-sse-last-event-id and client-sse-retry-timing in about one run in ten.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: de85b0e873 (2026-10-05)
  • Tests: mcp/streamable_http_test.go::TestStreamableHTTPTransportWritesTheGETStreamRequestBeforeTheInitializedNotificationReturns
  • Release note: An HTTP MCP client opens its server-to-client stream before it sends the next request after notifications/initialized, as Pi does. A server that treats the first GET as a stream reconnection no longer fails about one connection in ten.

BF-de91c76

When an MCP server drops tools or is disabled, its tools are hidden in the order the server listed them

  • Source: PiG's own defect
  • Symptom: When an MCP server drops tools or is disabled, its tools are hidden in the order the server listed them.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): de91c76c2f (2026-10-05)
  • Tests: None recorded.
  • Release note: When an MCP server drops tools or is disabled, its tools are hidden in the order the server listed them.

BF-de91c76-2

The error of an MCP stdio server that fails to start ends with the last 2000 UTF-16 code units of its…

  • Source: PiG's own defect
  • Symptom: The error of an MCP stdio server that fails to start ends with the last 2000 UTF-16 code units of its standard error, as in Pi.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): de91c76c2f (2026-10-05)
  • Tests: None recorded.
  • Release note: The error of an MCP stdio server that fails to start ends with the last 2000 UTF-16 code units of its standard error, as in Pi.

BF-deadeb8

A prompt in a long session no longer costs time in proportion to the session's history: the current branch…

  • Source: PiG's own defect
  • Symptom: A prompt in a long session no longer costs time in proportion to the session's history: the current branch and its context are cached instead of rebuilt from the whole path several times per prompt (630 ms to 50 ms per prompt at 50,000 entries).
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: deadeb8d16 (2026-10-10)
  • Tests: internal/codingagent/session_store_norace_test.go::TestResumedSessionRetainsContextNotHistory, internal/codingagent/session_store_norace_test.go::TestTurnCostIndependentOfHistoryBehindCompaction, internal/codingagent/session_store_test.go::TestExtensionSessionReadsOfAnIndexedSessionMatchTheWholeLog, internal/codingagent/session_store_test.go::TestLiveSessionReleasesEntriesBehindCompaction, internal/codingagent/session_store_test.go::TestSessionIndexReadsMatchResidentEntries
  • Release note: A prompt in a long session no longer costs time in proportion to the session's history: the current branch and its context are cached instead of rebuilt from the whole path several times per prompt (630 ms to 50 ms per prompt at 50,000 entries).

BF-deadeb8-2

Extension session reads (getEntry, getLabel, getChildren, getBranch, buildSessionContext and the…

  • Source: PiG's own defect
  • Symptom: Extension session reads (getEntry, getLabel, getChildren, getBranch, buildSessionContext and the others) decode only the entries they return instead of the whole session on every call.
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: deadeb8d16 (2026-10-10)
  • Tests: bench/sessionscale/cmd/sessionscale/gate_test.go::TestResumeMemoryAndPromptCostFollowTheContext, bench/sessionscale/gen_test.go::TestGenerateWritesTheScaledOwnerShape, internal/codingagent/session_store_norace_test.go::TestResumedSessionRetainsContextNotHistory, internal/codingagent/session_store_norace_test.go::TestTurnCostIndependentOfHistoryBehindCompaction, internal/codingagent/session_store_test.go::TestExtensionSessionReadsOfAnIndexedSessionMatchTheWholeLog, internal/codingagent/session_store_test.go::TestLiveSessionReleasesEntriesBehindCompaction, internal/codingagent/session_store_test.go::TestSessionIndexReadsMatchResidentEntries
  • Release note: Extension session reads (getEntry, getLabel, getChildren, getBranch, buildSessionContext and the others) decode only the entries they return instead of the whole session on every call.

BF-debe30e

A keybindings.json key named like a property every JavaScript object inherits (constructor, toString…

  • Source: PiG's own defect
  • Symptom: A keybindings.json key named like a property every JavaScript object inherits (constructor, toString, __proto__ and the rest) is renamed on load the way Pi renames it (to that property's printed text), instead of being kept as written.
  • Root cause: PiG kept those keys as written. legacyKeybindingName now returns Pi's rename for the twelve inherited names.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: debe30e486 (2026-10-12)
  • Tests: internal/codingagent/keybindings_file_oracle_test.go::TestKeybindingsFileLoadingMatchesPi
  • Release note: A keybindings.json key named like a property every JavaScript object inherits (constructor, toString, __proto__ and the rest) is renamed on load the way Pi renames it (to that property's printed text), instead of being kept as written.

BF-dec54eb

fix(tui): Container.Render is safe beside a frame that borrows the window

  • Source: PiG's own defect
  • Symptom: fix(tui): Container.Render is safe beside a frame that borrows the window
  • Root cause: An earlier change made a rebuild write into the array the render two back returned. Container.Render still cloned that array after releasing the lock, and it recycled arrays too. Two regressions followed, both race-free on: - concurrent Render callers raced: a render wrote into the array another caller was still copying; - a frame that borrowed a nested container's lines raced with Render calls on that container from another goroutine, which wrote into the borrowed array.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: dec54ebcc9 (2026-10-10)
  • Tests: tui/container_render_concurrency_test.go::TestContainerRenderIsSafeForConcurrentCallers, tui/container_render_concurrency_test.go::TestFrameBorrowIsSafeFromConcurrentContainerRender, tui/windowed_render_test.go::TestWindowedFramesEqualWholeFrames

BF-df0b21c

A Go, Rust or Python extension that calls parseFrontmatter or Date.parse while its factory runs, in a…

  • Source: PiG's own defect
  • Symptom: A Go, Rust or Python extension that calls parseFrontmatter or Date.parse while its factory runs, in a runtime cell shared with other extensions, no longer waits forever when another member of the cell has already connected.
  • Root cause: In a packed cell the host accepts members in order and serves a connection only after the members before it, so when the ui-peer member had registered first (thread order in the runner is not fixed) the rust-sdk-fixture factory's frontmatter.parse rode ui-peer's connection and waited for an answer the host would not send until the fixture had connected: a deadlock. strace of the stuck runner: the fixture thread sendto(fd of ui-peer's socket, frontmatter.parse) and never returned. Fix: running connections carry the socket they serve; with PIG_EXT_SOCKET set the helper uses the running extension on that socket and otherwise dials it (isolated extensions keep their own connection, an extension…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 07eccbca97 (2026-10-11), 34f7594aa5 (2026-10-11), ca281e10fe (2026-10-11), df0b21c39b (2026-10-12)
  • Tests: coding/extension/host/subprocess/runtime_node_model_runtime_test.go::TestNodeLoadTimeCompletionAnswersBeforeRegistration, extensions/sdk/host_helpers_running_test.go::TestHelperConnectionSurvivesTheFirstRunningExtensionStopping, extensions/sdk/host_helpers_running_test.go::TestHelperNamedForASocketDoesNotUseAnotherMembersConnection, internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, internal/codingagent/subprocess_early_model_stream_test.go::TestEarlyModelStreamServesAPICallsWithoutASession and 5 more in docs/bug-ledger.json
  • Release note: A Go, Rust or Python extension that calls parseFrontmatter or Date.parse while its factory runs, in a runtime cell shared with other extensions, no longer waits forever when another member of the cell has already connected. The helper now uses the connection of the extension that owns the socket the runner names, and dials that socket when the extension has not connected yet, instead of reusing a sibling's connection that the host serves only later.

BF-df96b6a

Fix in shipped code

  • Source: PiG's own defect
  • Symptom: Fix in shipped code
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: df96b6a5ac (2026-10-21)
  • Tests: None recorded.

BF-dfa57b3

An OpenAI Responses request for a conversation with no messages sends "input": [] instead of "input": null

  • Source: PiG's own defect
  • Symptom: An OpenAI Responses request for a conversation with no messages sends "input": [] instead of "input": null.
  • Root cause: convertResponsesMessages starts from const messages: ResponseInput = [] (openai-responses-shared.ts:152). convertAnchoredMessages declared a nil slice, so a context with no messages encoded input: null.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: dfa57b3253 (2026-10-06)
  • Tests: ai/openai_responses_empty_input_test.go::TestConvertAnchoredMessagesEncodesAnEmptyInputAsAnArray
  • Release note: An OpenAI Responses request for a conversation with no messages sends "input": [] instead of "input": null.

BF-e098463

A Gemini stream chunk with a member of an unexpected type is read as Pi's JavaScript reads it instead of…

  • Source: PiG's own defect
  • Symptom: A Gemini stream chunk with a member of an unexpected type is read as Pi's JavaScript reads it instead of ending the turn with google: invalid SSE JSON: a text that is not a string (or null) appends its string form, a thought or thoughtSignature of the wrong type is ignored, a candidates, candidate, content or functionCall that is not the expected shape reads as empty, parts that are a string are skipped and other non-arrays end the turn with candidate.content.parts is not iterable, a null part ends it with Pi's Cannot read properties of null error after the parts before it, a finish reason that is not a string ends it with Unhandled stop reason: <its string form>, and usage that is not an object reads as zeros, and a candidatesTokenCount of true counts as 1, as Pi's + makes it.
  • Root cause: google-generative-ai.ts:236-237 reads output: (usageMetadata.candidatesTokenCount || 0) + (usageMetadata.thoughtsTokenCount || 0), so true adds 1 and the usage stays numeric. PiG's JS read of a mistyped chunk refused a truthy non-number count and ended the turn with the decode error. normalizeGeminiUsage now reads a true candidates count as 1; every other count is stored as read (a truthy non-number thoughts, total or cached count still ends the turn, because Pi stores it raw). The prompt count is only subtracted from and already counted as its Number().
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 366969bbae (2026-10-11), 5078f73100 (2026-10-11), 5932542c21 (2026-10-11), 71e9d13eb9 (2026-10-11), 82414338e7 (2026-10-11), 8eb1202528 (2026-10-11), b6afa7be1a (2026-10-11), e098463b19 (2026-10-11), f1a59f9161 (2026-10-11)
  • Tests: ai/anthropic_event_member_type_oracle_test.go::TestAnthropicEventMistypedMembersMatchPi, ai/google_chunk_member_type_oracle_test.go::TestGoogleChunkMistypedMembersMatchPi, ai/mistral_chunk_member_type_oracle_test.go::TestMistralChunkMistypedMembersMatchPi, ai/openai_completions_member_type_oracle_test.go::TestOpenAICompletionsMistypedMembersMatchPi, ai/openai_responses_member_type_oracle_test.go::TestOpenAIResponsesMistypedMembersMatchPi
  • Release note: A Gemini stream chunk with a member of an unexpected type is read as Pi's JavaScript reads it instead of ending the turn with google: invalid SSE JSON: a text that is not a string (or null) appends its string form, a thought or thoughtSignature of the wrong type is ignored, a candidates, candidate, content or functionCall that is not the expected shape reads as empty, parts that are a string are skipped and other non-arrays end the turn with candidate.content.parts is not iterable, a null part ends it with Pi's Cannot read properties of null error after the parts before it, a finish reason that is not a string ends it with Unhandled stop reason: <its string form>, and usage that is not an object reads as zeros, and a candidatesTokenCount of true counts as 1, as Pi's + makes it.

BF-e1764c3

A live /skill: message renders as the collapsed [skill] name block instead of the raw <skill> XML

  • Source: PiG's own defect
  • Symptom: A live /skill: message renders as the collapsed [skill] name block instead of the raw <skill> XML.
  • Root cause: Pi's message_start(user) handler calls addMessageToChat, which parses the skill block and shows a SkillInvocationMessageComponent plus the trailing user message (interactive-mode.ts:3501-3505, 3933-3952). PiG did that only when rebuilding the transcript and printed the raw <skill> XML while the turn ran. The user case of the rebuild is now one method shared with the live path. Found by the skills journey.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e1764c3d37 (2026-10-09)
  • Tests: internal/codingagent/interactive_live_skill_block_test.go::TestLiveUserSkillBlockRendersAsSkillInvocation
  • Release note: A live /skill: message renders as the collapsed [skill] name block instead of the raw <skill> XML.

BF-e1ad272

The xAI, Kimi Code, Meta, Radius, OpenAI Codex, Sign in with ChatGPT and GitHub Copilot OAuth requests send…

  • Source: PiG's own defect
  • Symptom: The xAI, Kimi Code, Meta, Radius, OpenAI Codex, Sign in with ChatGPT and GitHub Copilot OAuth requests send their form fields in Pi's order and with Pi's encoding (* kept, ~ encoded as %7E) instead of in alphabetical order.
  • Root cause: xai.ts:73,146-219, kimi-coding.ts:45-236, meta.ts:80,115, radius.ts:162-311 build bodies with new URLSearchParams(fields). url.Values.Encode sorted keys and used QueryEscape. One ordered formFields encoder replaces them; orderedQuery shares it.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 01b94a8249 (2026-10-09), e1ad272ec4 (2026-10-09)
  • Tests: ai/oauth_credential_members_test.go::TestOAuthBuildersWriteCredentialMembersInPiOrder, ai/oauth_form_order_openai_test.go::TestOpenAIAndCopilotOAuthFormBodiesKeepPiFieldOrderAndEncoding, ai/oauth_form_order_test.go::TestOAuthFormBodiesKeepPiFieldOrderAndEncoding
  • Release note: The xAI, Kimi Code, Meta, Radius, OpenAI Codex, Sign in with ChatGPT and GitHub Copilot OAuth requests send their form fields in Pi's order and with Pi's encoding (* kept, ~ encoded as %7E) instead of in alphabetical order.

BF-e24487e

fix(cli): PIG_VERSION_FORMAT=pi prints the pinned Pi package version

  • Source: PiG's own defect
  • Symptom: fix(cli): PIG_VERSION_FORMAT=pi prints the pinned Pi package version
  • Root cause: Pi's --version prints the package version, which differs from the display version when the pin is a pi-mono commit. Use coding.UpstreamPackageVersion so the opt-in always matches pi --version.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: e24487ecef (2026-10-11)
  • Tests: None recorded.

BF-e25a09f

A tool argument or event payload with a number outside the double range (1e400), at any nesting depth, no…

  • Source: PiG's own defect
  • Symptom: A tool argument or event payload with a number outside the double range (1e400), at any nesting depth, no longer fails the call in the Go SDK or ends a Rust extension; it reads as Infinity (Go) or null (Rust), as JSON.parse and JSON.stringify do.
  • Root cause:
    • Rust SDK: from_json_str rewrote an out-of-range number only when the first decode met it. A frame nested past serde_json's 128 levels went to the deep decode, whose "number out of range" was returned, so 1e400 inside a 200-deep argument still ended the extension (JSON.parse takes it). The deep result now goes through the same repair. Every cell now registers "Label <name> <value>", which also proves the replacement carries its new label. - The in-process fixture's throw_probe returned "unused" although makeInprocFixture must match testfixture; it now throws the same messages and TestConformance_ThrownToolErrorMessagesAgreeAcrossSDKs runs the in-process Go reference too.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 6229289c22 (2026-10-11), 78ceab7584 (2026-10-11), a4e5b071cb (2026-10-11), e25a09f2ac (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, extensions/sdk-py/tests/test_compaction_estimate.py, extensions/sdk/compaction_estimate_test.go::TestEstimateTokensCountsUTF16UnitsAndJSONStringifyLength, extensions/sdk/compaction_estimate_test.go::TestJSNumberStringMatchesNumberToString, extensions/sdk/json/decode_numbers_test.go::TestUnmarshalReadsANumberOutOfRangeAsInfinity and 8 more in docs/bug-ledger.json
  • Release note: A tool argument or event payload with a number outside the double range (1e400), at any nesting depth, no longer fails the call in the Go SDK or ends a Rust extension; it reads as Infinity (Go) or null (Rust), as JSON.parse and JSON.stringify do.

BF-e2db931

On Windows, refreshing an MCP server's OAuth token no longer fails with EPERM when another pig process is…

  • Source: PiG's own defect
  • Symptom: On Windows, refreshing an MCP server's OAuth token no longer fails with EPERM when another pig process is still deleting the refresh lock.
  • Root cause: The prune loop checks for cancellation only before each entry. A busy failure that arrived after the cancellation counted as skipped, and the last entry's classification could be cut short with nothing to remove, so the collection returned success and the automatic collector wrote its marker for a run that did not finish. The collection now checks for cancellation before it reports success.
  • User impact: An operation failed or was rejected
  • Platform: Windows
  • Fixed in: 0.5.0
  • Fix commits: ba9c37b247 (2026-10-06, #171), e2db9318fa (2026-10-06, #173)
  • Tests: coding/extension/exec_tree_test.go::TestExecCommandKeepsOutputWrittenAfterTheLeaderExits, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleBusyEntriesAreSkippedNotErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledBeforeStartTouchesNothing, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleCancelledDuringTheLastEntryReportsTheCancellation, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleEntryLeasedAfterClassificationIsSkippedNotAnError, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleOtherErrorsStayErrors, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestCacheLifecycleStopsWhenItsContextIsCancelled, coding/extension/host/runtimecell/cache_lifecycle_busy_test.go::TestIsBusyErrorLooksThroughWrappedJoins and 4 more in docs/bug-ledger.json
  • Release note: On Windows, refreshing an MCP server's OAuth token no longer fails with EPERM when another pig process is still deleting the refresh lock. Lock acquisition that waits for a lock now retries every error, as Pi's lock does, and reports the most frequent error when it gives up.

BF-e32c60c

A Piglet or Package installed under a symlinked agent directory keeps the origin integrity recorded in its…

  • Source: PiG's own defect
  • Symptom: A Piglet or Package installed under a symlinked agent directory keeps the origin integrity recorded in its npm lockfile.
  • Root cause:
    • env: the remote watcher reports paths as given (remote-env.ts #resolve); drop the symlink resolution from the test expectation. - cmd/pig: the warning-only diagnostics name the package as listed (package-manager.ts packageRoot). - coding/piglet: resolve npm lockfile entries whose keys npm writes relative to a symlinked install root, so a symlinked agent directory no longer loses the origin integrity. - interactive thinking harness: drain the PTY until pig exits. - subprocess and experimental tests: use short socket directories. - experimental: wait for a removed socket by polling under a deadline, since fsnotify v1.5.4 reports no removal on macOS. - CI: run cmd/pig, env, the extension…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e32c60c500 (2026-10-06)
  • Tests: coding/extension/host/subprocess/node_frame_buffer_test.go::TestNodeProviderSocketFrameCopyIsLinear, coding/piglet/origin_integrity_test.go::TestReadNPMOriginIntegrityResolvesLockfileKeys, env/watch_test.go::TestRemoteWatcherOpensAgainAfterALostConnectionAndReportsOverflow, test/ci-images/macos_shards_test.go::TestMacOSShardsTestTheMacOSSensitivePackages
  • Release note: A Piglet or Package installed under a symlinked agent directory keeps the origin integrity recorded in its npm lockfile. Lockfile entries that npm writes relative to a symlinked install root were not found.

BF-e3f39b7

Automatic retry now triggers for the gateway error "Unable to forward upstream stream"

  • Source: PiG's own defect
  • Symptom: Automatic retry now triggers for the gateway error "Unable to forward upstream stream".
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e3f39b7bee (2026-10-14, #10920)
  • Tests: ai/assistant_retry_test.go::TestIsRetryableAssistantErrorClassification
  • Release note: Automatic retry now triggers for the gateway error "Unable to forward upstream stream". Carried forward from Pi 7efebff1 (earendil-works/pi#10920), past the 1.2.0+3daa2028 pin.

BF-e42ae82

SettingsManager structured getters (settings-manager.ts getBranchSummarySettings, getRetrySettings…

  • Source: PiG's own defect
  • Symptom: SettingsManager structured getters (settings-manager.ts getBranchSummarySettings, getRetrySettings, getProviderRetrySettings, getCacheWarmingMode, getQuietStartup, getDefaultProjectTrust, getDoubleEscapeAction, getTreeFilterMode, getOutputPad, getMermaidRenderingMode, getImageWidthCells, getTuiMode, the fullscreen getters, getEnabledModels, getNpmCommand, getTrackingId and others): new live-Pi oracle TestSettingsManagerStructuredGettersMatchPi (2,000 layered global/project file pairs).
  • Root cause: Real fixes: mergeSettings ignored project enableAnalytics and trackingId; terminal.imageWidthCells of 0 or negative read as 60 instead of Pi's max(1, floor).
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e42ae82264 (2026-10-10)
  • Tests: internal/codingagent/settings_structured_getters_oracle_test.go::TestSettingsManagerStructuredGettersMatchPi, internal/codingagent/settings_structured_getters_oracle_test.go::TestSettingsWriteKeepsAnAuthoredNonPositiveImageWidthCells
  • Release note: SettingsManager structured getters (settings-manager.ts getBranchSummarySettings, getRetrySettings, getProviderRetrySettings, getCacheWarmingMode, getQuietStartup, getDefaultProjectTrust, getDoubleEscapeAction, getTreeFilterMode, getOutputPad, getMermaidRenderingMode, getImageWidthCells, getTuiMode, the fullscreen getters, getEnabledModels, getNpmCommand, getTrackingId and others): new live-Pi oracle TestSettingsManagerStructuredGettersMatchPi (2,000 layered global/project file pairs). Two real fixes: (1) a project settings file's enableAnalytics and trackingId were ignored by the merge (deepMergeSettings lets every project key override), so a project enableAnalytics: true read as false; (2) terminal.imageWidthCells of 0 or a negative number read as the 60 default, where Pi's getImageWidthCells reads Math.max(1, Math.floor(width)) = 1; an authored value is now tracked (imageWidthCellsSet), merged from the project file and written back. Regression TestSettingsWriteKeepsAnAuthoredNonPositiveImageWidthCells. 15 mutations red. Not compared: environment-reading getters, wrong-typed values and fractional numbers (Settings is typed), empty strings.

BF-e48a02f

fix(tui): filterable list drops the label when no cell remains (Pi select-list)

  • Source: PiG's own defect
  • Symptom: fix(tui): filterable list drops the label when no cell remains (Pi select-list)
  • Root cause: select-list.ts renderItem computes maxWidth = width - 2 - 2 without a floor and truncateToWidth returns "" for maxWidth <= 0; Pig's FilterableList floored it at 1, drawing one label cell at widths 3-4. TestFilterableListShowsNoLabelWhenNoCellRemains is red without the fix.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: e48a02f7ec (2026-10-11)
  • Tests: internal/codingagent/settings_selector_oracle_test.go::TestSettingsSelectorMatchesPi, tui/select_filterable_test.go::TestFilterableListShowsNoLabelWhenNoCellRemains

BF-e51bb37

OpenAI Responses streams follow Pi's processResponsesStream on malformed and edge events, checked against…

  • Source: PiG's own defect
  • Symptom: OpenAI Responses streams follow Pi's processResponsesStream on malformed and edge events, checked against the pinned Pi in a new oracle test: a failed response records only its status (no usage or response id), end_turn is recorded only for Codex, an error event without a code or message reads Error Code undefined: undefined, a response.created or terminal event without a response reports Pi's TypeError text, and a reasoning signature backfilled with encrypted_content keeps its original key order.
  • Root cause: It exposed five differences, fixed at the source: a failed response leaked usage and response id (Pi records only the status); end_turn was recorded outside Codex; an error event without code or message printed empty text where Pi prints undefined; a created or terminal event without a response gave a Go message where Pi gives its TypeError text; the encrypted_content backfill re-sorted the signature's keys where Pi keeps insertion order. Custom tool input accumulation gets a direct test. ~65 mutants run; evidence recorded. ai unproven rows 153 -> 151.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e8cc072521 (2026-10-08), e51bb373be (2026-10-10)
  • Tests: ai/auth_store_test.go::TestInMemoryCredentialStoreListRejectsACancelledContext, ai/cloudflare_auth_upstream_test.go::TestCloudflareWorkersAIAuthMergesCredentialAndEnvironmentPerField, ai/convert_completions_proof_test.go::TestConvertCompletionsMessagesAddsEmptyReasoningContentOnlyWhenNoneWasCopied, ai/convert_completions_proof_test.go::TestConvertCompletionsMessagesJoinsAssistantThinkingByTheReasoningFieldRules, ai/convert_completions_proof_test.go::TestConvertCompletionsMessagesKeepsTheFirstReasoningDetailsAndFallsBackToLegacyOnes, ai/convert_completions_proof_test.go::TestConvertCompletionsMessagesNeedsAnAssistantBridgeAfterToolResultsOnlyWhenTheLastKeptMessageWasOne, ai/convert_completions_proof_test.go::TestConvertCompletionsMessagesSanitizesStringUserTextAndToolResultText, ai/convert_completions_proof_test.go::TestConvertCompletionsMessagesSendsASanitizedMidConversationInstructionInTheInstructionRole and 12 more in docs/bug-ledger.json
  • Release note: OpenAI Responses streams follow Pi's processResponsesStream on malformed and edge events, checked against the pinned Pi in a new oracle test: a failed response records only its status (no usage or response id), end_turn is recorded only for Codex, an error event without a code or message reads Error Code undefined: undefined, a response.created or terminal event without a response reports Pi's TypeError text, and a reasoning signature backfilled with encrypted_content keeps its original key order.

BF-e54c2a7

openai-decisions writes the image input's user message, text part and image parts with their members in…

  • Source: PiG's own defect
  • Symptom: openai-decisions writes the image input's user message, text part and image parts with their members in Pi's order (role before content, type before the value), not in Go map order.
  • Root cause: 700 random states, questions, image counts, prices with prompt-length tiers, base URLs and responses (well-formed, malformed, refused, missing and extra answers, usage objects, HTTP errors) run through the pinned classifier and ClassifyOpenAIDecisions against one scripted server: request, stop reason, error message, answers and priced usage agree. The one difference found: the user message of an image request was a Go map and went out role-last; it is a struct in Pi's member order now (openai-decisions.ts:101-113).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 91f99469d5 (2026-10-11), e1abc6c615 (2026-10-11), e54c2a7fa2 (2026-10-11)
  • Tests: ai/llama_cpp_classify_oracle_test.go::TestLlamaCppClassifyMatchesPi, ai/openai_decisions_oracle_test.go::TestOpenAIDecisionsMatchesPi, ai/openrouter_images_edge_oracle_test.go::TestOpenRouterImagesEdgeResponsesMatchPi
  • Release note: openai-decisions writes the image input's user message, text part and image parts with their members in Pi's order (role before content, type before the value), not in Go map order.

BF-e55895a

Durable: opening a store that pi-durable 1.3.0 wrote no longer leaves you unable to go back to it

  • Source: PiG's own defect
  • Symptom: Durable: opening a store that pi-durable 1.3.0 wrote no longer leaves you unable to go back to it.
  • Root cause: PiG's Durable writes schema version 2, which pi-durable 1.3.0 refuses, and opening a version 1 store by file path upgraded it in place, so a user could not go back to the released Pi. The fix copies such a store to <path>.schema-<N>.bak (VACUUM INTO) before the upgrade; an earlier copy is never replaced, and a failed copy fails the open.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: c6cb4f6c37 (2026-10-20), e55895a96d (2026-10-21)
  • Tests: durable/core/driver/schema_backup_core_test.go::TestOpenHarnessCopiesAnOlderSchemaBeforeUpgrading, durable/storage/sqlite/node/schema_backup_test.go::TestOpenNodeSqliteStorageCopiesAnOlderSchemaBeforeUpgrading, durable/storage/sqlite/node/schema_backup_test.go::TestOpenNodeSqliteStorageDoesNotCopyANewOrCurrentStore
  • Release note: Durable: opening a store that pi-durable 1.3.0 wrote no longer leaves you unable to go back to it. PiG's Durable writes schema version 2, which no Pi release reads yet, and it upgraded the store in place. driver.OpenHarness and node.OpenNodeSqliteStorage now copy a store at an older schema version to <path>.schema-1.bak before upgrading it; pi-durable 1.3.0 opens that copy. An earlier copy is never replaced, and if the copy cannot be written the open fails and the store is left as it was. The copy is one extra file the size of the store, written once per older store (D188, #245; the commit that made the change calls it D186, its number before the renumber).

BF-e55cdc2

The Codex catalog entry for GPT-6.1 Sol reports the 500K context window the Codex models endpoint returns…

  • Source: PiG's own defect
  • Symptom: The Codex catalog entry for GPT-6.1 Sol reports the 500K context window the Codex models endpoint returns; other Codex models keep 272K (carried Pi fix, Pi 5712b134, #10840).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e55cdc239a (2026-10-14)
  • Tests: ai/supports_xhigh_upstream_test.go::TestSupportsXHighUpstream
  • Release note: The Codex catalog entry for GPT-6.1 Sol reports the 500K context window the Codex models endpoint returns; other Codex models keep 272K (carried Pi fix, Pi 5712b134, #10840).

BF-e59f3f6

Fixed tool-call arguments losing a field and every field after it when the model wrote an invalid \u escape…

  • Source: PiG's own defect
  • Symptom: Fixed tool-call arguments losing a field and every field after it when the model wrote an invalid \u escape such as \usepackage; the escape is now kept as literal text like other invalid escapes (Pi 4b994d5e, #10868).
  • Root cause: repairJSON treated \u as a valid escape even when four hex digits did not follow, so \usepackage left the JSON unparseable and the partial parser dropped the field and every field after it. A \u without four hex digits is now an invalid escape whose backslash is doubled, like \q (Pi utils/json-parse.ts, #10868). Ports packages/ai/test/json-parse.test.ts; the two tests that pinned the old behaviour now pin the new one.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e59f3f62ed (2026-10-13)
  • Tests: ai/json_parse_upstream_test.go::TestParseStreamingJsonDecodesValidUnicodeEscapes, ai/json_parse_upstream_test.go::TestParseStreamingJsonKeepsInvalidUnicodeEscapesAsLiteralText, ai/json_parse_upstream_test.go::TestParseStreamingJsonOmitsTruncatedUnicodeEscape, ai/sse_json_parity_test.go::TestParseJSONWithRepairKeepsInvalidUnicodeEscapesLiteral
  • Release note: Fixed tool-call arguments losing a field and every field after it when the model wrote an invalid \u escape such as \usepackage; the escape is now kept as literal text like other invalid escapes (Pi 4b994d5e, #10868).

BF-e5a345e

An Anthropic request holds a mid-conversation system update until an assistant message that is actually sent…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: An Anthropic request holds a mid-conversation system update until an assistant message that is actually sent, so an assistant message that converts to no blocks no longer moves the update before a user message.
  • Root cause: Ports Pi (#11091). A mid-conversation system message is held until an assistant message that is actually sent (or the end of the transcript), so an assistant message that converts to no blocks no longer lets the update land before a user message.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.3
  • Fix commits: e5a345eeba (2026-10-22)
  • Tests: ai/transcript_tool_changes_test.go::TestTranscriptToolChangesAnthropicHoldsUpdatePastAssistantThatConvertsToNoBlocks
  • Release note: An Anthropic request holds a mid-conversation system update until an assistant message that is actually sent, so an assistant message that converts to no blocks no longer moves the update before a user message.

BF-e5aae63

The editor now enters bash mode (bash border color, Escape clears it) when the text starts with ! after any…

  • Source: PiG's own defect
  • Symptom: The editor now enters bash mode (bash border color, Escape clears it) when the text starts with ! after any JavaScript whitespace, including blank lines and non-breaking spaces, as in Pi.
  • Root cause: setupKeyHandlers' onChange sets isBashMode from the whole text after JavaScript trimStart. PiG trimmed only spaces and tabs of the first line.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e5aae639c1 (2026-10-13)
  • Tests: internal/codingagent/tool_codemode_timer_test.go::TestCodemodeRowShowsElapsedWhileRunningAndTookOnceFinished, tui/bash_execution_test.go::TestEditorBashModeTrimsTheWholeTextAsJavaScript, tui/terminal_image_test.go::TestHerdrWithGraphicsSignalRendersKittyImage
  • Release note: The editor now enters bash mode (bash border color, Escape clears it) when the text starts with ! after any JavaScript whitespace, including blank lines and non-breaking spaces, as in Pi. PiG looked only at spaces and tabs on the first line.

BF-e5f9fdd

The provider retry transport no longer replaces Body on the caller's request when it retries

  • Source: PiG's own defect
  • Symptom: The provider retry transport no longer replaces Body on the caller's request when it retries.
  • Root cause: A RoundTripper must not modify its request, and the base transport can still be reading the previous attempt's body after an early 503 with Connection: close. Regression: TestRetryTransport_EarlyResponseDoesNotRaceRequestBody.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e5f9fdd166 (2026-10-06)
  • Tests: ai/provider_retry_test.go::TestRetryTransport_EarlyResponseDoesNotRaceRequestBody, coding/mcpext/connection_test.go::TestMCPConnectionsClosesAConnectionThatIsStillInitializingBeforeCloseReturns, coding/mcpext/connection_test.go::TestMCPConnectionsStopsWaitingToRetryAConnectionWhenClosed
  • Release note: The provider retry transport no longer replaces Body on the caller's request when it retries. Each retry sends its own request clone with a fresh body, so a server that answers 503 with Connection: close before reading a large request body no longer races the HTTP transport's body writer.

BF-e63c61c

fix(interactive): pump the owner loop while model selection awaits extensions (#208) (#209)

  • Source: PiG's own defect
  • Symptom: fix(interactive): pump the owner loop while model selection awaits extensions (#208) (#209)
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: e63c61c338 (2026-10-12, #208)
  • Tests: internal/codingagent/interactive_model_tools_deadlock_test.go::TestInteractiveModelSelectionPumpsToolRegistration

BF-e64d2cf

fix(tui): a tool error result collapses like any other (Pi updateResult)

  • Source: PiG's own defect
  • Symptom: fix(tui): a tool error result collapses like any other (Pi updateResult)
  • Root cause: tool-execution.ts updateResult never changes the expansion; bash/read/write renderers show a collapsed preview for an error as for a success. Found by e2e runs of scripted replies through real Pi and Pig (TUI panes and session files compared). TestErrorResultStaysCollapsedUntilExpanded is red without the change. NOTE: TestRefreshModelRuntimeHonoursNetworkForceErrorsAndRegistrationCover fails ('TempDir RemoveAll cleanup: directory not empty') at -count>1 and in full-package runs on the integrate base, unrelated.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: e64d2cfe13 (2026-10-11)
  • Tests: internal/codingagent/model_registry_availability_refresh_gaps_test.go::TestRefreshModelRuntimeHonoursNetworkForceErrorsAndRegistrationCover, tui/tool_execution_test.go::TestErrorResultStaysCollapsedUntilExpanded

BF-e69d018

Closing an env connection, or losing it, now ends its daemon process with SIGTERM, as in Pi, instead of…

  • Source: PiG's own defect
  • Symptom: Closing an env connection, or losing it, now ends its daemon process with SIGTERM, as in Pi, instead of SIGKILL, so a daemon reached through ssh can close its remote side.
  • Root cause: Pi's Connection #teardown (connection.ts:307-320) ends the daemon's stdin and calls child.kill(), Node's SIGTERM. PiG's teardown called Process.Kill (SIGKILL), so an ssh child could not close its remote side. It now uses terminateProcess, the helper runSsh's timeout uses (a plain kill on Windows, as Node does).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 50233fd0d4 (2026-10-10), e69d0181ec (2026-10-10)
  • Tests: env/connection_teardown_signal_test.go::TestConnectionTeardownEndsTheDaemonWithSIGTERM, env/ssh_known_hosts_semantics_test.go::TestConcurrentHostKeyChangesToOneFileAreAppliedOneAtATime, env/ssh_timeout_test.go::TestRunSshTimeoutTerminatesTheChildAndFailsWithoutWaitingForItsOutputToClose
  • Release note: Closing an env connection, or losing it, now ends its daemon process with SIGTERM, as in Pi, instead of SIGKILL, so a daemon reached through ssh can close its remote side.

BF-e73e536

Codemode scripts no longer fail immediately when both codemode workers are busy, for example with parallel…

  • Source: A fix Pi shipped that PiG ported
  • Symptom: Codemode scripts no longer fail immediately when both codemode workers are busy, for example with parallel codemode calls or in-process subagents.
  • Root cause: Ports Pi (#11184): scripts wait up to 60 s in arrival order for a free VM, PI_CODEMODE_MAX_WORKERS raises the limit of two, aborts and deadlines count the wait, a launch resuming after its result settled starts no worker, and the reservation is released exactly once.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: e73e536f2c (2026-10-20)
  • Tests: codemode/worker_settled_launch_test.go::TestLaunchResumingAfterTheResultSettledTakesNoWorker, coding/extension/builtin/codemode/worker_admission_test.go::TestCodemodeWorkerAdmissionBoundsSurvivingWorkers, coding/extension/builtin/codemode/worker_admission_test.go::TestCodemodeWorkerAdmissionDropsAbortedAndTimedOutWaiters, coding/extension/builtin/codemode/worker_admission_test.go::TestCodemodeWorkerAdmissionIgnoresAnInvalidPICodemodeMaxWorkers, coding/extension/builtin/codemode/worker_admission_test.go::TestCodemodeWorkerAdmissionQueuesABurstInArrivalOrder, coding/extension/builtin/codemode/worker_admission_test.go::TestCodemodeWorkerAdmissionReadsTheLimitFromPICodemodeMaxWorkers, coding/extension/builtin/codemode/worker_admission_test.go::TestCodemodeWorkerAdmissionRefusesAnAbortedCallerAtOnce, coding/extension/builtin/codemode/worker_admission_test.go::TestCodemodeWorkerSlotIsReturnedAfterSuccessAndTimeout and 2 more in docs/bug-ledger.json
  • Release note: Codemode scripts no longer fail immediately when both codemode workers are busy, for example with parallel codemode calls or in-process subagents. A script waits up to 60 seconds, in arrival order, for a free worker; the wait counts toward its timeout_ms (Pi 885a564b, #11184).

BF-e75a8b9

The external editor (ctrl+g) now edits prompt.md inside a private pi-editor-* directory under the…

  • Source: PiG's own defect
  • Symptom: The external editor (ctrl+g) now edits prompt.md inside a private pi-editor-* directory under the system temp directory and removes the directory afterwards, as Pi does.
  • Root cause:
    • external-editor.ts (editInExternalEditor, external-editor.ts:15-45) writes prompt.md inside mkdtempSync(join(tmpdir(), "pi-editor-")) and removes the directory; OpenExternalEditor wrote pig-editor-*.md straight into the shared temp directory. TestExternalEditorUpstream ports the three cases of external-editor.test.ts:36,52,58 (red on the old code: wrong directory, file name, entries, leftover directory); the test-mapping row now names it instead of "direct name match". They carry // upstream: references to session-selector.ts setStatusMessage (:837,858,862,1003) and formatSessionDate (:43). The cell names its load, status and delete helpers in prose again. - pi_wsl_test.go duplicated…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e75a8b9cbd (2026-10-09)
  • Tests: internal/codingagent/external_editor_test.go::TestEditorHelperProcess, internal/codingagent/external_editor_test.go::TestExternalEditorUpstream, internal/codingagent/wsl_test.go::TestIsWSL
  • Release note: The external editor (ctrl+g) now edits prompt.md inside a private pi-editor-* directory under the system temp directory and removes the directory afterwards, as Pi does. It previously wrote a pig-editor-*.md file directly in the shared temp directory.

BF-e79c70e

fix(ai): Codex WebSocket, OpenRouter images, pi-messages and replaced Anthropic bodies write JSON.stringify…

  • Source: PiG's own defect
  • Symptom: fix(ai): Codex WebSocket, OpenRouter images, pi-messages and replaced Anthropic bodies write JSON.stringify text
  • Root cause: An earlier change moved provider request bodies to marshalRequestBody but left four encoders on encoding/json.Marshal, which escapes '<', '>', '&', U+2028 and U+2029 where Pi's JSON.stringify writes them as they are:
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: e79c70ea9e (2026-10-10)
  • Tests: ai/request_body_escaping_internal_test.go::TestAnthropicReplacedPayloadWritesCharactersAsJSONStringifyDoes, ai/request_body_escaping_internal_test.go::TestCodexWebSocketRequestWritesCharactersAsJSONStringifyDoes, ai/request_body_escaping_internal_test.go::TestOpenRouterImagesRequestWritesCharactersAsJSONStringifyDoes, ai/request_body_escaping_internal_test.go::TestPiMessagesRequestWritesCharactersAsJSONStringifyDoes, ai/request_body_escaping_test.go::TestProviderRequestBodiesWriteCharactersAsJSONStringifyDoes

BF-e8489d4

A key hint for an app action the user unbound shows no key, as in Pi, instead of the action's default key

  • Source: PiG's own defect
  • Symptom: A key hint for an app action the user unbound shows no key, as in Pi, instead of the action's default key.
  • Root cause: Pi keyHint formats getKeybindings().getKeys(action); an action bound to [] gives "" (keybinding-hints.ts formatKeys). AppKeyText fell back to the default key whenever the installed resolver returned "", so the extension editor hint row still showed ctrl+g for an unbound app.editor.external. The fallback now applies only when no resolver is installed (component tests outside the interactive mode).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e8489d4003 (2026-10-11), fac470319e (2026-10-11)
  • Tests: internal/codingagent/extension_editor_test.go::TestExtensionEditorHandleInputMatchesPi
  • Release note: A key hint for an app action the user unbound shows no key, as in Pi, instead of the action's default key. For example, the extension editor dialog showed ctrl+g external editor after app.editor.external was bound to []; it now shows external editor (tui/keybinding_hints.go AppKeyText, keybinding-hints.ts keyText; probe in TestExtensionEditorHandleInputMatchesPi, red before).

BF-e90ac19

Very long streamed answers no longer slow down with their length: each streamed frame renders only the last…

  • Source: PiG's own defect
  • Symptom: Very long streamed answers no longer slow down with their length: each streamed frame renders only the last blocks of the message that can still change instead of lexing, wrapping and laying out all of it, so a 60,000-character answer costs about 5% of the CPU it did, with the same output.
  • Root cause: Tests: - TestStreamedMarkdownWithAForeignThemeCallsItsStylesAgain and TestStreamedMarkdownWithAForeignTextColorCallsItAgain use style functions that read a changing generation. - TestStreamedMarkdownFollowsTheDefaultStyle changes one input per step. - TestStreamedMarkdownEqualsAFreshRender now streams with the default theme. Its reference reuses nothing, because one of its styles is wrapped, so a key without the lexer state fails it. - Mutants red: no purity check, no style flags, no default italic, a foreign Color allowed, no context comparison, no lexer state in the key.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 06f40b8ca1 (2026-10-10), 136e3703be (2026-10-10), 79eadcf623 (2026-10-10), 8693d2dfa9 (2026-10-10), 9f76da76d0 (2026-10-10), ab5be97bbe (2026-10-10), c4fef6c21a (2026-10-10), e892003a81 (2026-10-10), e90ac19595 (2026-10-10)
  • Tests: internal/codingagent/complexity_scaling_mermaid_norace_test.go::TestScalingMermaidTransformOfAStreamedMessage, internal/codingagent/mermaid_word_test.go::TestMermaidTransformSkipsOnlyMessagesWithoutTheWord, internal/testscaling/testscaling_test.go::TestBytesBoundSeparatesTheByteSlope, tui/complexity_scaling_norace_test.go::TestScalingBashOutputFrame, tui/complexity_scaling_norace_test.go::TestScalingMainScreenFrames, tui/complexity_scaling_norace_test.go::TestScalingStreamingMessage, tui/container_render_cache_test.go::TestMainScreenCursorExtractionDoesNotMutateBorrowedContainerLines, tui/highlight_cache_bound_test.go::TestHighlightMemoKeepsOnlyTheNewestPrefixOfAStreamedBlock and 11 more in docs/bug-ledger.json
  • Release note: Very long streamed answers no longer slow down with their length: each streamed frame renders only the last blocks of the message that can still change instead of lexing, wrapping and laying out all of it, so a 60,000-character answer costs about 5% of the CPU it did, with the same output.

BF-e9332c8

fix(codingagent): session search re: reads patterns as V8 does

  • Source: PiG's own defect
  • Symptom: fix(codingagent): session search re: reads patterns as V8 does
  • Root cause: Random JavaScript patterns against Pi's parseSearchQuery/filterAndSortSessions: 441 of 3000 differed (quantified assertions, [a-\d], astral literals, \b \w \s, case folding of U+0130/U+212A, (?s:) groups, error text). translateJSRegExp parses the Annex B grammar with V8's messages and emits regexp2 source over UTF-16 units; annexBRegexpSource is gone.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: e9332c8ccf (2026-10-10)
  • Tests: internal/codingagent/session_search_regex_random_oracle_test.go::TestSessionSearchRegexMatchesPi

BF-e9392fd

A Google Generative AI or Vertex request with no contents (a history of system messages only, or one user…

  • Source: PiG's own defect
  • Symptom: A Google Generative AI or Vertex request with no contents (a history of system messages only, or one user message without content) now fails with contents are required before it is sent, as Pi's @google/genai SDK does, instead of reaching the API and failing with HTTP 400.
  • Root cause: prepareProviderToolFlow and TransformMessages converted every message from the type-switch copy back to the Message interface, which allocates a copy of the whole message on every request, and prepareProviderToolFlow allocated a fresh map per assistant turn. A message that needs no change now keeps the interface value it arrived as, and the tool-flow bookkeeping is cleared instead of reallocated.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 532f5728bb (2026-10-10), 942ecc33e7 (2026-10-10), e9392fd158 (2026-10-10)
  • Tests: ai/google_empty_contents_test.go::TestGoogleEmptyContentsChecksThePayloadOnPayloadReturned, ai/google_empty_contents_test.go::TestGoogleEmptyContentsFailsBeforeSending, ai/tool_flow_result_reset_test.go::TestTransformMessagesResultBeforeCallDoesNotAnswerIt, ai/transform_messages_allocs_test.go::TestTransformMessagesDoesNotReboxUnchangedMessages, test/pipeline-diff/system_only_test.go::TestSystemOnlyHistoryFailsBeforeSendingLikePi
  • Release note: A Google Generative AI or Vertex request with no contents (a history of system messages only, or one user message without content) now fails with contents are required before it is sent, as Pi's @google/genai SDK does, instead of reaching the API and failing with HTTP 400. An onPayload hook that adds or removes contents is checked as sent.

BF-e973464

A fused Go extension's createLocalBashOperations().exec (and createLocalPowerShellOperations) no longer…

  • Source: PiG's own defect
  • Symptom: A fused Go extension's createLocalBashOperations().exec (and createLocalPowerShellOperations) no longer fails with "extension stopped while waiting for builtinTool.localExec response" when another extension in the same process stops while the command runs; a command past its timeout rejects with Pi's timeout:<seconds>.
  • Root cause: localShellExec has no Context, so it sent builtinTool.localExec over runningExtension()'s connection: the first running extension in the process. A fused extension shares the process with every other fused extension, so a command with a timeout ended with "extension stopped while waiting for builtinTool.localExec response" when that extension stopped while the command ran, where Pi's exec rejects with "timeout:<seconds>" and depends on no extension.
  • User impact: An operation failed or was rejected
  • Platform: Windows
  • Fixed in: 0.5.3
  • Fix commits: e97346436d (2026-10-22)
  • Tests: coding/extension/host/subprocess/local_exec_test.go::TestLocalExecReportsPisTimeoutAndEndsWithADeliveryFailure, test/extension-conformance/builtin_local_exec_fused_test.go::TestConformance_FusedLocalExecIsNotCarriedByAnotherExtension
  • Release note: A fused Go extension's createLocalBashOperations().exec (and createLocalPowerShellOperations) no longer fails with "extension stopped while waiting for builtinTool.localExec response" when another extension in the same process stops while the command runs; a command past its timeout rejects with Pi's timeout:<seconds>. The call has no Context, so it was sent over the connection of the first running extension in the process, and ended with that extension, whichever made the call. The host now runs the command for a fused extension directly, as it already does for the other helpers that have no Context, so the command depends on no extension, as Pi's exec depends on none.

BF-e9e9cf8

An extension that calls a pi-ai function such as completeSimple at top level or in its factory no longer…

  • Source: PiG's own defect
  • Symptom: An extension that calls a pi-ai function such as completeSimple at top level or in its factory no longer hangs startup; the Host answers the model stream while the module loads.
  • Root cause: An extension that awaited completeSimple at top level or in its factory hung PiG: the Node runtime sent the modelStream host call before it had registered, and the Host answered extension calls only after the register handshake the factory was holding up. Pi runs pi-ai in the extension's own process with no registration gate.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 15cb4f2db8 (2026-10-11), e9e9cf8816 (2026-10-11)
  • Tests: coding/cli/extension_host_wiring_binary_test.go::TestHeadlessExtensionSessionActionsMatchPi, coding/extension/host/subprocess/runtime_node_model_runtime_test.go::TestNodeLoadTimeCompletionAnswersBeforeRegistration, internal/codingagent/subprocess_early_model_stream_test.go::TestEarlyModelStreamServesAPICallsWithoutASession, test/extension-conformance/host_parser_helpers_test.go::TestConformance_HostParserHelpersAgreeAcrossSDKs
  • Release note: An extension that calls a pi-ai function such as completeSimple at top level or in its factory no longer hangs startup; the Host answers the model stream while the module loads.

BF-ea6db99

A provider request that fails in transport now records its cause: the failed message carries a…

  • Source: PiG's own defect
  • Symptom: A provider request that fails in transport now records its cause: the failed message carries a provider_transport_failure diagnostic (the same shape Pi writes for its Codex WebSocket failures) with the Go cause and a Node-style code such as ECONNRESET, ETIMEDOUT or ENOTFOUND, so a reset, a dial timeout and a DNS failure can be told apart in the session file and /bug.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ea6db9948d (2026-10-14)
  • Tests: ai/transport_diagnostic_test.go::TestClassifyTransportCause, ai/transport_diagnostic_test.go::TestOldAssistantMessageWithoutDiagnosticRoundTrips, ai/transport_diagnostic_test.go::TestProviderContextCancelRecordsNoTransportDiagnostic, ai/transport_diagnostic_test.go::TestProviderTransportMidStreamRecordsDiagnostic, ai/transport_diagnostic_test.go::TestProviderTransportResetRecordsDiagnosticAndKeepsMessage, ai/transport_diagnostic_test.go::TestTransportDiagnosticLeavesEventStreamUnchanged, ai/transport_diagnostic_test.go::TestTransportFailureDiagnosticSkipsAbortsAndOtherErrors
  • Release note: A provider request that fails in transport now records its cause: the failed message carries a provider_transport_failure diagnostic (the same shape Pi writes for its Codex WebSocket failures) with the Go cause and a Node-style code such as ECONNRESET, ETIMEDOUT or ENOTFOUND, so a reset, a dial timeout and a DNS failure can be told apart in the session file and /bug. The error message, stop reason, retries and events are unchanged, and existing sessions are untouched. Reported by @ShoichiTect (#87)

BF-ea90fb3

A running !cmd block keeps only the last 2000 lines or 50 KB it shows instead of every byte the command…

  • Source: PiG's own defect
  • Symptom: A running !cmd block keeps only the last 2000 lines or 50 KB it shows instead of every byte the command prints, so a long build or !cat of a large file no longer grows memory with its output (32 MB of output: 33.3 MB retained before, 0.2 MB now).
  • Root cause: Records D176 and the changelog fragment.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 0532a57c08 (2026-10-13), ea90fb311c (2026-10-13)
  • Tests: internal/codingagent/bash_execution_oracle_test.go::TestBashExecutionComponentMatchesPi, internal/codingagent/settings_live_effects_test.go::TestOnSettingAppliedOutputPaddingReachesEveryChatAndPendingChild, tui/bash_execution_retention_test.go::TestBashExecutionStreamingRetainsTheTailNotTheWholeOutput, tui/bash_execution_test.go::TestBashExecutionAppendOutputCleansChunksAndExposesOutputAndCommand, tui/component_accessors_test.go::TestBashExecutionAppendOutputSanitizesAndGettersReadItBack
  • Release note: A running !cmd block keeps only the last 2000 lines or 50 KB it shows instead of every byte the command prints, so a long build or !cat of a large file no longer grows memory with its output (32 MB of output: 33.3 MB retained before, 0.2 MB now). The rows drawn and the output saved to the session are unchanged. tui.BashExecutionComponent.GetOutput is replaced by ContextOutput, which returns the shown window.

BF-ead285d

Two compact commands of one RPC input batch no longer cancel each other; they run one after another (D147)

  • Source: PiG's own defect
  • Symptom: Two compact commands of one RPC input batch no longer cancel each other; they run one after another (D147).
  • Root cause: Each compact ran on its own goroutine and the second claimed the Session while the first was starting: 'Compaction cancelled' in 2 of 3 runs. The compactions now run through rpcAwaitInOrder, the same chain as set_model and the Session replacements, each starting after the previous one settled.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ead285ddfa (2026-10-10)
  • Tests: coding/cli/rpc_replacement_order_test.go::TestRPCCompactsOfOneBatchDoNotCancelEachOther
  • Release note: Two compact commands of one RPC input batch no longer cancel each other; they run one after another (D147).

BF-eb34085

The find tool trims each fd result line like JavaScript trim(): a name ending in U+FEFF loses it and a name…

  • Source: PiG's own defect
  • Symptom: The find tool trims each fd result line like JavaScript trim(): a name ending in U+FEFF loses it and a name ending in U+0085 keeps it.
  • Root cause: find.ts reads each fd line as rawLine.replace(/\r$/, "").trim(); Go used strings.TrimSpace, which trims U+0085 and keeps U+FEFF where JavaScript does the opposite, so a file name ending in U+FEFF kept it and one ending in U+0085 or a name that is only U+0085 lost it. The line is trimmed with jsstring.Trim now. The grep and find stderr trims and the grep blank-line test see no such text (ripgrep JSON lines, error messages) and are left alone.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: eb34085200 (2026-10-11)
  • Tests: internal/codingagent/tools/find_trim_oracle_test.go::TestFindToolTrimsFdLinesLikePi
  • Release note: The find tool trims each fd result line like JavaScript trim(): a name ending in U+FEFF loses it and a name ending in U+0085 keeps it.

BF-ebb4c0e

An "m" operation whose permutation a Go caller builds as []int validates and applies again, as it did in…

  • Source: PiG's own defect
  • Symptom: AssertValidOp reported m permutation is not an array.
  • Root cause:
    • An "m" permutation built as []int (Pi's number[]) validated and applied in 0.4.1; since the one-package change it fails with "m permutation is not an array" and Apply panics on it. permutationItems accepts both forms. TestAnIntPermutationStillApplies is red before the fix. - Since Op is the alias again, json.Unmarshal into durable.DocumentContent put Go maps in its operations ( decoded them in order). DocumentContent.UnmarshalJSON decodes Ops as delta.Ops, as Pi's storage JSON.parse keeps order (storage/sqlite/storage.ts:63). TestDocumentContentDecodesOperationObjectsInKeyOrder is red before. - Decoding the node facet host's "state" Ops as a plain []chord.Op survived every test…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 80988ccdf3 (2026-10-07), 1f5a782c0a (2026-10-08), 2f86645d6b (2026-10-08), 33c35623be (2026-10-08), 5355335e3a (2026-10-08), 6ca5fde3a9 (2026-10-08), 9d34ce6e4f (2026-10-08), 301e3c185f (2026-10-09), 8c7bacff5b (2026-10-09), 9b505ead89 (2026-10-09), ebb4c0e638 (2026-10-09)
  • Tests: chord/delta/apply_immutable_upstream_test.go::TestCheckedImmutableOperationApplication, chord/delta/apply_immutable_upstream_test.go::TestImmutableApplicationComplexity, chord/delta/apply_numeric_key_test.go::TestApplyImmutableSetsANumericSegmentOnAnObjectAsAKey, chord/delta/apply_pi_oracle_test.go::TestApplyResolvesArraySegmentsInUpstreamOrder, chord/delta/apply_pi_oracle_test.go::TestTruncateInsideASurrogatePairKeepsTheLowHalf, chord/delta/compat_test.go::TestAnIntPermutationStillApplies, chord/delta/compat_test.go::TestOpsDecodeKeepsObjectKeyOrder, chord/delta/compat_test.go::TestPathOfAReplacementIsNil and 30 more in docs/bug-ledger.json
  • Release note: An "m" operation whose permutation a Go caller builds as []int validates and applies again, as it did in 0.4.1. Before this fix, AssertValidOp reported m permutation is not an array.

BF-ebb4c0e-2

durable.DocumentContent decoded by encoding/json keeps the key order of the objects its operations carry…

  • Source: PiG's own defect
  • Symptom: durable.DocumentContent decoded by encoding/json keeps the key order of the objects its operations carry, as Pi's storage JSON.parse does.
  • Root cause:
    • An "m" permutation built as []int (Pi's number[]) validated and applied in 0.4.1; since the one-package change it fails with "m permutation is not an array" and Apply panics on it. permutationItems accepts both forms. TestAnIntPermutationStillApplies is red before the fix. - Since Op is the alias again, json.Unmarshal into durable.DocumentContent put Go maps in its operations ( decoded them in order). DocumentContent.UnmarshalJSON decodes Ops as delta.Ops, as Pi's storage JSON.parse keeps order (storage/sqlite/storage.ts:63). TestDocumentContentDecodesOperationObjectsInKeyOrder is red before. - Decoding the node facet host's "state" Ops as a plain []chord.Op survived every test…
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6ca5fde3a9 (2026-10-08), 301e3c185f (2026-10-09), 8c7bacff5b (2026-10-09), ac3759ebb7 (2026-10-09), cb15f9d6b6 (2026-10-09), ebb4c0e638 (2026-10-09)
  • Tests: chord/delta/apply_immutable_upstream_test.go::TestCheckedImmutableOperationApplication, chord/delta/apply_immutable_upstream_test.go::TestImmutableApplicationComplexity, chord/delta/apply_numeric_key_test.go::TestApplyImmutableSetsANumericSegmentOnAnObjectAsAKey, chord/delta/apply_pi_oracle_test.go::TestApplyResolvesArraySegmentsInUpstreamOrder, chord/delta/apply_pi_oracle_test.go::TestTruncateInsideASurrogatePairKeepsTheLowHalf, chord/delta/compat_test.go::TestAnIntPermutationStillApplies, chord/delta/compat_test.go::TestOpsDecodeKeepsObjectKeyOrder, chord/delta/compat_test.go::TestPathOfAReplacementIsNil and 41 more in docs/bug-ledger.json
  • Release note: durable.DocumentContent decoded by encoding/json keeps the key order of the objects its operations carry, as Pi's storage JSON.parse does.

BF-ebbdfe7

fix(extensions): load highlight.js grammars after the startup session_start, as Pi does

  • Source: PiG's own defect
  • Symptom: fix(extensions): load highlight.js grammars after the startup session_start, as Pi does
  • Root cause: Pi's interactive mode starts loadAllHighlightLanguages only after init has emitted session_start and resources_discover and rendered (interactive-mode.ts:1100-1122). The Node runtime started it when the host's ready frame arrived, so the dynamic import evaluated every highlight.js grammar (~100 ms of main-thread CPU, node --cpu-prof) just as the host sent session_start, and the extension's session_start handler waited behind it. On 06-eventbus-pubsub (warm, Linux, not a BENCH) the host's session_start emission took ~88 ms; with this change ~38 ms.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: ebbdfe743e (2026-10-11)
  • Tests: coding/extension/host/subprocess/node_highlight_startup_test.go::TestNodeRuntimeLoadsHighlightLanguagesAfterStartupSessionStart, coding/extension/host/subprocess/node_highlight_startup_test.go::TestNodeRuntimeLoadsHighlightLanguagesAtReadyWithoutStartupHandlers

BF-ebec8e2

A URL host with an xn-- label that is empty after IDNA mapping (ws://xn--。, https://..\ufeffxn--) or…

  • Source: PiG's own defect
  • Symptom: A URL host with an xn-- label that is empty after IDNA mapping (ws://xn--。, https://..\ufeffxn--) or that holds non-ASCII text after the prefix (https://xn--é/) is rejected, as Node's new URL does; the first was read as a dropped label, the second re-encoded.
  • Root cause: A random composition of URL tokens compared with Node's new URL (20,000 strings, plus 300,000-string seed sweeps) found hosts Node rejects and nodeurl accepted: - a label that is only xn-- after mapping: U+3002/U+FF0E/U+FF61 separators, ignored code points (U+FEFF, soft hyphen, variation selectors) and compatibility forms hid it from the plain-dot split in hasEmptyPunycodeLabel; - an xn-- label with non-ASCII text, which x/net/idna re-encoded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ebec8e29b9 (2026-10-11)
  • Tests: internal/nodeurl/random_compose_oracle_test.go::TestParseMatchesNodeURLOnComposedInputs, internal/nodeurl/random_compose_oracle_test.go::TestParseMatchesNodeURLOnInvalidPunycodeLabels
  • Release note: A URL host with an xn-- label that is empty after IDNA mapping (ws://xn--。, https://..\ufeffxn--) or that holds non-ASCII text after the prefix (https://xn--é/) is rejected, as Node's new URL does; the first was read as a dropped label, the second re-encoded.

BF-ebfeed0

getSystemPromptOptions().hiddenTools lists hidden tools in the order the prepareLoadout hooks hid them…

  • Source: PiG's own defect
  • Symptom: getSystemPromptOptions().hiddenTools lists hidden tools in the order the prepareLoadout hooks hid them, as Pi does, instead of sorted.
  • Root cause: PiG sorted the names, so getSystemPromptOptions().hiddenTools and the before_agent_start options listed them in a different order.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ebfeed0965 (2026-10-05)
  • Tests: coding/session_hidden_tools_order_test.go::TestSystemPromptOptionsListHiddenToolsInTheOrderTheHooksHidThem
  • Release note: getSystemPromptOptions().hiddenTools lists hidden tools in the order the prepareLoadout hooks hid them, as Pi does, instead of sorted.

BF-ec2080a

A subprocess extension's ctx.ui.custom component now learns when another component shown over it takes…

  • Source: PiG's own defect
  • Symptom: The component kept reporting focus until its next key arrived.
  • Root cause: TestCustomOverlayFocusChangesReachSDKs (bridge in place, SDK changes and pack reverted): every cell (subprocess-go, -node, -node-packed, -rust, -python, fused-go) "the component did not render focused=false after it lost focus".
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 007c91dcbb (2026-10-13), 1848633092 (2026-10-13), 2a055a7a3a (2026-10-13), d87b70c54c (2026-10-13), ec2080a75f (2026-10-13)
  • Tests: coding/extension/host/subprocess/host_calls_test.go::TestReadLoopWidgetPushOrdersAgainstQueuedSlotCalls, coding/extension/host/subprocess/ui_bridge_test.go::TestFocusedCustomComponentsStack, coding/extension/host/subprocess/ui_bridge_test.go::TestInteractiveDialogOpensOverFocusedOverlay, coding/extension/host/subprocess/ui_prompt_test.go::TestUIBridgePromptScopeDialogOverCustomAndNoUI, coding/extension/host/subprocess/view_terminal_parity_test.go::TestViewsLeaveTheTerminalBytesUnchanged, coding/extension/host/subprocess/widget_key_namespace_test.go::TestTwoExtensionsSettingOneWidgetKeyShareIt, extensions/sdk-py/tests/test_system_prompt_render.py, extensions/sdk/system_prompt_render_test.go::TestBeforeAgentStartSystemPromptRenderErrorIsTheReadersError and 15 more in docs/bug-ledger.json
  • Release note: A subprocess extension's ctx.ui.custom component now learns when another component shown over it takes focus and when focus comes back, as Pi's setFocus tells it: the overlay handle's isFocused() follows, the component's focused flag is cleared and set again (Node, Go SetFocused, Python focused) and the component renders again. This holds for Node, Go, Rust and Python extensions. Before, the component kept reporting focus until its next key arrived.

BF-ed05099

An extension's ui.setHiddenThinkingLabel now changes the label shown for hidden thinking in the chat…

  • Source: PiG's own defect
  • Symptom: An extension's ui.setHiddenThinkingLabel now changes the label shown for hidden thinking in the chat, including assistant messages already on screen.
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ed05099398 (2026-10-06)
  • Tests: ai/pi_messages_event_type_test.go::TestPiMessagesEventTypeConstantsMatchTheWireLiterals, coding/extension/session_boundary_draft_type_test.go::TestSessionBoundaryDraftTypeSelectsTheUpstreamMember, internal/codingagent/interactive_resume_thinking_test.go::TestInteractiveModeSetHiddenThinkingLabelReachesExistingAndLaterBlocks, internal/codingagent/reload_trust_models_test.go::TestInitNoticesFollowUpstreamOrder, tui/assistant_message_block_label_test.go::TestAssistantMessageComponentSetHiddenThinkingLabel, tui/assistant_message_block_test.go::TestAssistantMessageBlock_SetHideThinkingBlock, tui/assistant_mouse_upstream_test.go::TestAssistantThinkingClicksThroughTerminal, tui/assistant_thinking_markdown_test.go::TestAssistantThinkingTransformIsSeparateAndTracksState and 6 more in docs/bug-ledger.json
  • Release note: An extension's ui.setHiddenThinkingLabel now changes the label shown for hidden thinking in the chat, including assistant messages already on screen. It only changed a footer string before.

BF-ed05099-2

Tab at the start of a line no longer forces file completion while the text before the cursor is a…

  • Source: PiG's own defect
  • Symptom: Tab at the start of a line no longer forces file completion while the text before the cursor is a slash-command name.
  • Root cause: Not recorded.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ed05099398 (2026-10-06)
  • Tests: ai/pi_messages_event_type_test.go::TestPiMessagesEventTypeConstantsMatchTheWireLiterals, coding/extension/session_boundary_draft_type_test.go::TestSessionBoundaryDraftTypeSelectsTheUpstreamMember, internal/codingagent/interactive_resume_thinking_test.go::TestInteractiveModeSetHiddenThinkingLabelReachesExistingAndLaterBlocks, internal/codingagent/reload_trust_models_test.go::TestInitNoticesFollowUpstreamOrder, tui/assistant_message_block_label_test.go::TestAssistantMessageComponentSetHiddenThinkingLabel, tui/assistant_message_block_test.go::TestAssistantMessageBlock_SetHideThinkingBlock, tui/assistant_mouse_upstream_test.go::TestAssistantThinkingClicksThroughTerminal, tui/assistant_thinking_markdown_test.go::TestAssistantThinkingTransformIsSeparateAndTracksState and 6 more in docs/bug-ledger.json
  • Release note: Tab at the start of a line no longer forces file completion while the text before the cursor is a slash-command name.

BF-ed05099-3

pi.exec called while an extension loads, before the session binds, no longer fails with `extension…

  • Source: PiG's own defect
  • Symptom: pi.exec called while an extension loads, before the session binds, no longer fails with extension connection closed or replaced.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ed05099398 (2026-10-06)
  • Tests: ai/pi_messages_event_type_test.go::TestPiMessagesEventTypeConstantsMatchTheWireLiterals, coding/extension/session_boundary_draft_type_test.go::TestSessionBoundaryDraftTypeSelectsTheUpstreamMember, internal/codingagent/interactive_resume_thinking_test.go::TestInteractiveModeSetHiddenThinkingLabelReachesExistingAndLaterBlocks, internal/codingagent/reload_trust_models_test.go::TestInitNoticesFollowUpstreamOrder, tui/assistant_message_block_label_test.go::TestAssistantMessageComponentSetHiddenThinkingLabel, tui/assistant_message_block_test.go::TestAssistantMessageBlock_SetHideThinkingBlock, tui/assistant_mouse_upstream_test.go::TestAssistantThinkingClicksThroughTerminal, tui/assistant_thinking_markdown_test.go::TestAssistantThinkingTransformIsSeparateAndTracksState and 6 more in docs/bug-ledger.json
  • Release note: pi.exec called while an extension loads, before the session binds, no longer fails with extension connection closed or replaced. Load-time exec runs with Pi's default working directory, as Pi's loader serves it (#165, reported by @luiul).

BF-ed05099-4

Tool-call input keeps its object members in the order the model sent them through events, tool execution…

  • Source: PiG's own defect
  • Symptom: Tool-call input keeps its object members in the order the model sent them through events, tool execution, tool_result and the session file, where an edited input was sorted by key (#165).
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ed05099398 (2026-10-06)
  • Tests: ai/pi_messages_event_type_test.go::TestPiMessagesEventTypeConstantsMatchTheWireLiterals, coding/extension/session_boundary_draft_type_test.go::TestSessionBoundaryDraftTypeSelectsTheUpstreamMember, internal/codingagent/interactive_resume_thinking_test.go::TestInteractiveModeSetHiddenThinkingLabelReachesExistingAndLaterBlocks, internal/codingagent/reload_trust_models_test.go::TestInitNoticesFollowUpstreamOrder, tui/assistant_message_block_label_test.go::TestAssistantMessageComponentSetHiddenThinkingLabel, tui/assistant_message_block_test.go::TestAssistantMessageBlock_SetHideThinkingBlock, tui/assistant_mouse_upstream_test.go::TestAssistantThinkingClicksThroughTerminal, tui/assistant_thinking_markdown_test.go::TestAssistantThinkingTransformIsSeparateAndTracksState and 6 more in docs/bug-ledger.json
  • Release note: Tool-call input keeps its object members in the order the model sent them through events, tool execution, tool_result and the session file, where an edited input was sorted by key (#165).

BF-edb73db

fix(codingagent): one owner-overflow forwarder at a time; the drain test waits for it, not for the flag's…

  • Source: PiG's own defect
  • Symptom: fix(codingagent): one owner-overflow forwarder at a time; the drain test waits for it, not for the flag's early read
  • Root cause: forwardOwnerOverflow removed the last task, released the lock, and only then took it again to see the overflow empty and clear ownerOverflowActive. A post the owner loop made in that gap (its queue still full) found the overflow empty, started a second forwarder and queued its task; both forwarders then sent the same task, which ran twice, and the second popped an empty overflow (index out of range in a background goroutine). The forwarder now leaves in the critical section that removes the last task and clears the flag, so a post that finds the overflow empty finds no forwarder.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: edb73db2bf (2026-10-14)
  • Tests: internal/codingagent/overlay_owner_loop_deadlock_test.go::TestOwnerOverflowRunsEachPostOnceInOrderWhileTheQueueStaysFull, internal/codingagent/overlay_owner_loop_deadlock_test.go::TestRunOnMainFromOwnerLoopNeverBlocksOnItsOwnQueue

BF-edd287c

Migrating a version 1 session file that holds a line parsing to a number, string, array or true no longer…

  • Source: PiG's own defect
  • Symptom: Migrating a version 1 session file that holds a line parsing to a number, string, array or true no longer fails while finding the header.
  • Root cause: Pi keeps whatever JSON.parse returns for a session-file line (session-manager.ts parseSessionEntryLine). migrateV1ToV2 gives every record after the header an id and a parent: an array takes the id as an unserialized property and the chain goes through it, a number, string or boolean throws the strict-mode TypeError.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 34f7594aa5 (2026-10-11), edd287c5d8 (2026-10-12)
  • Tests: internal/codingagent/extension_ui_phase_shutdown_test.go::TestAwaitedExtensionPhaseWithoutShutdownReturnsItsResult, internal/codingagent/extension_ui_phase_shutdown_test.go::TestNewSessionEndedByShutdownIsNotAFatalRuntimeError, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseDoesNotWaitForAHandlerThatIgnoresCancellation, internal/codingagent/extension_ui_phase_shutdown_test.go::TestShutdownDuringAwaitedExtensionPhaseEndsThePhase, internal/codingagent/session_open_nonentry_oracle_test.go::TestMigrateSessionEntriesPassesOverRecordsThatAreNotObjectsWhenFindingTheHeader, internal/codingagent/session_open_nonentry_oracle_test.go::TestMigrateV1ToV2ChainsThroughAnArrayRecordAndRefusesAPrimitiveAsPiDoes, internal/codingagent/session_open_nonentry_oracle_test.go::TestSessionOpenKeepsRecordsThatAreNotEntriesAsPiDoes, test/parity/scenarios/extensions-runtime/73-shutdown-from-session-before-fork.toml and 2 more in docs/bug-ledger.json
  • Release note: Migrating a version 1 session file that holds a line parsing to a number, string, array or true no longer fails while finding the header. Like Pi, an array record stays as written and still takes a place in the migration's id chain, and a number, string or boolean record stops the migration with the same Cannot create property 'id' error Pi throws.

BF-eeac307

Anthropic and Bedrock assistant messages write providerThinkingLevel after model, and write…

  • Source: PiG's own defect
  • Symptom: Anthropic and Bedrock assistant messages write providerThinkingLevel after model, and write cacheWrite1h and reasoning after cost in usage, as Pi does.
  • Root cause: Pi passes a tool's parameters object through to the body (openai-completions.ts convertTools :1506-1514, openai-responses-shared.ts :386-390), so JSON.stringify writes the schema's members in declaration order; a strict schema is the same object rebuilt member by member (constrained-sampling.ts makeJsonSchemaNodeStrict: required, then additionalProperties are assigned after the existing members, and an optional property becomes {anyOf: [property, {type: "null"}]}). PiG copied ToolSchema.Parameters as a map[string]any and dropped the recorded parameterOrder, so every schema object was written in sorted order.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2309604d80 (2026-10-10), c10cde302d (2026-10-10), eeac307095 (2026-10-10), f29e906cb3 (2026-10-10)
  • Tests: agent/assistant_member_order_oracle_test.go::TestAssistantLateMembersFollowPiCreationOrder, ai/constrained_sampling_oracle_test.go::TestConstrainedSamplingMatchesPi, ai/openai_request_key_order_oracle_test.go::TestOpenAICompletionsRequestKeysFollowPiAssignmentOrder, ai/openai_responses_request_key_order_oracle_test.go::TestOpenAIResponsesRequestKeysFollowPiAssignmentOrder, coding/cli/rpc_assistant_member_order_test.go::TestRPCAssistantMessageMembersMatchTheSessionLineOrder, coding/cli/rpc_model_type_binary_test.go::TestRPCModelObjectsCarryTheRegistryModelType, coding/codemode_session_upstream_test.go::TestUpstreamCodemodeModels, coding/session_boundaries_drafts_upstream_test.go::TestUpstreamSessionBoundariesDrafts
  • Release note: Anthropic and Bedrock assistant messages write providerThinkingLevel after model, and write cacheWrite1h and reasoning after cost in usage, as Pi does.

BF-ef0f95a

OpenCode Zen and OpenCode Go use the opencode.ai/inference endpoints, which fixes OpenCode Go Gemini models…

  • Source: PiG's own defect
  • Symptom: OpenCode Zen and OpenCode Go use the opencode.ai/inference endpoints, which fixes OpenCode Go Gemini models failing with a 404; OpenCode Go MiniMax M2.7 uses the Anthropic Messages API, and OpenCode Go DeepSeek V4 Flash is no longer listed (carried Pi fix, Pi 195120a9).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ef0f95ab0e (2026-10-14)
  • Tests: ai/classifier_models_upstream_test.go::TestClassifierModelsUpstream, cmd/gen-models/carried_fixes_test.go::TestCarriedOpenCodeInferenceEndpoints
  • Release note: OpenCode Zen and OpenCode Go use the opencode.ai/inference endpoints, which fixes OpenCode Go Gemini models failing with a 404; OpenCode Go MiniMax M2.7 uses the Anthropic Messages API, and OpenCode Go DeepSeek V4 Flash is no longer listed (carried Pi fix, Pi 195120a9).

BF-efded13

Installing a Package or Piglet from a local git:file:// source on Windows no longer fails with `fatal…

  • Source: PiG's own defect
  • Symptom: Installing a Package or Piglet from a local git:file:// source on Windows no longer fails with fatal: '$GIT_DIR' too big.
  • Root cause: A file:// source checked out to <root>/localhost/<whole absolute path>, doubling its length; Git for Windows then failed with "$GIT_DIR too big" whatever core.longpaths says. A file:// source now checks out to <host>/<12 hex of sha256(absolute path)>/<basename>. Sources Pi accepts keep Pi's layout. Older nested file:// checkouts are not reused.
  • User impact: An operation failed or was rejected
  • Platform: Windows
  • Fixed in: 0.5.3
  • Fix commits: efded13767 (2026-10-22)
  • Tests: coding/cli/git_checkout_path_test.go::TestGitCheckoutPathOnThisPlatform, coding/cli/git_checkout_path_test.go::TestGitCheckoutRelativePerPlatform, internal/packagemanager/git_checkout_relative_test.go::TestGitCheckoutRelativeFileSourceIsShort, internal/packagemanager/git_checkout_relative_test.go::TestGitCheckoutRelativeFileSourceLengthBound, internal/packagemanager/git_checkout_relative_test.go::TestGitCheckoutRelativeNonFileKeepsPiLayout, internal/packagemanager/git_checkout_relative_test.go::TestGitCheckoutRelativeSameBasenameDiffers, internal/packagemanager/git_checkout_relative_test.go::TestGitCheckoutRelativeWindowsFileSourceUnderScratchRootIsShort
  • Release note: Installing a Package or Piglet from a local git:file:// source on Windows no longer fails with fatal: '$GIT_DIR' too big. A file URL now checks out to <install root>/localhost/<12 hex of the path's sha256>/<basename> instead of re-nesting the whole absolute path, which pushed $GIT_DIR past Git for Windows' own path limit that core.longpaths does not lift. Sources Pi accepts keep Pi's exact layout. A file URL checkout made by an earlier version is not reused and is cloned again.

BF-f02269c

When an extension's before_provider_request handler replaces an Anthropic request, or a Codex WebSocket…

  • Source: PiG's own defect
  • Symptom: When an extension's before_provider_request handler replaces an Anthropic request, or a Codex WebSocket request carries raw JSON, nested values are written as Pi's JSON.stringify writes them after JSON.parse: \u00e9 becomes é, and a nested object's repeated key keeps its first place with its last value.
  • Root cause: One part of is still missing there: writeJSONObjectMembers copied each value's text (json.Compact). Pi sends JSON.stringify({ ...nextParams, stream: true }) of what JSON.parse read (anthropic-messages.ts:640-642), so \u00e9 goes out as é, and a nested repeated key keeps its first place with its last value. writeJSONObjectMembers now writes each value through jsonstringify.AppendCanonical. The Codex WebSocket writer shares it; a body from marshalRequestBody is already canonical and takes the copy path.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f02269c6e1 (2026-10-10)
  • Tests: ai/anthropic_replaced_payload_order_oracle_test.go::TestAnthropicReplacedPayloadKeepsPiMemberOrder
  • Release note: When an extension's before_provider_request handler replaces an Anthropic request, or a Codex WebSocket request carries raw JSON, nested values are written as Pi's JSON.stringify writes them after JSON.parse: \u00e9 becomes é, and a nested object's repeated key keeps its first place with its last value. PiG copied the replacement's text unchanged.

BF-f06ab7e

A run that sendMessage({ triggerTurn: true }) starts while idle emits before_agent_start, checks…

  • Source: PiG's own defect
  • Symptom: A run that sendMessage({ triggerTurn: true }) starts while idle emits before_agent_start, checks compaction first, applies the handlers' result and delivers pending nextTurn messages.
  • Root cause: PiG did this only for idle triggerTurn custom messages (D139) and prepared a user prompt idle.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1cc200dcb5 (2026-10-12), 4c36280562 (2026-10-13), f06ab7eca6 (2026-10-13)
  • Tests: agent/abort_before_first_request_test.go::TestAbortBeforeTheFirstRequestSendsNoProviderRequest, coding/session_pi_bugs_test.go::TestCustomRunStartThatLosesTheClaimRejectsWithTheAgentError, coding/session_preflight_test.go::TestAbandonedRunStartReturnsTheSessionToIdle, coding/session_preflight_test.go::TestPromptRunStartClaimsTheSessionBeforePreparation, coding/session_prompt_abort_starting_upstream_test.go::TestUpstreamSessionPromptAbortWhileBeforeAgentStartRuns, coding/session_prompt_custom_run_upstream_test.go::TestUpstreamSessionPromptRunsStartedByACustomMessage, coding/session_prompt_start_run_test.go::TestUserPromptRunIsBusyWhileItStarts
  • Release note: A run that sendMessage({ triggerTurn: true }) starts while idle emits before_agent_start, checks compaction first, applies the handlers' result and delivers pending nextTurn messages. The event carries messageType ("user" or "custom") and, for a custom run, message. A user prompt's run, like a custom one, is busy from its start, as in Pi 1.2.0: a message sent while its compaction check and before_agent_start handlers run joins it, a prompt or sendUserMessage sent meanwhile must name streamingBehavior/deliverAs, bash results recorded meanwhile are appended after the handlers, and an abort aborts the run at agent_start instead of letting its request complete (Pi #5581, Pi PR #10739).

BF-f07d033

A response stream dropped mid-run now reports terminated (cause: ...) instead of a bare terminated (D144…

  • Source: A Pi defect that PiG fixed first (divergence D144)
  • Symptom: A response stream dropped mid-run now reports terminated (cause: ...) instead of a bare terminated (D144, Pi #10697).
  • Root cause: A user cannot tell a server disconnect from a client timeout without instrumenting the process.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10697
  • Removed when Pi fixes it: Pi includes error.cause in the message of a transport failure.
  • Fix commits: f07d033aac (2026-10-10)
  • Tests: ai/transport_error_test.go::TestProviderTransportFailureMidStreamKeepsTheCause, ai/transport_error_test.go::TestProviderTransportFailureMidStreamMatchesTerminated
  • Release note: A response stream dropped mid-run now reports terminated (cause: ...) instead of a bare terminated (D144, Pi #10697).

BF-f0b910f

Extensions' ui_prompt_start and ui_prompt_end notifications are recorded in order when the host is slow…

  • Source: PiG's own defect
  • Symptom: Extensions' ui_prompt_start and ui_prompt_end notifications are recorded in order when the host is slow to apply the start event's ctx.ui.notify: the dialog's answer is no longer let through, and the end event no longer sent, before the handler's own host call has applied.
  • Root cause: A runtime reports the suspension of a handler that waits for a host call before it sends the call. A start notification applied late (a stalled goroutine on a loaded machine) was recorded after the answers settled and the end event's notification, where Pi runs each handler's synchronous part before the prompting extension continues (TestConformance_ConcurrentDialogsAgreeAcrossSDKs/fused-go/last_in_first_out failed once in three full runs: the first ui_prompt event was the end).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.3
  • Fix commits: f0b910f8ca (2026-10-22)
  • Tests: coding/extension/host/subprocess/ack_hold_test.go::TestBlockedAcknowledgmentWaitsForTheAnnouncedHostCall, test/extension-conformance/ui_concurrent_dialogs_test.go::TestConformance_ConcurrentDialogsAgreeAcrossSDKs, test/extension-conformance/ui_concurrent_dialogs_test.go::TestConformance_UIPromptStartIsAppliedBeforeTheDialogsAnswerSettles
  • Release note: Extensions' ui_prompt_start and ui_prompt_end notifications are recorded in order when the host is slow to apply the start event's ctx.ui.notify: the dialog's answer is no longer let through, and the end event no longer sent, before the handler's own host call has applied. A blocked-on-a-host-call report from an extension runtime admitted the UI prompt drain as soon as it was read, while the call it announced was still queued; the admission now waits for that call.

BF-f12896b

Interactive startup writes the terminal color query before its first frame, as Pi does: starting the renderer…

  • Source: PiG's own defect
  • Symptom: Interactive startup writes the terminal color query before its first frame, as Pi does: starting the renderer only requests the first frame, which paints while startup waits for the terminal's colors.
  • Root cause: Pi's TUI.start() requests the first frame instead of painting it, and init() writes the color query in applyFromSettings() before the deferred frame runs at await waitForTerminalColors(). Init now mounts the layout, starts the renderer with a requested frame (TuiAltScreen.StartRequestingRender in fullscreen), and the color wait serves scheduled renders, so the frame paints during the wait. A live tui-mode switch still paints at once.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f12896bcc6 (2026-10-13)
  • Tests: internal/codingagent/interactive_init_state_pty_test.go::TestInitBuildsTheInteractiveStateBeforeTheSessionBindChild, internal/codingagent/startup_color_wait_render_test.go::TestRenderRequestedWhileStartupWaitsForTheTerminalColorsPaints, internal/codingagent/startup_query_order_pty_test.go::TestStartupWritesTheTerminalColorQueryBeforeTheFirstFrame, internal/codingagent/startup_query_order_pty_test.go::TestStartupWritesTheTerminalColorQueryBeforeTheFirstFrameChild
  • Release note: Interactive startup writes the terminal color query before its first frame, as Pi does: starting the renderer only requests the first frame, which paints while startup waits for the terminal's colors. In fullscreen the alternate screen is still entered first. The color round trip is now under way when the editor first shows, so input typed at the first frame waits on the same startup work as in Pi.

BF-f19efb1

A session_compact handler's passive custom message reaches the overflow retry (D138, Pi #9051)

  • Source: A Pi defect that PiG fixed first (divergence D138)
  • Symptom: A session_compact handler's passive custom message reaches the overflow retry (D138, Pi #9051).
  • Root cause: Pi #9051 (PR #9738 flush), Pi #5581 (PR #10739 event shape and run preparation).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/9051
  • Removed when Pi fixes it: Pi flushes deferred custom messages before an overflow retry.
  • Fix commits: f19efb1eb7 (2026-10-09)
  • Tests: coding/extension/extension_event_test.go::TestBeforeAgentStartEventWireShape, coding/session_pi_bugs_test.go::TestIdleCustomMessageRunEmitsBeforeAgentStart, coding/session_pi_bugs_test.go::TestOverflowRetrySeesCustomMessagesPublishedBySessionCompact, internal/codingagent/interactive_custom_prompt_event_test.go::TestCustomSeedEmitsBeforeAgentStart
  • Release note: A session_compact handler's passive custom message reaches the overflow retry (D138, Pi #9051).

BF-f1a59f9

An OpenAI Responses event's output_index matches an output item as a key of Pi's Map does: a string…

  • Source: PiG's own defect
  • Symptom: An OpenAI Responses event's output_index matches an output item as a key of Pi's Map does: a string, null, a boolean or a missing index is its own key rather than a decode error or item 0, and an object never matches.
  • Root cause: The red claim above is re-checked on this base: the 9 new rows run against the parent commit's production code.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 5078f73100 (2026-10-11), 5932542c21 (2026-10-11), 82414338e7 (2026-10-11), 8eb1202528 (2026-10-11), b6afa7be1a (2026-10-11), e1abc6c615 (2026-10-11), f1a59f9161 (2026-10-11)
  • Tests: ai/anthropic_event_member_type_oracle_test.go::TestAnthropicEventMistypedMembersMatchPi, ai/mistral_chunk_member_type_oracle_test.go::TestMistralChunkMistypedMembersMatchPi, ai/openai_responses_member_type_oracle_test.go::TestOpenAIResponsesMistypedMembersMatchPi
  • Release note: An OpenAI Responses event's output_index matches an output item as a key of Pi's Map does: a string, null, a boolean or a missing index is its own key rather than a decode error or item 0, and an object never matches.

BF-f20cc08

A Node extension that calls pi.appendEntry many times in one handler no longer slows down with the size of…

  • Source: PiG's own defect
  • Symptom: A Node extension that calls pi.appendEntry many times in one handler no longer slows down with the size of the session: each append now costs the same, as in Pi.
  • Root cause: Upstream SessionManager.appendCustomEntry pushes the entry, sets byId and moves the leaf: O(1). Since every pi.appendEntry goes through the runtime's RuntimeSessionManager, which copied the pending list, rescanned the whole replicated log for confirmed entries and rebuilt the id index on every append, so k appends in one handler over an n-entry log cost O(k*(n+k)).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 8d990c5d54 (2026-10-07), 31cde9f009 (2026-10-09), 7489735c3f (2026-10-09), f20cc081d5 (2026-10-09)
  • Tests: coding/cli/rpc_append_entry_visible_test.go::TestRPCNodeAppendEntryIsVisibleToTheSameHandlerAndEmitsEntryAppended, coding/extension/host/inproc/upstream_scoped_models_test.go::TestContextThinkingLevelReadsTheBoundRuntimeLive, coding/extension/host/subprocess/builder_node_entry_test.go::TestResolveNodeEntrypointImportsAnIndexMjsDirectoryAsJiti, test/extension-conformance/api_append_entry_test.go::TestConformance_ExtensionAPIAppendEntry, test/parity/runner/snapshot_test.go::TestSnapshotExtensionPathKeepsADirectoryExtensionsName, test/parity/scenarios/extensions-runtime/75-robust-notes-go.toml, test/parity/scenarios/extensions-runtime/75-robust-notes-node.toml, test/parity/scenarios/extensions-runtime/75-robust-notes-python.toml and 1 more in docs/bug-ledger.json
  • Release note: A Node extension that calls pi.appendEntry many times in one handler no longer slows down with the size of the session: each append now costs the same, as in Pi.

BF-f24a8ee

Fixed Markdown links not being clickable in Herdr: TERM_PROGRAM=herdr is now detected as supporting OSC 8…

  • Source: PiG's own defect
  • Symptom: Fixed Markdown links not being clickable in Herdr: TERM_PROGRAM=herdr is now detected as supporting OSC 8 hyperlinks, with images off.
  • Root cause: Ports packages/tui/src/terminal-image.ts TERM_PROGRAM=herdr branch (hyperlinks on, images off, before outer-terminal detection) and tui-alt-screen.ts resetTextSelection, called from renderSessionEntries (interactive-mode.ts, #9311).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f24a8eef3e (2026-10-07)
  • Tests: internal/codingagent/interactive_transcript_selection_test.go::TestRenderSessionEntriesDropsFullscreenSelection, tui/terminal_image_upstream_test.go::TestUpstreamTerminalImageCapabilities, tui/tui_alt_screen_test.go::TestAltScreenResetTextSelectionDropsSelectionAndClickHistory
  • Release note: Fixed Markdown links not being clickable in Herdr: TERM_PROGRAM=herdr is now detected as supporting OSC 8 hyperlinks, with images off.

BF-f24a8ee-2

Fixed the fullscreen text selection and multi-click state surviving a session switch or other transcript…

  • Source: PiG's own defect
  • Symptom: Fixed the fullscreen text selection and multi-click state surviving a session switch or other transcript rebuild, which highlighted unrelated text in the new transcript.
  • Root cause: Ports packages/tui/src/terminal-image.ts TERM_PROGRAM=herdr branch (hyperlinks on, images off, before outer-terminal detection) and tui-alt-screen.ts resetTextSelection, called from renderSessionEntries (interactive-mode.ts, #9311).
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f24a8eef3e (2026-10-07)
  • Tests: internal/codingagent/interactive_transcript_selection_test.go::TestRenderSessionEntriesDropsFullscreenSelection, tui/terminal_image_upstream_test.go::TestUpstreamTerminalImageCapabilities, tui/tui_alt_screen_test.go::TestAltScreenResetTextSelectionDropsSelectionAndClickHistory
  • Release note: Fixed the fullscreen text selection and multi-click state surviving a session switch or other transcript rebuild, which highlighted unrelated text in the new transcript.

BF-f25348f

A turn_end or agent_before_settle handler in an extension process receives each…

  • Source: PiG's own defect
  • Symptom: A turn_end or agent_before_settle handler in an extension process receives each context.contextEntries[].sourceEntry with its members in the order the session wrote them, as in Pi, instead of sorted by name.
  • Root cause: Tests: inproc TestEmitBoundaryDecodesSourcesOnlyForHandlersThatReadGoValues (Go handlers get decoded sources in their own preview, JSON handlers none, an all-JSON dispatch decodes nothing); subprocess TestBoundaryWireEncodesEachSourceAsItsRecord (records on the wire, decoded and encoded after a Go handler, no sharing then); the surrogate test now goes through Deliver. Mutants killed: Deliver never called, Deliver for every handler, Encoded ignoring delivery, Deliver not marking delivery, the wire ignoring the records.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 34f7594aa5 (2026-10-11), f25348fcf6 (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, coding/extension/host/inproc/boundary_test.go::TestEmitBoundaryDecodesSourcesOnlyForHandlersThatReadGoValues, coding/extension/host/subprocess/boundary_wire_test.go::TestBoundaryEventWireEncodesAsTheEvent, coding/extension/host/subprocess/boundary_wire_test.go::TestBoundaryWireCacheSharesOnlyAcrossUnchangedInputs, coding/extension/host/subprocess/boundary_wire_test.go::TestBoundaryWireEncodesEachSourceAsItsRecord and 15 more in docs/bug-ledger.json
  • Release note: A turn_end or agent_before_settle handler in an extension process receives each context.contextEntries[].sourceEntry with its members in the order the session wrote them, as in Pi, instead of sorted by name.

BF-f2790f9

A tui input listener or light/dark listener that is added while input is dispatched now sees that input…

  • Source: PiG's own defect
  • Symptom: A tui input listener or light/dark listener that is added while input is dispatched now sees that input, and one that is removed before its turn no longer does, as Pi's listener sets iterate.
  • Root cause: Pi's TuiBase keeps inputListeners and terminalColorSchemeListeners in Sets that handleTerminalInput iterates with for...of (tui.ts:517-518, 1052-1062, 1174-1176): a listener added during dispatch sees the same input and one removed before its turn does not (probed on pi-tui TuiMainScreen: [a:x c:x]). RunInputListeners and ConsumeTerminalColorSchemeReport iterated a snapshot ([a:x b:x]). A liveSet iterates by position and tombstones removals made during an iteration. TestInputListenersIterateLiveAsPisSets fails on the snapshot, on an in-iteration delete, and on undropped tombstones.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f2790f9e0a (2026-10-09)
  • Tests: tui/tui_input_pipeline_test.go::TestInputListenersIterateLiveAsPisSets
  • Release note: A tui input listener or light/dark listener that is added while input is dispatched now sees that input, and one that is removed before its turn no longer does, as Pi's listener sets iterate.

BF-f288d69

MCP OAuth token and client registration responses keep empty lists, read expires_in like JavaScript's…

  • Source: PiG's own defect
  • Symptom: MCP OAuth token and client registration responses keep empty lists, read expires_in like JavaScript's Number(), treat null timestamps as absent, and keep registration members PiG does not model.
  • Root cause: The release commit's --move-unreleased moves them under [0.4.2].
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): f288d69087 (2026-10-06)
  • Tests: None recorded.
  • Release note: MCP OAuth token and client registration responses keep empty lists, read expires_in like JavaScript's Number(), treat null timestamps as absent, and keep registration members PiG does not model. A registration response keeps token_endpoint_auth_method and the other registered members when one member has an unexpected type, so the token request uses the authentication method the server registered, as in Pi.

BF-f288d69-2

On Windows, an npm self-update no longer fails when a virus scanner or indexer holds pig.exe open for a…

  • Source: A Pi defect that PiG fixed first (divergence D99)
  • Symptom: On Windows, an npm self-update no longer fails when a virus scanner or indexer holds pig.exe open for a moment.
  • Root cause: The release commit's --move-unreleased moves them under [0.4.2].
  • User impact: An operation failed or was rejected
  • Platform: Windows
  • Fixed in: 0.5.0
  • Pi issue: none recorded
  • Removed when Pi fixes it: Pi retries the quarantine rename, or Windows stops refusing to rename a file that another process holds open.
  • Commit that wrote the release note (the fix commit is not identified): 6a96295659 (2026-10-06, #161), cbbaa8d2d7 (2026-10-06), f288d69087 (2026-10-06)
  • Tests: coding/cli/self_update_windows_test.go::TestWindowsNpmSelfUpdateReplacesTheRunningInstallation, internal/codingagent/windows_self_update_windows_test.go::TestQuarantineWaitsOutAHandleHeldOnTheRunningImage
  • Release note: On Windows, an npm self-update no longer fails when a virus scanner or indexer holds pig.exe open for a moment. The update retries moving the loaded images for up to 10 seconds; any other error fails the update as before (D99).

BF-f288d69-3

ModelRuntime.Close cancels the remote model catalog's refreshes and waits for them, so none runs on after…

  • Source: PiG's own defect
  • Symptom: ModelRuntime.Close cancels the remote model catalog's refreshes and waits for them, so none runs on after Close returns, and a refresh requested after Close does not start.
  • Root cause: The release commit's --move-unreleased moves them under [0.4.2].
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): f288d69087 (2026-10-06)
  • Tests: coding/mcpext/connection_test.go::TestMCPConnectionsClosesAConnectionThatIsStillInitializingBeforeCloseReturns, coding/mcpext/connection_test.go::TestMCPConnectionsStopsWaitingToRetryAConnectionWhenClosed
  • Release note: ModelRuntime.Close cancels the remote model catalog's refreshes and waits for them, so none runs on after Close returns, and a refresh requested after Close does not start.

BF-f288d69-4

Crash attribution reads Go goroutine traces as well as JavaScript stacks, so a crash in the PiG process…

  • Source: PiG's own defect
  • Symptom: Crash attribution reads Go goroutine traces as well as JavaScript stacks, so a crash in the PiG process carries stack frames (D100).
  • Root cause: The release commit's --move-unreleased moves them under [0.4.2].
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): f288d69087 (2026-10-06)
  • Tests: None recorded.
  • Release note: Crash attribution reads Go goroutine traces as well as JavaScript stacks, so a crash in the PiG process carries stack frames (D100).

BF-f29e906

OpenAI Completions, OpenAI Responses and Codex request bodies list their fields in the order Pi writes them…

  • Source: PiG's own defect
  • Symptom: OpenAI Completions, OpenAI Responses and Codex request bodies list their fields in the order Pi writes them, and sampling parameters from models.json or the request replace a field in place and append new fields after the rest, as Pi's Object.assign does, instead of sorting every key of the body.
  • Root cause: JSON.stringify writes params in the order Pi's builders create the keys, and Object.assign(params, samplingParams) keeps an existing key's position and appends a new one. PiG sorted every key of the body whenever sampling parameters or a non-default thinking budget field were set (marshal -> map[string]any -> marshal), and its struct order differed from Pi even without them (stream_options, store and prompt_cache_key landed late, tools early).
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f29e906cb3 (2026-10-10)
  • Tests: ai/anthropic_messages_payload_oracle_test.go::TestAnthropicMessagesPayloadMatchesPi, ai/openai_request_key_order_oracle_test.go::TestOpenAICompletionsRequestKeysFollowPiAssignmentOrder, ai/openai_responses_request_key_order_oracle_test.go::TestOpenAIResponsesRequestKeysFollowPiAssignmentOrder
  • Release note: OpenAI Completions, OpenAI Responses and Codex request bodies list their fields in the order Pi writes them, and sampling parameters from models.json or the request replace a field in place and append new fields after the rest, as Pi's Object.assign does, instead of sorting every key of the body.

BF-f2a2b34

An unmatched tool result row in the /tree selector shows the tool's name, as in Pi ([bash]), instead of…

  • Source: PiG's own defect
  • Symptom: An unmatched tool result row in the /tree selector shows the tool's name, as in Pi ([bash]), instead of [tool].
  • Root cause: Label timestamps are toggled off before any save because a label saved now shows the wall clock.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): f2a2b3409b (2026-10-07)
  • Tests: internal/codingagent/tree_row_format_test.go::TestFormatTreeRow_AllEntryTypes, internal/codingagent/tree_selector_oracle_test.go::TestTreeSelectorInputMatchesPi
  • Release note: An unmatched tool result row in the /tree selector shows the tool's name, as in Pi ([bash]), instead of [tool].

BF-f2c8b02

After await ctx.reload(), a command's captured pi and ctx now throw Pi's stale message for every…

  • Source: PiG's own defect
  • Symptom: Members an SDK answers locally, such as ctx.cwd, getFlag, getCommands, getThinkingLevel, getSystemPrompt and isProjectTrusted, kept answering for the replaced extension instance.
  • Root cause: retireGeneration now sends the invalidate notify (Pi's stale message) to a replaced generation whose stop a running command defers, under retirementMu, so it precedes both the reply to the reload and the deferred stop's shutdown. An immediately stopped generation needs none (its runtime invalidates as its connection ends); sending it there deadlocked a retained Node reload whose invalidation unsubscribes its shared-bus listeners while the reload waits for the process to close. The Go SDK's Context.Reload waits for the notifies queued before its result, as AppendEntry does, because its main loop applies notifies after the read loop routed the result.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f2c8b02bd9 (2026-10-10)
  • Tests: coding/extension/host/subprocess/generation_retirement_test.go::TestReplacedGenerationCallsFailWithThePiStaleMessage, test/extension-conformance/sdkscenario_test.go::TestSDKScenarios, test/parity/scenarios/extensions-runtime/77-reload-stale-context.toml
  • Release note: After await ctx.reload(), a command's captured pi and ctx now throw Pi's stale message for every member, in every SDK (Node, Go, Python and Rust). Before, members an SDK answers locally, such as ctx.cwd, getFlag, getCommands, getThinkingLevel, getSystemPrompt and isProjectTrusted, kept answering for the replaced extension instance.

BF-f34b545

Durable: resolving harness settings no longer shares the default retry policy's max agent delay between…

  • Source: PiG's own defect
  • Symptom: Durable: resolving harness settings no longer shares the default retry policy's max agent delay between harnesses.
  • Root cause: ResolveSettings now allocates its own.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f34b545a0c (2026-10-07)
  • Tests: durable/harness/agent_defaults_test.go::TestDefaultPoliciesMatchPiAndResolveByCopy, durable/harness/agent_defaults_test.go::TestDefineExtensionReturnsTheExtension, durable/harness/harness_conversations_test.go::TestHarnessRootAndConversations, durable/harness/harness_tasks_scheduling_test.go::TestTaskClose, durable/harness_types_test.go::TestCompactionReasonValuesMatchPi
  • Release note: Durable: resolving harness settings no longer shares the default retry policy's max agent delay between harnesses.

BF-f3b4045

.gitignore, .ignore and .fdignore rules that skills, packages and extension entry discovery read now…

  • Source: PiG's own defect
  • Symptom: .gitignore, .ignore and .fdignore rules that skills, packages and extension entry discovery read now follow the ignore package Pi uses: a pattern ending in a slash ignores directories only (a file named build survives build/), patterns match case-insensitively, a path inside an ignored directory stays ignored, a pattern with a slash before its end is anchored, [a-c] and [!a] bracket expressions work, and **/x, x/** and x/**/y follow Pi's forms.
  • Root cause: TestProviderRequestHeadersMatchPi reads the URL path, method and headers each API module sends through the pinned pi-ai's options.fetch and through PiG's HTTP client (60 requests per API with session ids, caller header overrides, API-key forms and providers; client-library and transport headers excluded). The OpenAI-compatible requests now send accept, and directAPIProviderFor wires the Copilot dynamic headers (and bearer auth for anthropic) when the model's provider is github-copilot.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2b3403045b (2026-10-09), 4333619e20 (2026-10-09), 5cec6423ef (2026-10-09), 794d676720 (2026-10-09), 7cfdcb77b3 (2026-10-09), ea72139272 (2026-10-09), f3b4045e8a (2026-10-10)
  • Tests: ai/anthropic_messages_payload_oracle_test.go::TestAnthropicMessagesPayloadMatchesPi, ai/anthropic_oauth_test.go::TestAnthropicHistoryToolNames, ai/auth_file_bytes_oracle_test.go::TestAuthFileBytesMatchPi, ai/google_generative_ai_payload_oracle_test.go::TestGoogleGenerativeAIPayloadMatchesPi, ai/google_generative_ai_payload_oracle_test.go::TestGoogleVertexPayloadMatchesPi, ai/mistral_payload_oracle_test.go::TestMistralPayloadModeAndEffortMatchPi, ai/oauth_credential_members_test.go::TestOAuthBuildersWriteCredentialMembersInPiOrder, ai/openai_responses_payload_oracle_test.go::TestOpenAICodexResponsesPayloadMatchesPi and 7 more in docs/bug-ledger.json
  • Release note: .gitignore, .ignore and .fdignore rules that skills, packages and extension entry discovery read now follow the ignore package Pi uses: a pattern ending in a slash ignores directories only (a file named build survives build/), patterns match case-insensitively, a path inside an ignored directory stays ignored, a pattern with a slash before its end is anchored, [a-c] and [!a] bracket expressions work, and **/x, x/** and x/**/y follow Pi's forms. A leading space in a pattern is kept, as in Pi.

BF-f4d85f8

A large file written through a tool call no longer slows down as it streams: each streamed piece of the…

  • Source: PiG's own defect
  • Symptom: A large file written through a tool call no longer slows down as it streams: each streamed piece of the tool-call arguments is parsed on its own instead of re-parsing all the arguments so far after a provider opened the call with empty arguments, so the cost of the whole call grows with its size instead of with the square of its size.
  • Root cause: changelog.d/perf-max.md: the user-visible quadratic large-write fix had no bullet. It states no time numbers because they were measured on a loaded shared host.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): f4d85f8a39 (2026-10-10)
  • Tests: ai/assistant_published_clone_test.go::TestCellObservationsCopyPublishedArgumentsUnread, ai/assistant_published_clone_test.go::TestObserveOfAnUnpublishedMessageStillChecksArgumentStrings, ai/streaming_json_resume_oracle_test.go::TestStreamingArgumentsParserMatchesPiAtEveryChunk
  • Release note: A large file written through a tool call no longer slows down as it streams: each streamed piece of the tool-call arguments is parsed on its own instead of re-parsing all the arguments so far after a provider opened the call with empty arguments, so the cost of the whole call grows with its size instead of with the square of its size.

BF-f5ecdc2

Settings and models.json compat objects keep the members PiG does not model, as Pi 1.2.0 does

  • Source: PiG's own defect
  • Symptom: Both were dropped on load (Pi 1.2.0 settings-manager.ts getGlobalSettings and deepMergeSettings, provider-composer.ts mergeCompat, config-schemas.test.ts).
  • Root cause: Pi keeps every member of settings.json (getGlobalSettings returns them, deepMergeSettings merges them) and every member of a compat object (ProviderCompatSchema allows additional properties; mergeCompat spreads them into the composed model). Settings now holds the top-level members settingsWire does not decode, after migrateSettings, and writes them in its JSON form; mergeSettings deep-merges them. ai.OpenAICompat keeps unmodeled members in Extra through decode, encode and mergeCompat.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 21654635e7 (2026-10-13), f5ecdc24d2 (2026-10-13)
  • Tests: coding/session_prompt_custom_run_upstream_test.go::TestUpstreamSessionPromptRunsStartedByACustomMessage, coding/session_tool_info_reaches_tools_test.go::TestGetAllToolsCarriesTheDefinitionsReachesTools, internal/codingagent/config_schemas_runtime_upstream_test.go::TestConfigSchemasAcceptCustomCompatibilitySettingsAndRejectMalformedKnownFields, internal/codingagent/config_schemas_runtime_upstream_test.go::TestConfigSchemasAcceptSchemaInKeybindingsJSONWithoutTreatingItAsAnAction, internal/codingagent/config_schemas_runtime_upstream_test.go::TestConfigSchemasAcceptSchemaInSettingsJSON, internal/codingagent/config_schemas_runtime_upstream_test.go::TestConfigSchemasValidateSchemaInModelsJSONAtRuntime, internal/codingagent/model_registry_merge_compat_fields_test.go::TestMergeCompatSpreadsEveryScalarCompatField, internal/codingagent/unmodeled_config_members_test.go::TestUnmodeledCompatMembersReachTheComposedModel and 12 more in docs/bug-ledger.json
  • Release note: Settings and models.json compat objects keep the members PiG does not model, as Pi 1.2.0 does. getGlobalSettings(), getSettings() and the settings JSON now include keys such as $schema or an extension's own setting, project values merge over global ones member by member, and only the legacy keys a migration rewrote (queueMode, websockets) are dropped. A provider or model compat object keeps keys such as a custom API's customOption, and the composed model carries them, so a custom API reads its own compat settings. Before, both were dropped on load (Pi 1.2.0 settings-manager.ts getGlobalSettings and deepMergeSettings, provider-composer.ts mergeCompat, config-schemas.test.ts).

BF-f6305d9

A new_session, fork, clone or switch_session in the same RPC batch as a compact aborts the…

  • Source: PiG's own defect
  • Symptom: A new_session, fork, clone or switch_session in the same RPC batch as a compact aborts the compaction instead of waiting for it (D148).
  • Root cause: Not recorded.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f6305d9e5a (2026-10-10)
  • Tests: coding/cli/rpc_replacement_order_test.go::TestRPCReplacementAbortsTheCompactionOfItsBatch
  • Release note: A new_session, fork, clone or switch_session in the same RPC batch as a compact aborts the compaction instead of waiting for it (D148).

BF-f68aa40

A delta.Tracker no longer keeps memory alive that Pi's tracker releases

  • Source: PiG's own defect
  • Symptom: A delta.Tracker no longer keeps memory alive that Pi's tracker releases.
  • Root cause:
    • AssertValidOp never checked path safety: it accepted [-1], [1.5], [true], [null] and nested segments that Pi's assertPathArg rejects. - Its messages carried an "invalid delta operation: " prefix and wrong names ("t payload" for Pi's "t shape", "invalid ... path" for "path is empty"). - ApplyImmutable rejected a numeric segment on an object; Pi sets that key: applyImmutable({a:1}, [["s",[1],2]]) is {"1":2,"a":1}. - The public path_error test expected ["a","b","c"] where Pi reports the unresolvable container ["a","b"].
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 1f5a782c0a (2026-10-08), 26f831bec5 (2026-10-08), 6ca5fde3a9 (2026-10-08), d4e4a82ed9 (2026-10-08), f2d7795fbb (2026-10-08), 301e3c185f (2026-10-09), 3e5b73b24a (2026-10-09), 8c7bacff5b (2026-10-09), cb15f9d6b6 (2026-10-09), f68aa400e5 (2026-10-09)
  • Tests: chord/delta/apply_immutable_upstream_test.go::TestCheckedImmutableOperationApplication, chord/delta/apply_immutable_upstream_test.go::TestImmutableApplicationComplexity, chord/delta/apply_numeric_key_test.go::TestApplyImmutableSetsANumericSegmentOnAnObjectAsAKey, chord/delta/apply_pi_oracle_test.go::TestApplyResolvesArraySegmentsInUpstreamOrder, chord/delta/apply_pi_oracle_test.go::TestTruncateInsideASurrogatePairKeepsTheLowHalf, chord/delta/compat_test.go::TestOpsDecodeKeepsObjectKeyOrder, chord/delta/compat_test.go::TestTheReleasedAPIKeepsWorking, chord/delta/delta_pi_differential_test.go::TestDeltaMatchesPiOnRandomCases and 41 more in docs/bug-ledger.json
  • Release note: A delta.Tracker no longer keeps memory alive that Pi's tracker releases. A settled Change, a draft handle kept after its change settled, and a Prepared no longer keep the draft, the base revision or the Tracker alive. An aborted change no longer stays registered with its tracker until the next adoption, and an open change the caller drops is reclaimed. Each case is one of Pi's retention.test.ts scenarios.

BF-f68aa40-2

delta.CloneJSON, chord.CopyJSON and draft placements report a Go struct or pointer with Pi's message for…

  • Source: PiG's own defect
  • Symptom: The message named the Go kind.
  • Root cause:
    • AssertValidOp never checked path safety: it accepted [-1], [1.5], [true], [null] and nested segments that Pi's assertPathArg rejects. - Its messages carried an "invalid delta operation: " prefix and wrong names ("t payload" for Pi's "t shape", "invalid ... path" for "path is empty"). - ApplyImmutable rejected a numeric segment on an object; Pi sets that key: applyImmutable({a:1}, [["s",[1],2]]) is {"1":2,"a":1}. - The public path_error test expected ["a","b","c"] where Pi reports the unresolvable container ["a","b"].
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6f0c4625ee (2026-10-07), df51d7b7f8 (2026-10-07), 1804eaf46b (2026-10-08), 1f5a782c0a (2026-10-08), 26f831bec5 (2026-10-08), 2f86645d6b (2026-10-08), 33c35623be (2026-10-08), 5355335e3a (2026-10-08), 6ca5fde3a9 (2026-10-08), 6cd85b3523 (2026-10-08), 9d34ce6e4f (2026-10-08), b7325c7314 (2026-10-08), d722b24dfa (2026-10-08), d83a19a303 (2026-10-08), e31e231285 (2026-10-08), e6218c5dc4 (2026-10-08), f2d7795fbb (2026-10-08), 301e3c185f (2026-10-09), 3e5b73b24a (2026-10-09), 8c7bacff5b (2026-10-09), ac3759ebb7 (2026-10-09), cb15f9d6b6 (2026-10-09), f68aa400e5 (2026-10-09)
  • Tests: chord/delta/apply_immutable_upstream_test.go::TestCheckedImmutableOperationApplication, chord/delta/apply_immutable_upstream_test.go::TestImmutableApplicationComplexity, chord/delta/apply_numeric_key_test.go::TestApplyImmutableSetsANumericSegmentOnAnObjectAsAKey, chord/delta/apply_pi_oracle_test.go::TestApplyResolvesArraySegmentsInUpstreamOrder, chord/delta/apply_pi_oracle_test.go::TestTruncateInsideASurrogatePairKeepsTheLowHalf, chord/delta/compat_test.go::TestOpsDecodeKeepsObjectKeyOrder, chord/delta/compat_test.go::TestTheReleasedAPIKeepsWorking, chord/delta/delta_pi_differential_test.go::TestDeltaMatchesPiOnRandomCases and 51 more in docs/bug-ledger.json
  • Release note: delta.CloneJSON, chord.CopyJSON and draft placements report a Go struct or pointer with Pi's message for a value that is not a plain object (Value must contain strict JSON plain objects or arrays), and a Go func as Value contains a non-JSON function; expected strict JSON. Before, the message named the Go kind.

BF-f6e7d49

/tree opens and searches a 300 MB session faster than Pi: it reads the session's index and each message's…

  • Source: PiG's own defect
  • Symptom: /tree opens and searches a 300 MB session faster than Pi: it reads the session's index and each message's search text instead of decoding every entry.
  • Root cause: Per lead rulings 01:42Z, 02:20Z item 3 and 04:25Z item 2 ((A)+(B)):
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 27d40bded4 (2026-10-10), 6e31cf8261 (2026-10-10), 747f9e050e (2026-10-10), 7d65c14f4c (2026-10-10), 8b65157e36 (2026-10-10), c9ba8f3dbf (2026-10-10), deadeb8d16 (2026-10-10), e097327828 (2026-10-10), f6e7d49d7b (2026-10-10), f7821fbbf9 (2026-10-10)
  • Tests: bench/sessionscale/cmd/sessionscale/gate_test.go::TestResumeMemoryAndPromptCostFollowTheContext, bench/sessionscale/gen_test.go::TestGenerateWritesTheScaledOwnerShape, coding/cli/rpc_mode_test.go::TestRPCTreeSerializesUpstreamShape, internal/codingagent/session_lazy_test.go::TestHandleReadsMatchDecodedReads, internal/codingagent/session_lazy_test.go::TestLazyEntriesKeepTheirTimestamps, internal/codingagent/session_lazy_test.go::TestLazyEntryOfAChangedRecordIsAnError, internal/codingagent/session_store_norace_test.go::TestIndexKeepsNoLargeSearchText, internal/codingagent/session_store_norace_test.go::TestResumedSessionRetainsContextNotHistory and 22 more in docs/bug-ledger.json
  • Release note: /tree opens and searches a 300 MB session faster than Pi: it reads the session's index and each message's search text instead of decoding every entry. RPC get_entries and get_tree, extension session reads and branch summaries read message records from the session file only when they need them, so these whole-history reads no longer decode the history.

BF-f6e7d49-2

A session without a file (--no-session) keeps the records of entries behind its latest compaction…

  • Source: PiG's own defect
  • Symptom: A session without a file (--no-session) keeps the records of entries behind its latest compaction compressed instead of decoded.
  • Root cause: Per lead rulings 01:42Z, 02:20Z item 3 and 04:25Z item 2 ((A)+(B)):
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 7d65c14f4c (2026-10-10), 8b65157e36 (2026-10-10), deadeb8d16 (2026-10-10), e097327828 (2026-10-10), f6e7d49d7b (2026-10-10)
  • Tests: bench/sessionscale/cmd/sessionscale/gate_test.go::TestResumeMemoryAndPromptCostFollowTheContext, coding/cli/rpc_mode_test.go::TestRPCTreeSerializesUpstreamShape, internal/codingagent/session_lazy_test.go::TestHandleReadsMatchDecodedReads, internal/codingagent/session_lazy_test.go::TestLazyEntriesKeepTheirTimestamps, internal/codingagent/session_lazy_test.go::TestLazyEntryOfAChangedRecordIsAnError, internal/codingagent/session_store_norace_test.go::TestResumedSessionRetainsContextNotHistory, internal/codingagent/session_store_norace_test.go::TestSessionWithoutFileRetainsCompressedHistory, internal/codingagent/session_store_norace_test.go::TestTurnCostIndependentOfHistoryBehindCompaction and 14 more in docs/bug-ledger.json
  • Release note: A session without a file (--no-session) keeps the records of entries behind its latest compaction compressed instead of decoded.

BF-f6e7d49-3

RPC get_entries, RPC get_tree and extension session reads now fail with an error naming the entry when…

  • Source: PiG's own defect
  • Symptom: RPC get_entries, RPC get_tree and extension session reads now fail with an error naming the entry when the session file was changed under PiG, instead of returning the entry's base members alone.
  • Root cause: Per lead rulings 01:42Z, 02:20Z item 3 and 04:25Z item 2 ((A)+(B)):
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 8b65157e36 (2026-10-10), c9ba8f3dbf (2026-10-10), deadeb8d16 (2026-10-10), e097327828 (2026-10-10), f6e7d49d7b (2026-10-10)
  • Tests: bench/sessionscale/cmd/sessionscale/gate_test.go::TestResumeMemoryAndPromptCostFollowTheContext, bench/sessionscale/gen_test.go::TestGenerateWritesTheScaledOwnerShape, coding/cli/rpc_mode_test.go::TestRPCTreeSerializesUpstreamShape, internal/codingagent/session_lazy_test.go::TestHandleReadsMatchDecodedReads, internal/codingagent/session_lazy_test.go::TestLazyEntriesKeepTheirTimestamps, internal/codingagent/session_lazy_test.go::TestLazyEntryOfAChangedRecordIsAnError, internal/codingagent/session_store_norace_test.go::TestResumedSessionRetainsContextNotHistory, internal/codingagent/session_store_norace_test.go::TestSessionWithoutFileRetainsCompressedHistory and 14 more in docs/bug-ledger.json
  • Release note: RPC get_entries, RPC get_tree and extension session reads now fail with an error naming the entry when the session file was changed under PiG, instead of returning the entry's base members alone.

BF-f73c00c

Replaying a thinking block that holds only U+FEFF drops it and one that holds only U+0085 keeps it, as…

  • Source: PiG's own defect
  • Symptom: Replaying a thinking block that holds only U+FEFF drops it and one that holds only U+0085 keeps it, as JavaScript trim() decides in Pi.
  • Root cause: Pi's transformMessages skips a thinking block whose text is empty after trim() (transform-messages.ts:111). Go's TrimSpace strips U+0085 and keeps U+FEFF, so a block of only U+FEFF was kept and replayed as text or thinking, and a block of only U+0085 was dropped, where Pi does the opposite. normalizeAssistantReplayBlocks now uses jsstring.Trim.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f73c00c391 (2026-10-11)
  • Tests: agent/agent_class_pi_oracle_test.go::TestAgentClassMatchesPi, agent/agent_loop_pi_oracle_test.go::TestAgentLoopMatchesPi, agent/transform_thinking_oracle_test.go::TestNormalizeAssistantReplayThinkingTrimMatchesPi
  • Release note: Replaying a thinking block that holds only U+FEFF drops it and one that holds only U+0085 keeps it, as JavaScript trim() decides in Pi.

BF-f78b951

fix(codingagent): cache the Markdown transformer list per theme and runner (TU-T6)

  • Source: PiG's own defect
  • Symptom: fix(codingagent): cache the Markdown transformer list per theme and runner (TU-T6)
  • Root cause: The assistant and user Markdown transforms apply the cached Mermaid and extension transformer list directly instead of building the list, a Mermaid closure and a transform closure per render. Drops the updateAssistantMessageBlockFromOwned alias (tui-ALIAS).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: f78b951554 (2026-10-13)
  • Tests: None recorded.

BF-f7b245a

A frontend member's main region now matches the transcript after an extension's custom entry is placed before…

  • Source: PiG's own defect
  • Symptom: A frontend member's main region now matches the transcript after an extension's custom entry is placed before the streaming message and a later child of the transcript changes in the same frame.
  • Root cause: pig piglet show lists the slot, the schema gains slots and removeSpec.slots, and a Binary's embedded Piglet carries no slots because the member is compiled in. The Binary record keeps the member's tree digest as a content-addressed frontend input.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f5190b9c48 (2026-10-04), 35ad0aaea9 (2026-10-05), 613c997d21 (2026-10-05), 69ee29f0d4 (2026-10-05), 70ca2f3ce2 (2026-10-05), 9846de83dc (2026-10-05), c027bb425f (2026-10-05), a03488a003 (2026-10-06), f7b245a8c1 (2026-10-08)
  • Tests: coding/piglet/frontend_dir_test.go::TestFrontendDirResolvesInsideThePigletDirectory, coding/piglet/frontend_slot_test.go::TestFrontendSlotInheritsReplacesAndRemoves, coding/piglet/piglet_test.go::TestParseBuildSpecRejectsInvalidDefaults, coding/piglet/schema_test.go::TestAC1SchemaParserAgreement, coding/pigletbuild/fuse_modules_test.go::TestMergeFusedModulesTakesTheHighestMemberVersionAndAnchorsLocalReplacements, coding/pigletbuild/native_build_test.go::TestOverlayFuseImportsFactoryPackageWithoutWritingGoMod, coding/pigletbuild/native_build_test.go::TestOverlayFuseRegistersFrontendMember, coding/pigletbuild/plan_test.go::TestValidateRejectsNonFusedExtensionWhenRequired and 98 more in docs/bug-ledger.json
  • Release note: A frontend member's main region now matches the transcript after an extension's custom entry is placed before the streaming message and a later child of the transcript changes in the same frame. Container.InsertBefore did not report the children it kept, so the frame spliced a stale range: the session kept the removed entry and missed the inserted one. An insert alone also walked the whole transcript instead of the inserted entry.

BF-f7c4b01

pig extension upgrade no longer changes, breaks or rejects a Go extension that already builds against the…

  • Source: PiG's own defect
  • Symptom: pig extension upgrade no longer changes, breaks or rejects a Go extension that already builds against the current SDK.
  • Root cause: fmt.Println(ctx.ConfigHome()) compiles against the current SDK: the value and the error spread into ...any, so no compile error marks it and the error is printed instead of handled. Find a getter call that is the only argument of a variadic call (or print/println) syntactically, in files without compile errors too, and rewrite it as the other argument positions: hoist it and return the error, or list a ConfigHome whose function cannot return it. A non-variadic call whose parameters take the value and the error is the new shape and stays. The fixture test now also fails when a file with a golden source is not rewritten.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 63bb0b7eac (2026-10-12), 974050757c (2026-10-12), d305b76bda (2026-10-12), f7c4b01ccc (2026-10-12)
  • Tests: coding/cli/extension_upgrade_test.go::TestUpgraderRestoresEveryFileWhenTheRebuildFails, coding/cli/extension_upgrade_test.go::TestUpgraderRestoresWrittenFilesWhenAWriteFails, coding/extension/host/runtimecell/go_sdk_override_test.go::TestGoSDKPathOverrideIgnoresRetiredStagedSDK, coding/extension/host/runtimecell/go_sdk_override_test.go::TestGoSDKPathOverrideKeepsCurrentStagedSDK, coding/extension/host/runtimecell/go_sdk_override_test.go::TestRustSDKPathOverrideIgnoresRetiredStagedSDK, coding/extension/host/subprocess/builder_test.go::TestGoNeedsStagedSDK, coding/extension/host/subprocess/builder_test.go::TestRustNeedsStagedSDK, extensions/sdk/upgrade/fixtures_test.go::TestFixturesUpgradeAndBuild and 6 more in docs/bug-ledger.json
  • Release note: pig extension upgrade no longer changes, breaks or rejects a Go extension that already builds against the current SDK. A getter passed straight into a variadic call, such as fmt.Println(ctx.GetActiveTools()), is current code when the getter returned its error in the SDK the extension's go.mod requires, so it is left as it is. The upgrade now rewrites only ConfigHome calls of this kind, in extensions that require an SDK older than 0.5.0, and lists such a call rather than discard its error. A call whose parameter for the error has the type error stays. Rewriting one of these calls no longer also rewrites ContextUsage.Tokens and Percent uses that were already correct. A getter in a select case is listed instead of making the upgrade fail with the rewrite does not parse.

BF-f7d701e

Tool argument validation asserts the format keyword for the formats TypeBox checks (date, time, date-time…

  • Source: PiG's own defect
  • Symptom: A value that violated a format passed validation.
  • Root cause: Pig compiled tool schemas without format assertions, so a value that breaks 'format' (email, uri, date-time...) passed where TypeBox's Check fails with 'must match format'. validate_formats.go ports typebox 1.3.27 build/format (17 of its 21 formats: date, time, date-time, duration, email, ipv4, ipv6, iri, iri-reference, json-pointer, json-pointer-uri-fragment, relative-json-pointer, uri, uri-reference, uri-template, url, uuid; the regexp sources are taken from the .mjs files; hostname, idn-hostname, idn-email and regex keep the schema library's checks) and registers them with AssertFormat.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e8cc072521 (2026-10-08), 093cf5c781 (2026-10-09), 6f4a82fd86 (2026-10-09), d1b3ad0151 (2026-10-09), d1cbde3321 (2026-10-09), d490a8b161 (2026-10-09), f7d701e67d (2026-10-09)
  • Tests: ai/error_body_oracle_test.go::TestErrorBodyHelpersMatchPiOnSeededProbes, ai/radius_config_oracle_test.go::TestRadiusConfigMatchesPiOnSeededProbes, ai/service_urls_oracle_test.go::TestProviderServiceURLsMatchPiOnSeededProbes, ai/validate_formats_oracle_test.go::TestTypeboxFormatsMatchPiOnSeededStrings, ai/validate_tool_args_oracle_test.go::TestValidateToolArgumentsMatchesPiOnSeededSchemas, chord/delta/own_key_order_test.go::TestDiffRevisionsVisitsArrayIndexKeysFirstInNumericOrder, internal/chord/chordjson/own_key_order_test.go::TestCopyFollowsPisKeyOrderAndMessages, internal/evals/report_oracle_test.go::TestEvalReportMatchesPiOnSeededDesigns
  • Release note: Tool argument validation asserts the format keyword for the formats TypeBox checks (date, time, date-time, duration, email, ipv4, ipv6, iri, iri-reference, json-pointer, json-pointer-uri-fragment, relative-json-pointer, uri, uri-reference, uri-template, url, uuid) with TypeBox's own rules. Before, a value that violated a format passed validation.

BF-f8673e6

Tool argument conversion for TypeBox schemas lower-cases with JavaScript rules (an İ in UNDEFİNED no…

  • Source: PiG's own defect
  • Symptom: Tool argument conversion for TypeBox schemas lower-cases with JavaScript rules (an İ in UNDEFİNED no longer converts to null) and matches Record patternProperties keys as the anchored expression ^pattern$, as TypeBox does.
  • Root cause: The generator now puts ~kind Object/Array/Tuple/Union on its nodes and adds a fixed matrix of kinds (Boolean, Number, Integer, String, Null, Literal, Array, Union, Tuple, Record) against 40+ padded, cased and typed argument spellings. Two differences found (typebox 1.3.27 build/value/convert): coerceArgumentPrimitive lower-cased with Go's ToLower, which maps U+0130 to 'i', so 'UNDEFİNED' converted to null where TypeBox's toLowerCase keeps 'i̇' (now jsstring.ToLower); and FromRecord tests each pattern as new RegExp('^'+key+'$') while Pig compiled it unanchored (mutant killed: pattern anchor in the converter). Equivalent mutants: ToLower->strings.ToLower on the 'true/false' comparisons (no…
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6f4a82fd86 (2026-10-09), a1fa6d1a33 (2026-10-09), d490a8b161 (2026-10-09), f7d701e67d (2026-10-09), f8673e6c49 (2026-10-09)
  • Tests: ai/node_http_proxy_oracle_test.go::TestResolveHTTPProxyURLForTargetMatchesPiOracle, ai/overflow_oracle_test.go::TestContextOverflowMatchesPiOnSeededMessages, ai/validate_formats_oracle_test.go::TestTypeboxFormatsMatchPiOnSeededStrings, ai/validate_tool_args_oracle_test.go::TestValidateToolArgumentsMatchesPiOnSeededSchemas, internal/evals/plan_oracle_test.go::TestPlanMatchesPiOnSeededProbes
  • Release note: Tool argument conversion for TypeBox schemas lower-cases with JavaScript rules (an İ in UNDEFİNED no longer converts to null) and matches Record patternProperties keys as the anchored expression ^pattern$, as TypeBox does.

BF-f90fedc

fix(cli): satisfy errcheck and gocritic in the validate-only change

  • Source: PiG's own defect
  • Symptom: fix(cli): satisfy errcheck and gocritic in the validate-only change
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: f90fedc1f1 (2026-10-22)
  • Tests: None recorded.

BF-f9648c6

On Windows, stopping a terminal reader whose input is a pipe no longer hangs

  • Source: PiG's own defect
  • Symptom: On Windows, stopping a terminal reader whose input is a pipe no longer hangs.
  • Root cause: TestInteractiveShutdownLeavesTerminalFileForNextReader timed out after 30m in Windows native-3 (run 40075386948, job 120845797941; twice more on 2026-10-12 and 2026-10-24). The dump has the test goroutine in interactiveTerminalReader.pause waiting for the reader session, and the session in tui.ReadInputChunk -> ReadFile on the pipe the test uses as its terminal.
  • User impact: Crash, hang or runaway process
  • Platform: Windows
  • Fixed in: Unreleased
  • Fix commits: 0869584873 (2026-10-24), f9648c6fd5 (2026-10-24)
  • Tests: internal/codingagent/keystroke_hop_test.go::TestKeystrokeReachesTheOwnerInOneHandOff, internal/codingagent/shutdown_input_owner_test.go::TestInteractiveShutdownLeavesTerminalFileForNextReader, tui/terminal_native_windows_test.go::TestWindowsInputWaiterTimesOutSignalsAndCancels, tui/terminal_pipe_windows_test.go::TestReadInputStreamDeliversAPipeToItsEnd, tui/terminal_pipe_windows_test.go::TestReadInputStreamLeavesAPipeForTheNextReader
  • Release note: On Windows, stopping a terminal reader whose input is a pipe no longer hangs. The reader waited on the input handle for input, but a pipe handle reads as ready whether or not input is queued, so a reader over a pipe went into a read that cancellation could not end, and Stop, shutdown and an external-editor handoff waited on it for good (seen as a 30 minute internal/codingagent test timeout in Windows CI). A pipe is now waited on in a zero-byte read, as Node's libuv does, which takes no input and which cancelling the reader ends, so a cancelled reader returns at once and leaves later input to the next reader. A console, which pig itself reads, is waited on as before.

BF-f98d129

Clearing the queued messages (RPC clear_queue, the SDK, the interactive restore-to-editor key) no longer…

  • Source: A Pi defect that PiG fixed first (divergence D135)
  • Symptom: Clearing the queued messages (RPC clear_queue, the SDK, the interactive restore-to-editor key) no longer destroys custom messages that an extension queued with sendMessage({ deliverAs }).
  • Root cause: Pi #9886, #10287, #10162. #9410 does not reproduce (test pins Pi main's behavior).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/9886
  • Removed when Pi fixes it: Pi's clearQueue stops destroying custom messages it does not return.
  • Fix commits: 5731351f4e (2026-10-09), f98d129beb (2026-10-09)
  • Tests: agent/image_limit_test.go::TestLimitImagesPerRequestKeepsTheNewestImages, agent/queue_clear_user_test.go::TestClearUserMessagesKeepsCustomMessagesInOrder, coding/session_pi_bugs_test.go::TestAbortInOverThresholdSessionDoesNotCompactBeforeSettling, coding/session_pi_bugs_test.go::TestClearQueueKeepsExtensionCustomMessages, coding/session_pi_bugs_test.go::TestContextUsageSurvivesAnOmittedFailedRetryAttempt, coding/session_pi_bugs_test.go::TestPromptSendsAtMostTheModelsImagesPerRequest, internal/codingagent/compaction/compaction_oracle_test.go::TestCompactionMatchesPi, internal/codingagent/compaction/context_edit_retry_anchor_test.go::TestEstimateDropsUsageAnchorWhenTheAnchorOrEarlierInputIsEdited and 3 more in docs/bug-ledger.json
  • Release note: Clearing the queued messages (RPC clear_queue, the SDK, the interactive restore-to-editor key) no longer destroys custom messages that an extension queued with sendMessage({ deliverAs }). They stay queued and run with the active run (D135, Pi #9886).

BF-f98d129-2

Omitting a failed retry attempt no longer makes the context usage jump to an estimate of the whole history

  • Source: A Pi defect that PiG fixed first (divergence D136)
  • Symptom: Omitting a failed retry attempt no longer makes the context usage jump to an estimate of the whole history.
  • Root cause: Pi #9886, #10287, #10162. #9410 does not reproduce (test pins Pi main's behavior).
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10287
  • Removed when Pi fixes it: Pi keeps the usage anchor across an edit that only touches later entries.
  • Fix commits: 5731351f4e (2026-10-09), f98d129beb (2026-10-09)
  • Tests: agent/image_limit_test.go::TestLimitImagesPerRequestKeepsTheNewestImages, agent/queue_clear_user_test.go::TestClearUserMessagesKeepsCustomMessagesInOrder, coding/session_pi_bugs_test.go::TestAbortInOverThresholdSessionDoesNotCompactBeforeSettling, coding/session_pi_bugs_test.go::TestClearQueueKeepsExtensionCustomMessages, coding/session_pi_bugs_test.go::TestContextUsageSurvivesAnOmittedFailedRetryAttempt, coding/session_pi_bugs_test.go::TestPromptSendsAtMostTheModelsImagesPerRequest, internal/codingagent/compaction/compaction_oracle_test.go::TestCompactionMatchesPi, internal/codingagent/compaction/context_edit_retry_anchor_test.go::TestEstimateDropsUsageAnchorWhenTheAnchorOrEarlierInputIsEdited and 2 more in docs/bug-ledger.json
  • Release note: Omitting a failed retry attempt no longer makes the context usage jump to an estimate of the whole history. A context edit that only touches entries after the last usage keeps that usage (D136, Pi #10287).

BF-f98d129-3

A request carries at most the model's inputLimits.images.maxPerRequest images

  • Source: A Pi defect that PiG fixed first (divergence D137)
  • Symptom: A request carries at most the model's inputLimits.images.maxPerRequest images.
  • Root cause: Pi #9886, #10287, #10162. #9410 does not reproduce (test pins Pi main's behavior).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Pi issue: https://github.com/earendil-works/pi/issues/10162
  • Removed when Pi fixes it: Pi enforces inputLimits.images.maxPerRequest on provider requests.
  • Fix commits: 5731351f4e (2026-10-09), f98d129beb (2026-10-09)
  • Tests: agent/image_limit_test.go::TestLimitImagesPerRequestKeepsTheNewestImages, agent/queue_clear_user_test.go::TestClearUserMessagesKeepsCustomMessagesInOrder, coding/session_pi_bugs_test.go::TestAbortInOverThresholdSessionDoesNotCompactBeforeSettling, coding/session_pi_bugs_test.go::TestClearQueueKeepsExtensionCustomMessages, coding/session_pi_bugs_test.go::TestContextUsageSurvivesAnOmittedFailedRetryAttempt, coding/session_pi_bugs_test.go::TestPromptSendsAtMostTheModelsImagesPerRequest, internal/codingagent/compaction/compaction_oracle_test.go::TestCompactionMatchesPi, internal/codingagent/compaction/context_edit_retry_anchor_test.go::TestEstimateDropsUsageAnchorWhenTheAnchorOrEarlierInputIsEdited and 2 more in docs/bug-ledger.json
  • Release note: A request carries at most the model's inputLimits.images.maxPerRequest images. Older images become a text placeholder in the request and stay in the session (D137, Pi #10162).

BF-f992050

fix(ai,durable): keep a value that is not JSON an error when a message or a nested result is encoded

  • Source: PiG's own defect
  • Symptom: fix(ai,durable): keep a value that is not JSON an error when a message or a nested result is encoded
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: f992050b6d (2026-10-24)
  • Tests: None recorded.

BF-fa3da97

A wake the scheduler took at a change is still delivered to OnWake when the Session fails and seals before…

  • Source: PiG's own defect
  • Symptom: A wake the scheduler took at a change is still delivered to OnWake when the Session fails and seals before the goroutine publishing it runs, as Pi's microtask ran first; a failing recovery pass no longer loses the host's active wake.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Lost or altered data
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A wake the scheduler took at a change is still delivered to OnWake when the Session fails and seals before the goroutine publishing it runs, as Pi's microtask ran first; a failing recovery pass no longer loses the host's active wake.

BF-fa3da97-10

A model reply with many tool calls no longer slows down as calls finish: finished calls are not parsed again…

  • Source: PiG's own defect
  • Symptom: A model reply with many tool calls no longer slows down as calls finish: finished calls are not parsed again on every later stream event.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A model reply with many tool calls no longer slows down as calls finish: finished calls are not parsed again on every later stream event.

BF-fa3da97-11

Durable task-graph and view watches no longer send or rebuild the whole graph or view on each commit: a…

  • Source: PiG's own defect
  • Symptom: Durable task-graph and view watches no longer send or rebuild the whole graph or view on each commit: a commit costs the same at any number of live tasks or entries.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Durable task-graph and view watches no longer send or rebuild the whole graph or view on each commit: a commit costs the same at any number of live tasks or entries.

BF-fa3da97-12

MCP: a server's tools, the tools it stopped offering, and the resource tools now reach the session as one…

  • Source: PiG's own defect
  • Symptom: MCP: a server's tools, the tools it stopped offering, and the resource tools now reach the session as one change, as Pi registers them in one synchronous turn.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: MCP: a server's tools, the tools it stopped offering, and the resource tools now reach the session as one change, as Pi registers them in one synchronous turn. A prompt that started while a server was connecting could see only some of its tools.

BF-fa3da97-13

On the Cloudflare Durable host, an AsyncLocalStorage value set around a call stays visible in the work that…

  • Source: PiG's own defect
  • Symptom: On the Cloudflare Durable host, an AsyncLocalStorage value set around a call stays visible in the work that call queues (turns, tool effects, timers and commit listeners), as in Pi.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: On the Cloudflare Durable host, an AsyncLocalStorage value set around a call stays visible in the work that call queues (turns, tool effects, timers and commit listeners), as in Pi.

BF-fa3da97-14

A Durable Session whose commit was cancelled while it waited for the Session line now closes instead of…

  • Source: PiG's own defect
  • Symptom: A Durable Session whose commit was cancelled while it waited for the Session line now closes instead of hanging in Close.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A Durable Session whose commit was cancelled while it waited for the Session line now closes instead of hanging in Close.

BF-fa3da97-15

An extension that registers many tools after load (pi.registerTool) no longer slows down with each…

  • Source: PiG's own defect
  • Symptom: An extension that registers many tools after load (pi.registerTool) no longer slows down with each registration.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: An extension that registers many tools after load (pi.registerTool) no longer slows down with each registration. The registration reply and state pushes no longer carry the whole tool catalog, and the Session no longer re-reads every tool on each registration. getAllTools and getActiveTools return the same results as before.

BF-fa3da97-16

A context event with several extension handlers is encoded once per request instead of once per handler…

  • Source: PiG's own defect
  • Symptom: A context event with several extension handlers is encoded once per request instead of once per handler, and a handler that leaves the conversation unchanged no longer sends it back.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A context event with several extension handlers is encoded once per request instead of once per handler, and a handler that leaves the conversation unchanged no longer sends it back.

BF-fa3da97-17

Rust SDK: a context handler that returns a new list equal to the one it received now counts as a…

  • Source: PiG's own defect
  • Symptom: Rust SDK: a context handler that returns a new list equal to the one it received now counts as a replacement, and an in-place edit marked with pig_sdk::context_unchanged() keeps system messages in place, as in Pi and the other SDKs.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Rust SDK: a context handler that returns a new list equal to the one it received now counts as a replacement, and an in-place edit marked with pig_sdk::context_unchanged() keeps system messages in place, as in Pi and the other SDKs. Return None for an unchanged conversation.

BF-fa3da97-18

A Durable core session with many live tasks recomputes its next wake without walking every live task on each…

  • Source: PiG's own defect
  • Symptom: A Durable core session with many live tasks recomputes its next wake without walking every live task on each commit: ending a chain of 20,000 held tasks takes under a second instead of about a minute.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A Durable core session with many live tasks recomputes its next wake without walking every live task on each commit: ending a chain of 20,000 held tasks takes under a second instead of about a minute.

BF-fa3da97-19

Idle and typing frames in a long interactive session no longer redraw from the header down: a frame costs the…

  • Source: PiG's own defect
  • Symptom: Idle and typing frames in a long interactive session no longer redraw from the header down: a frame costs the same at any transcript length.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Idle and typing frames in a long interactive session no longer redraw from the header down: a frame costs the same at any transcript length. Extension headers and footers still update every frame.

BF-fa3da97-2

env/daemon: listFiles with timeoutMs 0 truncates at once on Windows too; the deadline check no longer…

  • Source: PiG's own defect
  • Symptom: env/daemon: listFiles with timeoutMs 0 truncates at once on Windows too; the deadline check no longer misses on the platform's coarse clock.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: Windows
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: env/daemon: listFiles with timeoutMs 0 truncates at once on Windows too; the deadline check no longer misses on the platform's coarse clock.

BF-fa3da97-20

Markdown with many inline $ signs renders in linear time, as in Pi

  • Source: PiG's own defect
  • Symptom: Markdown with many inline $ signs renders in linear time, as in Pi.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Markdown with many inline $ signs renders in linear time, as in Pi.

BF-fa3da97-21

Extension SDKs: reading the session branch, an entry, a label or the leaf entry right after a new entry is…

  • Source: PiG's own defect
  • Symptom: Extension SDKs: reading the session branch, an entry, a label or the leaf entry right after a new entry is appended no longer costs time proportional to the session length, in the Go, Node, Python and Rust SDKs.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Extension SDKs: reading the session branch, an entry, a label or the leaf entry right after a new entry is appended no longer costs time proportional to the session length, in the Go, Node, Python and Rust SDKs.

BF-fa3da97-22

Choosing a bundled model no longer builds every model of its provider on each request, and providers in…

  • Source: PiG's own defect
  • Symptom: Choosing a bundled model no longer builds every model of its provider on each request, and providers in models.json with many custom models no longer slow down quadratically.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Choosing a bundled model no longer builds every model of its provider on each request, and providers in models.json with many custom models no longer slow down quadratically. Branch summaries of long sessions are prepared in linear time.

BF-fa3da97-23

chord/host: a host no longer sends on another host's connection while delivering notifications, and a…

  • Source: PiG's own defect
  • Symptom: chord/host: a host no longer sends on another host's connection while delivering notifications, and a reload that unwinds frees its slot and ends its operation together, so a following reload is not refused as already running.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: chord/host: a host no longer sends on another host's connection while delivering notifications, and a reload that unwinds frees its slot and ends its operation together, so a following reload is not refused as already running.

BF-fa3da97-24

A Durable code mode tool call made just before the script launches another call or ends has started before…

  • Source: PiG's own defect
  • Symptom: A Durable code mode tool call made just before the script launches another call or ends has started before the script goes on, as in Pi: a later call no longer overtakes it, and the end of the script no longer aborts it before its tool ran.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A Durable code mode tool call made just before the script launches another call or ends has started before the script goes on, as in Pi: a later call no longer overtakes it, and the end of the script no longer aborts it before its tool ran.

BF-fa3da97-25

Fixed pig piglet build failing on a Piglet that carries a single-file extension (a lone .ts or .js…

  • Source: PiG's own defect
  • Symptom: Fixed pig piglet build failing on a Piglet that carries a single-file extension (a lone .ts or .js file): locking it read <file>/go.mod, which fails with ENOTDIR rather than ENOENT, so the build stopped with "local Go replacements: not a directory".
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Fixed pig piglet build failing on a Piglet that carries a single-file extension (a lone .ts or .js file): locking it read <file>/go.mod, which fails with ENOTDIR rather than ENOENT, so the build stopped with "local Go replacements: not a directory". A file extension has no Go replacements and now locks like a directory extension without a go.mod.

BF-fa3da97-26

A provider stream could stop for good, with the turn waiting for events that never came, when the goroutine…

  • Source: PiG's own defect
  • Symptom: A provider stream could stop for good, with the turn waiting for events that never came, when the goroutine that reads the stream and the goroutine that runs the provider's response were scheduled against each other as the stream started.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: Windows, macOS, Linux
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A provider stream could stop for good, with the turn waiting for events that never came, when the goroutine that reads the stream and the goroutine that runs the provider's response were scheduled against each other as the stream started. The reader's goroutine could pick up a reaction loop that belonged to the response, run the reader's code beside a reaction that had not returned, and leave the stream's executor marked busy with nothing running. A goroutine now continues only a reaction loop its own turn holds, so no code runs beside a reaction and every stream ends. It stalled once in CI on Windows (TestGoogleProviderStreamMatchesPi); the same stall reproduces on macOS and Linux when the executor's lock hand-offs are delayed.

BF-fa3da97-27

PiG no longer hangs for 30 seconds at exit when two extensions share a Node process and one is still waiting…

  • Source: PiG's own defect
  • Symptom: PiG no longer hangs for 30 seconds at exit when two extensions share a Node process and one is still waiting on a host call (seen with a global project-trust extension and a project extension).
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: PiG no longer hangs for 30 seconds at exit when two extensions share a Node process and one is still waiting on a host call (seen with a global project-trust extension and a project extension).

BF-fa3da97-28

A Node extension whose factory failed, because it has no default export or throws while initializing, could…

  • Source: PiG's own defect
  • Symptom: A Node extension whose factory failed, because it has no default export or throws while initializing, could be reported as connect:process_exited ... exit status 1 instead of Pi's "Extension does not export a valid factory function: <path>" or "Failed to load extension: <message>".
  • Root cause: A Node extension whose factory failed, because it has no default export or throws while initializing, could be reported as connect:process_exited ... exit status 1 instead of Pi's "Extension does not export a valid factory function: <path>" or "Failed to load extension: <message>".
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A Node extension whose factory failed, because it has no default export or throws while initializing, could be reported as connect:process_exited ... exit status 1 instead of Pi's "Extension does not export a valid factory function: <path>" or "Failed to load extension: <message>". The extension's runtime reports that error over its socket and exits; on a loaded machine the host looked after the exit and chose between the connection and the exit by whichever it noticed first, which failed about 1 run in 20 of the upstream extension-discovery tests. The host now accepts the connection the extension made before it exited, in both a packed cell and an isolated process, and reports process_exited only for a process that never connected.

BF-fa3da97-29

A tool that streams a growing partial result no longer sends the whole result to each subscribed extension on…

  • Source: PiG's own defect
  • Symptom: A tool that streams a growing partial result no longer sends the whole result to each subscribed extension on every update; updates carry only what changed, and handlers still receive the full partialResult.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A tool that streams a growing partial result no longer sends the whole result to each subscribed extension on every update; updates carry only what changed, and handlers still receive the full partialResult.

BF-fa3da97-3

done() of an overlay ui.custom component hides that component's own overlay instead of the topmost one

  • Source: PiG's own defect
  • Symptom: done() of an overlay ui.custom component hides that component's own overlay instead of the topmost one.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: done() of an overlay ui.custom component hides that component's own overlay instead of the topmost one. With a shell overlay open under a dialog overlay from another extension, the shell's exit no longer removes the dialog or leaves the finished shell on screen (Pi #10999, #10648).

BF-fa3da97-30

A Durable core request costs the same however many requests are in flight: a notice is tested only against…

  • Source: PiG's own defect
  • Symptom: A Durable core request costs the same however many requests are in flight: a notice is tested only against the requests waiting for it.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A Durable core request costs the same however many requests are in flight: a notice is tested only against the requests waiting for it.

BF-fa3da97-31

Durable tool calls that the model issues in parallel or nested no longer slow down as the live state grows: a…

  • Source: PiG's own defect
  • Symptom: Durable tool calls that the model issues in parallel or nested no longer slow down as the live state grows: a commit no longer decodes the whole live document, and each tool reads only its own call.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Durable tool calls that the model issues in parallel or nested no longer slow down as the live state grows: a commit no longer decodes the whole live document, and each tool reads only its own call.

BF-fa3da97-32

With an extension turn_end handler, each tool result no longer copies the whole session history, and a…

  • Source: PiG's own defect
  • Symptom: With an extension turn_end handler, each tool result no longer copies the whole session history, and a boundary preview no longer re-encodes every entry: long sessions stay fast at turn ends.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: With an extension turn_end handler, each tool result no longer copies the whole session history, and a boundary preview no longer re-encodes every entry: long sessions stay fast at turn ends.

BF-fa3da97-33

A Durable reference harness turn no longer copies the whole conversation history to extend its context

  • Source: PiG's own defect
  • Symptom: A Durable reference harness turn no longer copies the whole conversation history to extend its context.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A Durable reference harness turn no longer copies the whole conversation history to extend its context.

BF-fa3da97-34

Durable: Submission.Wait, WaitForIdle and WaitForTask return only after every event-stream listener has…

  • Source: PiG's own defect
  • Symptom: Durable: Submission.Wait, WaitForIdle and WaitForTask return only after every event-stream listener has been given the batches committed before the wait, as in Pi.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Durable: Submission.Wait, WaitForIdle and WaitForTask return only after every event-stream listener has been given the batches committed before the wait, as in Pi. Stopping an event stream right after one of these waits no longer drops the end of a run.

BF-fa3da97-35

Selecting a go.work member module that is itself an exact factory package resolves that module's factory…

  • Source: PiG's own defect
  • Symptom: The selection scanned every member module and reported all of their factories as ambiguous, so a monorepo with one module per extension and a root go.work could not install any member.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Selecting a go.work member module that is itself an exact factory package resolves that module's factory and keeps the workspace as the compiler closure (#212). Before, the selection scanned every member module and reported all of their factories as ambiguous, so a monorepo with one module per extension and a root go.work could not install any member. The workspace root itself stays ambiguous when several members hold factories. An exactly selected member that is a package main standalone resolves to that member instead of "spans N modules".

BF-fa3da97-36

A --print or --mode json run that SIGTERM or SIGHUP reaches while its bash tool is starting the shell no…

  • Source: PiG's own defect
  • Symptom: A --print or --mode json run that SIGTERM or SIGHUP reaches while its bash tool is starting the shell no longer has a data race on os.Stdout.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A --print or --mode json run that SIGTERM or SIGHUP reaches while its bash tool is starting the shell no longer has a data race on os.Stdout. Starting a child process (a shell, an extension command, a Durable child) decided which of the child's files to leave open by reading the os.Stdout variable, which the exit path's restore of the output guard assigns at the same time. It now compares them with the files os.Stdin, os.Stdout and os.Stderr held when pig started, which stay open as before. The output and exit status are unchanged.

BF-fa3da97-37

chord/host: the Node facet loader no longer writes its child entry under one fixed name in the shared…

  • Source: PiG's own defect
  • Symptom: chord/host: the Node facet loader no longer writes its child entry under one fixed name in the shared temporary directory.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: chord/host: the Node facet loader no longer writes its child entry under one fixed name in the shared temporary directory. Another process that cleaned that file up, or two processes racing it, made a starting load fail with child exited before ready; and any other user could rewrite the file Node ran. Each load now writes the entry into a private 0700 directory of its own and removes it once the child has started.

BF-fa3da97-38

A Durable core listing of all or terminal tasks (ScanTasks) reads one page from storage per call instead of…

  • Source: PiG's own defect
  • Symptom: A Durable core listing of all or terminal tasks (ScanTasks) reads one page from storage per call instead of loading every task: a page costs the same at any task count.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A Durable core listing of all or terminal tasks (ScanTasks) reads one page from storage per call instead of loading every task: a page costs the same at any task count. Listing a conversation owner's conversations reads only that owner's.

BF-fa3da97-39

Edit diffs and the edit tool with many replacements on a large file use far less memory and time: a…

  • Source: PiG's own defect
  • Symptom: Edit diffs and the edit tool with many replacements on a large file use far less memory and time: a 16,000-line diff takes a quarter of the time and 2 MB instead of 336 MB.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Edit diffs and the edit tool with many replacements on a large file use far less memory and time: a 16,000-line diff takes a quarter of the time and 2 MB instead of 336 MB.

BF-fa3da97-4

A call through a remote Chord reference with a ctx from another host, or one not made by Chord, fails with…

  • Source: PiG's own defect
  • Symptom: A call through a remote Chord reference with a ctx from another host, or one not made by Chord, fails with invalid/foreign_ctx before anything crosses the link, like a call through a local reference.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A call through a remote Chord reference with a ctx from another host, or one not made by Chord, fails with invalid/foreign_ctx before anything crosses the link, like a call through a local reference.

BF-fa3da97-40

An extension that set its header or footer, or registered a sprite or a login, from its own goroutine painted…

  • Source: PiG's own defect
  • Symptom: An extension that set its header or footer, or registered a sprite or a login, from its own goroutine painted the whole screen on that goroutine while the main loop was changing the transcript, so the two could read and write the same transcript state at once.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: An extension that set its header or footer, or registered a sprite or a login, from its own goroutine painted the whole screen on that goroutine while the main loop was changing the transcript, so the two could read and write the same transcript state at once. Such a change now asks the main loop for a frame, as Pi's setExtensionHeader and setExtensionFooter end with ui.requestRender(); a change made on the main loop still paints at once. The race detector reported it in about one run in ten of the Node scroll-view footer scenario, which sets its footer while a notification arrives.

BF-fa3da97-41

The terminal width and height the extension host tells extensions during /reload, and when one starts or…

  • Source: PiG's own defect
  • Symptom: The terminal width and height the extension host tells extensions during /reload, and when one starts or restarts, were read while the main loop's frame updated them, and the handler that reports an extension's crash was replaced on each reload while the host's connection goroutines read it.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Crash, hang or runaway process
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: The terminal width and height the extension host tells extensions during /reload, and when one starts or restarts, were read while the main loop's frame updated them, and the handler that reports an extension's crash was replaced on each reload while the host's connection goroutines read it. Both now read an atomically published value. The race detector reported the width and height read in about one /reload run in twenty, and the crash handler once, in a shutdown from a session_start handler.

BF-fa3da97-42

Deleting members of a large Durable core JSON object no longer costs time proportional to its size for each…

  • Source: PiG's own defect
  • Symptom: Deleting members of a large Durable core JSON object no longer costs time proportional to its size for each delete.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Deleting members of a large Durable core JSON object no longer costs time proportional to its size for each delete.

BF-fa3da97-5

Turning thinking off on Claude Haiku 5.5 and Opus 5 sends plain thinking: {type: "disabled"} without effort…

  • Source: PiG's own defect
  • Symptom: Turning thinking off on Claude Haiku 5.5 and Opus 5 sends plain thinking: {type: "disabled"} without effort markers, which Anthropic rejects while thinking is disabled.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Turning thinking off on Claude Haiku 5.5 and Opus 5 sends plain thinking: {type: "disabled"} without effort markers, which Anthropic rejects while thinking is disabled.

BF-fa3da97-6

The retry classifier treats "The service is temporarily unavailable" and Bedrock's "Service unavailable: ..."…

  • Source: PiG's own defect
  • Symptom: The retry classifier treats "The service is temporarily unavailable" and Bedrock's "Service unavailable: ..." as retryable.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: The retry classifier treats "The service is temporarily unavailable" and Bedrock's "Service unavailable: ..." as retryable.

BF-fa3da97-7

A Progress pause longer than Node's timer maximum, or not a number, runs after 1 ms as a Node timer does

  • Source: PiG's own defect
  • Symptom: A Progress pause longer than Node's timer maximum, or not a number, runs after 1 ms as a Node timer does.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A Progress pause longer than Node's timer maximum, or not a number, runs after 1 ms as a Node timer does.

BF-fa3da97-8

Anthropic and Claude on Bedrock no longer lose the images of an error tool result: Pi sends them after the…

  • Source: PiG's own defect
  • Symptom: Anthropic and Claude on Bedrock no longer lose the images of an error tool result: Pi sends them after the tool results of the turn, under a caption naming the tool call, instead of replacing them with a note (#10794).
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: Anthropic and Claude on Bedrock no longer lose the images of an error tool result: Pi sends them after the tool results of the turn, under a caption naming the tool call, instead of replacing them with a note (#10794). Other Bedrock models keep the images in the result.

BF-fa3da97-9

A Durable tool call aborted while the Harness was still resolving the tool to run could stay running for…

  • Source: PiG's own defect
  • Symptom: A Durable tool call aborted while the Harness was still resolving the tool to run could stay running for good, with the generation that called it waiting on it.
  • Root cause: changelog.d held 62 fragments in legacy.txt, which exempts them from the marker rule.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.2
  • Fix commits: fa3da978e2 (2026-10-22)
  • Tests: None recorded.
  • Release note: A Durable tool call aborted while the Harness was still resolving the tool to run could stay running for good, with the generation that called it waiting on it. The abort cancelled the call before its tool started, and the native core driver ended the call without telling the core, which settles a cancelled call only on that report. The call now reports the cancelled lookup as a thrown result, as it reports a tool that observes the cancel. It stalled once in about 1,000 CI runs of the abort-with-generation test under the race detector.

BF-fac4703

The extension editor dialog (ctx.ui.editor(), /tree summary prompt) shows the hint keys the user bound…

  • Source: PiG's own defect
  • Symptom: The extension editor dialog (ctx.ui.editor(), /tree summary prompt) shows the hint keys the user bound for submit, newline and cancel instead of the default key names (internal/codingagent/extension_editor.go, extension-editor.ts keyHint; extended TestExtensionEditorHandleInputMatchesPi renders the hint row under five rebindings, red before).
  • Root cause: Pi builds the hint with keyHint(action), which resolves tui.select.confirm, tui.input.newLine and tui.select.cancel against the current bindings; Pig printed the default key names. Reverting the hint line fails them.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 211af490e0 (2026-10-11), 4ae159f3cd (2026-10-11), a2755058da (2026-10-11), fac470319e (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, coding/discover_extensions_oracle_test.go::TestDiscoverExtensionPathsMatchPi, coding/extension/host/inproc/shortcuts_oracle_test.go::TestRunnerShortcutsMatchPi, internal/codingagent/armin_test.go::TestArminFramesMatchPinnedPi, internal/codingagent/bug_report_files_oracle_test.go::TestBugReportFilesMatchPi and 14 more in docs/bug-ledger.json
  • Release note: The extension editor dialog (ctx.ui.editor(), /tree summary prompt) shows the hint keys the user bound for submit, newline and cancel instead of the default key names (internal/codingagent/extension_editor.go, extension-editor.ts keyHint; extended TestExtensionEditorHandleInputMatchesPi renders the hint row under five rebindings, red before).

BF-fac4703-2

TestSettingsSelectorMatchesPi walks every row under rebound, multi-key and unbound app.message.followUp /…

  • Source: PiG's own defect
  • Symptom: TestSettingsSelectorMatchesPi walks every row under rebound, multi-key and unbound app.message.followUp / app.thinking.cycle and rebound select keys, so the row descriptions that name those keys are compared; no difference; fixed-key mutations on both descriptions are red.
  • Root cause: Pi builds the hint with keyHint(action), which resolves tui.select.confirm, tui.input.newLine and tui.select.cancel against the current bindings; Pig printed the default key names. Reverting the hint line fails them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 6a6fffa638 (2026-10-10), 33fe10d58e (2026-10-11), fac470319e (2026-10-11)
  • Tests: agent/message_json_number_spelling_test.go::TestAgentMessageDecodeKeepsANonNumberWhereAnIntegerIsExpected, agent/message_json_test.go::TestAgentMessageJSONRejectsInvalidUnion, agent/message_type_mismatch_test.go::TestDecodedMessageKeepsMembersOfAnyType, agent/provider_content_block_decode_test.go::TestUnknownContentBlockCostsOnlyThatBlock, ai/models_error_oracle_test.go::TestModelsErrorMessageMatchesPi, coding/mcpext/manager_view_oracle_test.go::TestMcpManagerViewMatchesPi, internal/codingagent/bash_execution_oracle_test.go::TestBashExecutionComponentMatchesPi, internal/codingagent/extension_editor_test.go::TestExtensionEditorHandleInputMatchesPi and 10 more in docs/bug-ledger.json
  • Release note: TestSettingsSelectorMatchesPi walks every row under rebound, multi-key and unbound app.message.followUp / app.thinking.cycle and rebound select keys, so the row descriptions that name those keys are compared; no difference; fixed-key mutations on both descriptions are red.

BF-fac4703-3

TestSelectListInputMatchesPi also runs under the Kitty keyboard protocol (CSI-u enter, escape, ctrl+j…

  • Source: PiG's own defect
  • Symptom: TestSelectListInputMatchesPi also runs under the Kitty keyboard protocol (CSI-u enter, escape, ctrl+j, ctrl+c, shift+enter, modified arrows, rebound select keys); no difference; a literal-LF confirm mutation is red only in Kitty mode.
  • Root cause: Pi builds the hint with keyHint(action), which resolves tui.select.confirm, tui.input.newLine and tui.select.cancel against the current bindings; Pig printed the default key names. Reverting the hint line fails them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: fac470319e (2026-10-11)
  • Tests: coding/mcpext/manager_view_oracle_test.go::TestMcpManagerViewMatchesPi, internal/codingagent/extension_editor_test.go::TestExtensionEditorHandleInputMatchesPi, internal/codingagent/llama/ui_oracle_test.go::TestLlamaViewMatchesPi, internal/codingagent/mcp_cards_oracle_test.go::TestMcpToolCardsMatchPi, tui/extension_input_oracle_test.go::TestExtensionInputMatchesPi, tui/extension_selector_oracle_test.go::TestExtensionSelectorMatchesPi, tui/select_list_oracle_test.go::TestSelectListInputMatchesPi
  • Release note: TestSelectListInputMatchesPi also runs under the Kitty keyboard protocol (CSI-u enter, escape, ctrl+j, ctrl+c, shift+enter, modified arrows, rebound select keys); no difference; a literal-LF confirm mutation is red only in Kitty mode.

BF-fba9b47

fix(extension): hold the theme colors map the wire palette memo is keyed on

  • Source: PiG's own defect
  • Symptom: fix(extension): hold the theme colors map the wire palette memo is keyed on
  • Root cause: ActiveExtensionThemeWire identified the terminal-resolved colors by the address of the map theme.Colors returned, stored as a uintptr. The theme drops that map when the terminal's reported colors change, so the collector could free it and give its address to a later colors map with other values; the memo would then return the old palette and the state push would skip the theme as unchanged. The memo now holds the map it is keyed on, so the address cannot be reused while it identifies the palette.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: Unreleased
  • Fix commits: fba9b473e9 (2026-10-11)
  • Tests: internal/codingagent/ext_ui_theme_palette_test.go::TestActiveExtensionThemeWireHoldsTheColorsMapItIsKeyedOn

BF-fbd3640

The legacy credential migration writes auth.json and the rewritten settings.json with the key order Pi…

  • Source: PiG's own defect
  • Symptom: The files used to be written with every key sorted.
  • Root cause: migrations.ts:60,69 write JSON.stringify(settings|migrated, null, 2): keys keep Object.entries order and <, > and & stay unescaped. The Go port marshalled Go maps, which sorted every key.
  • User impact: Credential or permission exposure
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 8d23cfcee3 (2026-10-06), fbd3640da9 (2026-10-06)
  • Tests: internal/codingagent/migrations_auth_order_test.go::TestMigrateAuthToAuthJSONWritesFilesInPiKeyOrder, internal/codingagent/settings_key_order_test.go::TestSaveSettingsPatchKeepsTheFileKeyOrder, internal/codingagent/settings_key_order_test.go::TestSaveSettingsPatchReplacesInPlace
  • Release note: The legacy credential migration writes auth.json and the rewritten settings.json with the key order Pi writes (array-index names first, then the order in the old files) and without escaping <, > and &. The files used to be written with every key sorted.

BF-fc5baaf

pi.exec's timeout starts once the command has been spawned, as in Pi, so time spent finding and starting…

  • Source: PiG's own defect
  • Symptom: pi.exec's timeout starts once the command has been spawned, as in Pi, so time spent finding and starting the program no longer counts against it.
  • Root cause: exec.ts:75 sets the timer after spawn has returned, so finding and starting the program does not count against options.timeout. ExecCommand started it before the PATH search and the fork, so a slow spawn (a long PATH, a loaded host) killed the child before it ran where Pi lets it finish.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: fc5baaf077 (2026-10-13)
  • Tests: coding/extension/exec_oracle_test.go::TestExecCommandMatchesPi, coding/extension/exec_timeout_unix_test.go::TestExecCommandStartsTheTimeoutAfterTheSpawnAsPiDoes
  • Release note: pi.exec's timeout starts once the command has been spawned, as in Pi, so time spent finding and starting the program no longer counts against it.

BF-fc749ad

A task list item whose first line looks like a heading, quote, rule, bullet, fence, table row or HTML tag…

  • Source: PiG's own defect
  • Symptom: A task list item whose first line looks like a heading, quote, rule, bullet, fence, table row or HTML tag renders that line as text, and a setext heading over it keeps the checkbox marker, as Pi does.
  • Root cause: marked splits [ ] into a checkbox token and lexes the rest of the first line as a text token, so - [ ] # h, - [ ] > q, - [ ] ---, - [ ] - a, a fence, a table row, a definition or an HTML tag renders as text, while the lines after it are lexed normally; a setext heading over the first line (- [ ] t / ===) takes the marker into its text and the item has no checkbox. Pig stripped the marker and rendered the block. renderList now keeps the marker when setextHeadingEnd matches the original text, and otherwise lexes the first block with a leading control character that no block rule matches, removed from the rendered rows.
  • User impact: Wrong display
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: fc749adcb9 (2026-10-11)
  • Tests: tui/markdown_task_item_oracle_test.go::TestMarkdownTaskItemFirstLineIsTextMatchesPi
  • Release note: A task list item whose first line looks like a heading, quote, rule, bullet, fence, table row or HTML tag renders that line as text, and a setext heading over it keeps the checkbox marker, as Pi does.

BF-fc886e3

Google and Vertex request bodies list their members in the order the @google/genai SDK writes them, also…

  • Source: PiG's own defect
  • Symptom: Google and Vertex request bodies list their members in the order the @google/genai SDK writes them, also when an extension handles before_provider_request: body members (contents, systemInstruction, tools, toolConfig, then generationConfig), generationConfig members, parts before role, and the members of each part and function call.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), 2309604d80 (2026-10-10), b4e83dbe17 (2026-10-10), c10cde302d (2026-10-10), eeac307095 (2026-10-10), f29e906cb3 (2026-10-10), fc886e3bd2 (2026-10-10), fe16108cc7 (2026-10-10)
  • Tests: agent/assistant_member_order_oracle_test.go::TestAssistantLateMembersFollowPiCreationOrder, agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses and 18 more in docs/bug-ledger.json
  • Release note: Google and Vertex request bodies list their members in the order the @google/genai SDK writes them, also when an extension handles before_provider_request: body members (contents, systemInstruction, tools, toolConfig, then generationConfig), generationConfig members, parts before role, and the members of each part and function call. A function response lists parts before id, as Pi does.

BF-fce99d3

The "Your first ten minutes" and Quickstart pages now describe the first-time setup screen (theme, sprite and…

  • Source: PiG's own defect
  • Symptom: They said PiG had no setup screen.
  • Root cause: Not recorded.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): fce99d3151 (2026-10-11)
  • Tests: None recorded.
  • Release note: The "Your first ten minutes" and Quickstart pages now describe the first-time setup screen (theme, sprite and usage data) that PiG shows on a fresh install (D88). Before, they said PiG had no setup screen. The first-run page also explains that Enter in the model list picks a model for one conversation and Ctrl+S makes it the default, what the footer fields mean, and what a 401 error after /login means.

BF-fce99d4

An npm or git -e source that fails to install stops startup and /reload before any extension factory…

  • Source: PiG's own defect
  • Symptom: An npm or git -e source that fails to install stops startup and /reload before any extension factory runs, and later -e sources are not installed.
  • Root cause: One pattern's result varies between Node runs, because the length limit counts a random marker. 24 of 26 compiled mutants fail a test. The other two move the length limit's boundary inside a combine before the last, or drop it from a sequence; a later combine meets the same limit first, so the results cannot differ.
  • User impact: An operation failed or was rejected
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 311609427b (2026-10-07), a79de03e7f (2026-10-07), d0ad06fc18 (2026-10-07), 1cbb6b6fb2 (2026-10-09), 4401fa4630 (2026-10-09), 6584435bb6 (2026-10-09), 6e6b9132c2 (2026-10-09), d586d71f85 (2026-10-09), ef007a4e5e (2026-10-09), 6410ef49f6 (2026-10-10), 6f8d83861a (2026-10-10), fce99d43d7 (2026-10-10)
  • Tests: coding/cli/cli_package_glob_test.go::TestCLIGitSourceRefreshesOncePerLoad, coding/cli/cli_package_glob_test.go::TestCLIPackageSourceGlobThrowFirstSourceWins, coding/cli/cli_package_glob_test.go::TestCLIPackageSourceGlobThrowFollowsPi, coding/cli/cli_package_glob_test.go::TestCLIPackageSourceInstallFailureStopsStartup, coding/cli/cli_package_glob_test.go::TestReloadSnapshotReportsCLIPackageGlobThrow, coding/cli/cli_package_glob_test.go::TestStartupStopsAtACLIPackageGlobThrow, coding/cli/package_update_cases_upstream_test.go::TestPackageUpdateLookupPrefixesOriginal, coding/cli/settings_file_url_upstream_test.go::TestNpmPackageManifestGlobThrowRejectsResolution and 55 more in docs/bug-ledger.json
  • Release note: An npm or git -e source that fails to install stops startup and /reload before any extension factory runs, and later -e sources are not installed. Each -e source resolves once per load.

BF-fd4be86

OpenAI-compatible Chat Completions keeps the "text":"" member of a user text part whose text is only a lone…

  • Source: PiG's own defect
  • Symptom: OpenAI-compatible Chat Completions keeps the "text":"" member of a user text part whose text is only a lone UTF-16 surrogate, as Pi writes it after sanitizing (a part that is empty to begin with is still dropped).
  • Root cause: Not recorded.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): fd4be8683c (2026-10-11)
  • Tests: ai/openai_completions_empty_sanitized_text_test.go::TestCompletionsUserTextThatSanitizesToNothingKeepsItsEmptyTextMember
  • Release note: OpenAI-compatible Chat Completions keeps the "text":"" member of a user text part whose text is only a lone UTF-16 surrogate, as Pi writes it after sanitizing (a part that is empty to begin with is still dropped). Found by widening the payload oracles' random transcripts with lone surrogates and decoding and encoding them with the SDK-compatible JSON package.

BF-fd7f660

A resumed session keeps the tools that were active when it was saved (an optional or deferred tool a command…

  • Source: PiG's own defect
  • Symptom: A resumed session keeps the tools that were active when it was saved (an optional or deferred tool a command or extension had enabled no longer drops out and gets recorded as removed on the next turn); an explicit --tools list, --no-tools or --no-builtin-tools still wins (pi-mono #10785).
  • Root cause: Pi #10887 makes the AgentSession constructor restore the transcript's loadout whenever the session uses the default tools, applying the --tools +name/-name entries to it. NewSession now passes DefaultToolModifiers to restoreToolsFromTranscript instead of skipping the restore when modifiers are given. Divergence D166 is retired.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Commit that wrote the release note (the fix commit is not identified): fd7f660ae3 (2026-10-13)
  • Tests: coding/cli/rpc_resume_tools_test.go::TestRPCResumeKeepsDynamicallyActivatedTools, coding/session_resume_default_tools_port_test.go::TestResumeRestoresTheRecordedLoadoutPort
  • Release note: A resumed session keeps the tools that were active when it was saved (an optional or deferred tool a command or extension had enabled no longer drops out and gets recorded as removed on the next turn); an explicit --tools list, --no-tools or --no-builtin-tools still wins (pi-mono #10785).

BF-fdfb748

A surrogate pair split across two concatenated string halves is written as its character in session files…

  • Source: PiG's own defect
  • Symptom: A surrogate pair split across two concatenated string halves is written as its character in session files, extension frames and Durable records, as Pi's JSON.stringify writes it, instead of as two \u escapes.
  • Root cause: Sibling of (durable/core/jv). The shared host/Go-SDK codec writes WTF-8 surrogate units as \u escapes; a high unit directly followed by a low unit (two WTF-8 halves concatenated in Go) is one code point in a JavaScript string, and JSON.stringify writes the character (node 24: JSON.stringify("\ud83d" + "\ude00") === '"😀"'). appendString now joins such a pair into its four-byte UTF-8; unpaired units stay escapes. This codec writes session entries (jsstring.MarshalJSON), extension frames and Durable storage.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 89338b7eac (2026-10-10), fdfb74849c (2026-10-10), fe16108cc7 (2026-10-10)
  • Tests: ai/openai_request_key_order_oracle_test.go::TestOpenAICompletionsRequestKeysFollowPiAssignmentOrder, ai/openai_responses_request_key_order_oracle_test.go::TestOpenAIResponsesRequestKeysFollowPiAssignmentOrder, ai/perf_gate_norace_test.go::TestProviderRequestHistoryAllocationGate, ai/request_body_escaping_test.go::TestProviderRequestBodiesWriteCharactersAsJSONStringifyDoes, durable/core/jv/split_pair_test.go::TestAppendStringJoinsASplitSurrogatePair, extensions/sdk/json/surrogate_test.go::TestSplitSurrogatePairEncodesAsItsCharacter, internal/jsstring/unescape_stringify_test.go::TestUnescapeForStringifyRewritesOnlyRealEscapes
  • Release note: A surrogate pair split across two concatenated string halves is written as its character in session files, extension frames and Durable records, as Pi's JSON.stringify writes it, instead of as two \u escapes.

BF-fe16108

Provider request bodies (OpenAI Completions, OpenAI Responses, Codex, Anthropic, Google and Mistral) write…

  • Source: PiG's own defect
  • Symptom: Provider request bodies (OpenAI Completions, OpenAI Responses, Codex, Anthropic, Google and Mistral) write <, >, &, U+2028 and U+2029 as they are, as Pi's JSON.stringify does, instead of as \u escapes.
  • Root cause: Pi's provider SDKs send JSON.stringify(body) (openai 7.19.0 internal/request-options.js:29; the Anthropic, Google GenAI and Mistral SDKs stringify the same way), which writes these five characters as they are. PiG wrote every body with encoding/json.Marshal, which escapes them, so a user message "a <b> & c" went out as "a \u003cb\u003e \u0026 c".
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: f29e906cb3 (2026-10-10), fe16108cc7 (2026-10-10)
  • Tests: ai/anthropic_messages_payload_oracle_test.go::TestAnthropicMessagesPayloadMatchesPi, ai/openai_request_key_order_oracle_test.go::TestOpenAICompletionsRequestKeysFollowPiAssignmentOrder, ai/openai_responses_request_key_order_oracle_test.go::TestOpenAIResponsesRequestKeysFollowPiAssignmentOrder, ai/perf_gate_norace_test.go::TestProviderRequestHistoryAllocationGate, ai/request_body_escaping_test.go::TestProviderRequestBodiesWriteCharactersAsJSONStringifyDoes, internal/jsstring/unescape_stringify_test.go::TestUnescapeForStringifyRewritesOnlyRealEscapes
  • Release note: Provider request bodies (OpenAI Completions, OpenAI Responses, Codex, Anthropic, Google and Mistral) write <, >, &, U+2028 and U+2029 as they are, as Pi's JSON.stringify does, instead of as \u escapes.

BF-fe625bb

Mistral request bodies list their members in Pi's order (model, stream, messages, ..., then the renamed…

  • Source: PiG's own defect
  • Symptom: Mistral request bodies list their members in Pi's order (model, stream, messages, ..., then the renamed max_tokens, tool_choice, reasoning_effort, prompt_mode, prompt_cache_key; a tool result's tool_call_id last; thinking parts as type, text) instead of sorted, and the body is built about 3 times faster.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 433d232815 (2026-10-09), afa1dd5185 (2026-10-10), d8c227b7f5 (2026-10-10), f02269c6e1 (2026-10-10), fe625bb5dd (2026-10-10)
  • Tests: agent/assistant_member_order_oracle_test.go::TestAssistantLateMembersFollowPiCreationOrder, agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses and 17 more in docs/bug-ledger.json
  • Release note: Mistral request bodies list their members in Pi's order (model, stream, messages, ..., then the renamed max_tokens, tool_choice, reasoning_effort, prompt_mode, prompt_cache_key; a tool result's tool_call_id last; thinking parts as type, text) instead of sorted, and the body is built about 3 times faster.

BF-fe625bb-2

When an extension's before_provider_request handler returns a replacement body for OpenAI Completions…

  • Source: PiG's own defect
  • Symptom: When an extension's before_provider_request handler returns a replacement body for OpenAI Completions, OpenAI Responses, Codex, Pi Messages, Mistral or Google, PiG sends it as Pi's JSON.stringify writes what JSON.parse read: \u00e9 becomes é, 1.50 becomes 1.5, a repeated key keeps its first place with its last value, and array-index keys come first.
  • Root cause: After /tree navigation between parallel tool calls and their results, a branch summary followed by a new prompt sent a synthetic tool_result behind the summary, and Anthropic rejected it with an unexpected tool_use_id. Upstream runs transformMessages after convertToLlm, where a branch summary, compaction summary, bash execution or custom message is already a user message and closes the pending tool calls. NormalizeMessages ran first and did not see them.
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: e8cc072521 (2026-10-08), 433d232815 (2026-10-09), afa1dd5185 (2026-10-10), f02269c6e1 (2026-10-10), fe625bb5dd (2026-10-10)
  • Tests: agent/convert_custom_upstream_test.go::TestConvertToLLMCustomMessagesMatchUpstreamConvertToLlm, agent/convert_custom_upstream_test.go::TestConvertToLLMToolFlowMatchesUpstreamTransformOrder, agent/convert_custom_upstream_test.go::TestCustomMessagesReachProvidersAsUpstreamContentBlocks, agent/transform_custom_user_boundary_test.go::TestAnthropicRequestAfterBranchSummaryHasNoOrphanToolResult, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomMessageWithoutWireOutputKeepsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMCustomUserMessageInterruptsToolFlow, agent/transform_custom_user_boundary_test.go::TestConvertToLLMHoldsSystemMessageUntilToolFlowCloses, ai/anthropic_messages_payload_oracle_test.go::TestAnthropicMessagesPayloadMatchesPi and 19 more in docs/bug-ledger.json
  • Release note: When an extension's before_provider_request handler returns a replacement body for OpenAI Completions, OpenAI Responses, Codex, Pi Messages, Mistral or Google, PiG sends it as Pi's JSON.stringify writes what JSON.parse read: \u00e9 becomes é, 1.50 becomes 1.5, a repeated key keeps its first place with its last value, and array-index keys come first. PiG sent the handler's text unchanged.

BF-ffcd800

Text after an empty heading follows the same space-token rule as after any other block: blank lines or spaces…

  • Source: PiG's own defect
  • Symptom: Text after an empty heading follows the same space-token rule as after any other block: blank lines or spaces after a final empty heading add the empty row Pi shows.
  • Root cause: marked trims a heading's raw of its newlines, so the text after it is a space token when longer than one character, as after any block ('# \n\n', '#\n ', 'a\n#\n ' render two or more rows in Pi). The earlier guard that kept the heading's own row from a trailing token is replaced by one for the row a list already added. 9 new rows (6 red before).
  • User impact: Behavior differed from Pi
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: 2a919a4c3c (2026-10-10), 4e5b684ecd (2026-10-10), ac0dc3bee4 (2026-10-10), cdf0be76e4 (2026-10-10), ffcd800f73 (2026-10-10)
  • Tests: tui/markdown_render_oracle_test.go::TestMarkdownRenderMatchesPi
  • Release note: Text after an empty heading follows the same space-token rule as after any other block: blank lines or spaces after a final empty heading add the empty row Pi shows.

BF-ffeb469

A long session keeps less memory for each turn it has compacted away

  • Source: PiG's own defect
  • Symptom: A long session keeps less memory for each turn it has compacted away.
  • Root cause: A 21,000-turn RPC session with automatic compaction grew its live heap by 1.75 KiB per turn. Three parts of that were not entries Pi also keeps: - the /tree search text of an entry compaction moved out of the context was built at eviction (about 340 B per turn); it is now read from the entry's record on the first search, as after an open - the ai tool-call argument encoding cache kept the encodings of dropped calls until its 16 MiB limit (about 480 B per turn); an encoding is now released when its arguments are unreachable - agent.Recorder appended a duration per turn; it keeps the count and the last duration
  • User impact: Wrong output or ordering
  • Platform: All platforms
  • Fixed in: 0.5.0
  • Fix commits: ffeb469492 (2026-10-14)
  • Tests: agent/timings_test.go::TestRecorderRetainsNothingPerTurn, agent/timings_test.go::TestRecorderTurnRoundTrip, ai/tool_call_arguments_lifetime_test.go::TestArgumentEncodingsAreReleasedWithTheirArguments, ai/tool_call_arguments_lifetime_test.go::TestToolCallArgumentsJSONKeepsNothingForADroppedCall, coding/extension/host/subprocess/runtime_node_sdk_bundle_test.go::TestNodeLibraryBundlesRegenerateExactly, coding/extension/host/subprocess/runtime_node_sdk_bundle_test.go::TestNodeSDKBundleRegeneratesExactly, internal/codingagent/session_retained_growth_norace_test.go::TestSessionKeepsNoSearchTextForEntriesNobodySearched, internal/codingagent/session_retained_growth_norace_test.go::TestSessionLiveHeapPerExchange
  • Release note: A long session keeps less memory for each turn it has compacted away. The /tree search text of an entry that compaction moves out of the context is no longer built and kept when the entry leaves memory: the first search reads it from the entry's record, as it already did after opening a session, and a session nobody searches keeps none. The cache of encoded tool-call arguments releases an encoding when the arguments it encodes are no longer reachable, where it kept the encodings of dropped calls until its 16 MiB limit. The agent's turn timer keeps the number of completed turns and the last duration instead of one duration per turn. In a 21,000-turn RPC session with automatic compaction on a 32k window, live heap growth falls from about 1.7 KiB per turn to the index of the session's entries (slots, compressed records, compaction entries), which Pi's in-memory session also keeps, as whole entries.

Divergences from Pi added in this release

Each divergence record is accounted for here. A divergence that fixes a Pi defect links to its ledger entry; the others are deliberate differences and no bug fix.

Divergence
Title
Ledger entry
D96The remote execution daemon is a Go program, not Pi's Rust binarydeliberate difference
D98The eval harness hands the agent process an OAuth credential filedeliberate difference
D99The Windows npm self-update quarantine waits out a held imageBF-f288d69-2
D103JS Error.stack is not modelleddeliberate difference
D105The interactive driver owns terminal input; extension input listeners run asynchronouslydeliberate difference
D110Git sources: #subdirectory= selects a subdirectory; git:// and upper-case schemes yield usable…BF-D110
D111Abort abandons a non-cooperative extension tool after a grace periodBF-9be5765
D113A Session has no extensionRunnerRef celldeliberate difference
D114StdinBuffer has no getMaxListeners, setMaxListeners or MaxListenersExceededWarningdeliberate difference
D115Gemini 2.x tool-result images follow the whole run of function responsesBF-80bb522-2
D116Bedrock Converse messages of one role are mergedBF-80bb522-3
D117DiscoverAndLoadExtensions takes no event busdeliberate difference
D118A durable commit with an unknown task or submission status fails with "Unknown task status"deliberate difference
D120A streamed tool call without an id or name no longer wedges the session (pi-mono #10299)BF-a3859aa
D121The Anthropic SSE reader holds back a carriage return that ends a chunk (pi-mono #10390)BF-1fbdabb
D122OpenAI Completions keeps a signature-only reasoning.text delta (pi-mono #9534)BF-cb00273
D128OpenRouter image models that need the Images API are retried through /images (pi-mono #10652)BF-b730205
D129A Responses function call streamed with an empty call_id no longer wedges the session (pi-mono #10299)BF-0dff068
D130A managed-effort Anthropic request sends each managed field only with its beta (Pi #10759)BF-5e3e346
D131A stalled SSE stream that still sends keepalives ends after the HTTP idle timeout (pi-mono #10761, #10762)BF-3651a90
D132Session directories are created with mode 0700 (pi-mono #10770)BF-25c854d
D134One SSE line is bounded at Node's maximum string lengthdeliberate difference
D135clearQueue keeps queued extension custom messages (Pi #9886)BF-f98d129
D136A context edit after the usage anchor keeps the anchor (Pi #10287)BF-f98d129-2
D137A request carries at most the model's maxPerRequest images (Pi #10162)BF-f98d129-3
D138session_compact custom messages reach the overflow retry (Pi #9051)BF-f19efb1
D142Settlement keeps idle waiters pending and abort discards deferred prompts (Pi #10704, #10705)BF-31740ec
D143A device-attributes reply split across slow reads is not forwarded as input (Pi #10657)BF-0225a43
D144A dropped response stream keeps its cause in the error message (Pi #10697)BF-f07d033
D145Nested Errors keep their name and message in codemode output (Pi #10650)BF-35e46b1
D146RPC stdin end answers a Session-replacing command that Pi leaves unanswereddeliberate difference
D147Compactions requested in one input batch run one after anotherdeliberate difference
D148An aborted compaction answers one error; two Pi defects of overlapping compacts are not reproduceddeliberate difference
D150--no-stdin skips the startup read of piped stdin (Pi #10415)BF-D150
D151grep with context reads a window of each matched file (Pi #9276)BF-D151
D153The bash truncation notice names the size of the last line, not the bytes after the final newline (Pi #10015)BF-D153
D155Selection lists page with PageUp and PageDown (Pi #7629)BF-D155
D156MCP server url expands environment references (Pi #10654)BF-D156
D157PiG's startup additions stay quiet when the config root cannot be writtendeliberate difference
D158A setting of the wrong JSON type is ignored (Pi 1.4.0 throws)deliberate difference
D159A single grapheme wider than the editor's wrap width stays one chunk instead of exhausting the stackdeliberate difference
D165The durable image processor is native Go, not Photon's WebAssemblydeliberate difference
D167A history longer than the model's context window is summarized in chunksBF-85908ae
D173Tool search matches Chinese, Japanese and Korean queries (Pi #10816)BF-b99457f
D177The calls of a parallel durable tool round start in unspecified orderdeliberate difference
D178Facet modules load in an isolated Node child, not in the host processdeliberate difference
D180A provider transport failure records its Go cause as a provider_transport_failure diagnosticdeliberate difference
D181The commits of an unawaited durable job are ordered by goroutine scheduling, not by Pi's microtask hopsdeliberate difference
D182A proxy start event with an unusable content index ends the stream with an errordeliberate difference
D183A message delivered at the terminal proxy event is not changed by later eventsdeliberate difference
D184A script's unawaited tool call can be aborted before its tool starts, where Pi always starts it firstdeliberate difference
D185A Kitty CSI-u Shift+letter with no shifted alternate decodes as the uppercase letter, where Pi keeps the base…deliberate difference
D186A hung-up terminal under a session whose event loop holds nothing: Pi exits 0 or 129, PiG exits 129deliberate difference
D187A native SDK holds a batch's first host calls and responses in call and answer order, where Pi's single…deliberate difference
D188Opening a store at an older Durable schema version keeps a copy of it first, where Pi upgrades it in placedeliberate difference
D190The built-in file mutation queue also orders mutations of one existing file reached through a hard link or a…BF-648ff3e-13
D195An empty AWS_BEARER_TOKEN_BEDROCK is no token, where Pi's AWS SDK treats the variable as set and the request…BF-D195
D196Under skip-auth a bearer token from the process environment is not sent over an http:// endpoint, where Pi's…BF-D196
D198The Go, Rust and Python Type has no Codec family (Codec, Decode, Encode, DecodeBuilder…BF-D198
D199PiG lists OpenRouter's :nitro and :floor routing variants beside every OpenRouter model, where Pi lists only…deliberate difference
D200With no home directory PiG still runs the commands that load no resources, where Pi crashes at import, and…BF-D200
D203registerProvider with an empty apiKey leaves the provider unconfigured where Pi treats the empty string as…BF-D203

Fix-titled commits that are not bugs in a release

  • Failure found by the integration build of unreleased work: 2
  • Fix reverted before release: 1
  • Repair of a merge between development branches: 8
  • Review follow-up to unreleased work: 3
  • test-only: 1
PiG project documentation · MIT License