Conversation
Post-release development-phase marker after shipping 0.11.2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The site changelog is synced from CHANGELOG.md / CHANGELOG.zh-CN.md, but they weren't in pages.yml's path filter, so a release that only touched the changelog (+ code) didn't redeploy the website and its changelog went stale. Add both to the push / pull_request paths, and document the coupling (plus the manual-dispatch fallback) in the release guide. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e model pr-agent's retry_with_fallback_models logs the underlying exception into loguru's `artifact=` field, which the default format never prints, so a failed LLM call reached the run card as nothing but "all fallback models failed". For the local CLI providers this was compounded twice over: codex reports its failures on stdout (turn.failed / error events in the JSONL stream) and leaves stderr empty, while a turn that dies on a tool failure exits 0 with no assistant message at all, which then failed downstream as an unexplained empty prompt. - shim: extract the cause from the codex event stream on a non-zero exit, and raise explicitly when a zero exit carries an empty reply; - shim: emit `@@MEEBOX_LLM_ERROR@@` on stderr, alongside the usage sentinel, so the cause travels past pr-agent's lossy retry log; - main: prefer that sentinel over the generic marker for errorMessage, and classify it into errorHint (currently `model-unavailable`); - renderer: render one localized remedy line under the raw cause, since a local CLI provider's model lives in that CLI's own configuration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(agent): surface the real LLM failure cause and hint an unavailable model
A merge or review verdict returns as soon as the remote accepts it, while the state the UI reads is recomputed asynchronously and lands seconds later: a merged PR keeps reporting open and keeps coming back in the discovery list, and mergeStatus.canMerge still holds its pre-verdict value. Refreshing the instant the action returned therefore read pre-action state and looked like nothing had happened -- the merged PR sat in the list, and the merge button stayed hidden after the approval that had just unblocked it, both until the next periodic poll. Both actions now start a bounded backoff re-check in main: refresh that one PR on a growing delay until the expected change appears, broadcasting prs:changed for the renderer to reload. It is fire-and-forget (the IPC never waits on the remote settling) and past its window the periodic poll remains the backstop. A confirmed merge archives that one PR directly, via a new Poller method. The list filters on archivedAt rather than PR state, so a merged PR disappears only once archived -- and running a whole poll tick for it would put every connection's discovery fetch in front of the departure the user is waiting on. The departure a poll infers from absence is already established by the confirmed remote state, so it is asserted directly. For the same reason the renderer no longer fires a full poll on merge: that round is guaranteed to read pre-merge state, spending a round-trip to redraw the same list. Living in main covers every entry point with one implementation -- the buttons, the chat commands, and the CLI's review write actions share these controllers. The single-PR refresh is extracted for reuse, with comment-cache invalidation made opt-in: a background re-check must not make the open comment / diff panes re-fetch for a state the user never asked about. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A renderer failure could leave the window on its bare background permanently, indistinguishable from a hung app and unrecoverable short of a restart, because nothing was watching at any level: main.tsx rendered <App/> with no boundary above it, so any error thrown while rendering unmounted the whole tree and emptied #root; main registered no webContents listeners, so a dead renderer process went unnoticed; and no crash of either kind reached meebox.log, which is why such a report leaves nothing behind to diagnose. Three layers, each covering what the others structurally cannot see: - render-phase errors -> a root ErrorBoundary with a full-window AppCrashScreen (retry / reload), and the boundary now relays the stack via log:write, since the renderer console is not written to file and a caught render error is not an uncaught window error either; - failures before React mounts -> boot-guard.ts, imported first so it is armed before any other module can throw. A module failing while it initializes takes down the entry before render() runs, leaving no React and thus no boundary; the guard paints a plain-DOM recovery screen, depending on no React, i18n or stylesheet, since each is a candidate cause; - renderer process death -> render-process-gone / did-fail-load in main, logged and reloaded with a bounded retry budget so a page that dies on load cannot spin in a reload loop. 'unresponsive' is logged but not recovered: it usually resolves on its own and reloading would discard in-flight state. Also fixes a race in the same family: mergeSelectedPr compared the selection against the selectedId its callback had closed over, so selecting another PR while the merge round-trip was in flight cleared the PR the user had just opened. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat(pr): reconcile remote state after write actions, and never leave the window blank
The proxy is a single global egress, but part of what the app reaches commonly lives inside the network the proxy leads out of -- a self-hosted code platform, its git remote, an internal model server. Routing those through the proxy wastes a hop at best and makes them unreachable at worst, and turning the proxy off is not a fix, since the LLM egress still needs it. Only loopback was bypassed, and only as built-in behaviour, so there was no way to express any of this. Settings now take a bypass list, applying to every outbound path at once: the platform REST fetch, git over HTTPS, pr-agent and the local CLIs. The syntax mirrors the conventional NO_PROXY on purpose. The subprocess egresses are handed these rules as the environment variable and interpret them with their own libraries -- the app does not get to decide how git matches a host -- so any richer syntax would apply in-process and not in the subprocess, making the same config bypass on one egress while proxying on the other. Hence the portable subset (domain plus subdomains, IP literal, `*`, case-insensitive, port ignored) and the deliberate exclusion of CIDR ranges, which only some implementations honour. Matching lives in shared/no-proxy.ts, used by both paths so they cannot drift apart, and loopback is prepended to whatever the user configured: a local service must never be proxied, and that guarantee should not depend on the user having typed it. Normalization happens in the setProxy controller rather than the form, so the value stored is canonical however the config arrived, and what the user reads back is what is actually matched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat(proxy): let hosts bypass the proxy via a configurable no_proxy list
A mention is a reference to a person, but as plain text it disappears into the sentence -- exactly when the reader is scanning a thread for whether it concerns them. A remark plugin now renders each mention as a pill, wherever author-written prose that can name someone is rendered: the activity page, the inline comment zone, both draft surfaces and the PR description. Drafts are included because a draft is a comment about to be posted, and the same body must not read differently before and after publishing. Agent-facing markdown (chat, finding cards, rule previews) is not: nothing there addresses a person. Parsing lives beside writing. findMentions sits next to formatMention in shared/mention.ts, so the syntax -- notably Bitbucket's quoted @"first.last", required for a username containing a dot -- is defined once rather than once per direction. The pill accordingly shows @first.last: the quotes are platform syntax, not part of anyone's name, and this is the one place the rendered text intentionally differs from the source. Matching is syntactic, not resolved: there is no authoritative local list of who exists on the remote, so anything shaped like a mention is styled. That trade holds only because a false positive costs a tinted background and nothing else -- the text is unaltered and nothing becomes clickable. Boundary rules still exclude the common ones (email addresses, scoped package names, trailing punctuation), and code spans and fences are excluded for free, since only mdast text nodes are rewritten. The pill class had to be allowlisted for rehype-sanitize, which strips class from a span by default. It is allowed as a value-restricted attribute rather than as free-form className, so a comment cannot borrow arbitrary app styles by writing raw HTML. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat(comments): render @mentions as pills instead of running text
Suspense covers only the pending half of a dynamic import. When the chunk fails to load the promise rejects and the error propagates to the nearest boundary -- and neither lazy() site had one: the diff editor in PrPanel and a comment''s inline code context in CommentItem. DiffView carries an internal boundary around DiffPane, but that cannot see its own chunk failing to arrive. So a Monaco snippet that could not be fetched cost the user the entire application. Each lazy subtree now owns a LazyBoundary (Suspense plus a boundary) and the failure stays inside the pane that could not load. A stale chunk is singled out. The window holds a hashed module URL the app no longer has -- it was rebuilt or updated while the window stayed open -- so re-rendering re-requests the same dead URL and fails identically; only a reload recovers. Offering "retry" there is offering a button that cannot work, so isChunkLoadError detects the case and both the pane fallback and the root crash screen lead with reload and say why. Other errors keep retry. Observed via the root crash screen added earlier, which reported the failing module by name -- the diagnosis this containment work was meant to make possible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(gui): contain a failed lazy chunk instead of taking down the app
Assessed by diffing the 0.45.0 wheel against the vendored 0.39.0 rather than reading release notes: 82 of 123 files changed, including all eight we patch. Two patches are retired because upstream fixed the bugs. extract_hunk_headers now defaults an omitted hunk size to 1 instead of 0, so a single-line change no longer renders its old value as a still-present context line; get_diff_files now skips a file that fails to decode instead of crashing the review on a binary. Both were confirmed against the installed runtime, not assumed. The four remaining patches were probed under 0.45.0 and all apply -- none silently skipped by the version guard -- and get_line_link's call sites are unchanged, so the structured /review anchor still works. Three upstream features now default to on and append to the very output this app parses, which would have surfaced as bogus findings rather than as an obvious break: persistent_finding_state (upstream cross-run state the app already owns through drafts, finding closures and re-review verdicts) and two coverage footers. They are pinned off in buildPragentEnv, verified to be true by default and false under the override. Telemetry is new in 0.45.0 but ships disabled with no built-in endpoint, so it needs no handling. Also bumps the development version to 0.12.0-dev. Not exercised end to end: 0.45.0 revises six prompt files, and whether the rendered markdown still matches the parser needs a real /describe /review /ask against a live PR. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eadings splitting it Three defects surfaced by a real /describe run. None was introduced by the 0.45.0 upgrade -- the relevant upstream code is identical in 0.39.0 -- they had simply never been noticed. Line counts rendered as "+-1/--1". FilePatchInfo defaults num_plus_lines and num_minus_lines to -1 and only the real platform providers fill them in; LocalGitProvider does not. get_diff_files is wrapped rather than reimplemented to backfill both from the patch text, counted by the same rule the platform providers use, so upstream keeps owning how the diff is produced. Every walkthrough link pointed at "#L-1". Upstream passes relevant_line_start=-1 to mean "the whole file", and -1 is truthy, so a plain falsiness check let it through. The resulting fragment was then rejected by the anchor parser entirely (its line group accepts only digits), so the link yielded no anchor at all -- the fix therefore also recovers a path-level anchor for these rows. A merge tail in the PR description tore the result apart. `# Conflicts:` and the `#<tab>path` lines beneath it are markdown H1s, and /describe frames its own structure at H3, so the author's prose outranked the tool's structure and produced sections named after conflicted files. Section splitting now takes a minimum heading level, and describe splits only at H3 or deeper: quoted content stays inside the section that quotes it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
chore(pragent): upgrade the embedded runtime to pr-agent 0.45.0
0.56.0 replaces the identity `exports` map (`"./*": "./*"`) with
`"./*": "./esm/vs/*.js"`, so every deep import that still spelled the prefix
resolved to `esm/vs/esm/vs/…` and broke the build. It also moved the
worker-backed language services from `vs/language/<x>/monaco.contribution.js`
to typed namespaces on the package entry.
That second change lets the code get simpler rather than merely adapt: the four
`*Defaults` we disable are now read straight off `monaco` with real types, so
the hand-written LangServiceDefaults shape, the cast through unknown (the old
.d.ts was an empty `export {}`) and the by-name lookup are all gone.
Re-checked both suppressed Monaco errors against 0.56.0: still present, so the
suppression stays. `Missing requestHandler` is still a plain Promise.reject from
editorWebWorker.$fmr, and `TextModel got disposed` still goes through
onUnexpectedError in diffEditorWidget -- an unhandledrejection and a window
error respectively, which is what the two listeners catch. Recorded in the
comment so the next upgrade re-checks instead of assuming.
Security: monaco's pinned dompurify moves 3.2.7 -> 3.4.8, dropping the copy
behind 16 open advisories down to 4. Not enough to close the tracking issue
(#208) -- newer advisories widened the range past 3.4.11, and one has no fix in
any published version -- but its premise changed, so the issue is updated with
the revised bar rather than left stale.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
chore(deps): upgrade monaco-editor to 0.56.0
…of the pane The trailing commit divider rendered after everything in the timeline, so it was not marking a position -- it was tracking the bottom. Every message, thinking step or queued run that arrived pushed it further down, and a boundary that moves is worse than no boundary: it reads as if the commit itself keeps happening later. The boundary belongs immediately after the last run that reviewed the previous commit. Everything that arrives afterwards belongs below it, which is also what the divider means. So it now anchors before whatever first followed that run, and only falls back to the end in the one case where nothing follows it yet -- where the two positions coincide anyway. Placement moves into computeCommitDividers, a pure function: it is the part with the actual reasoning, and it was previously inlined in a component with no way to exercise it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(chat): anchor the commit divider to the boundary, not to the end of the pane
A reviewer''s comment is usually a claim -- is this actually a problem, does this path really run, was it handled elsewhere -- and settling it means reading the code the comment points at. That is what the agent is for, but the only way to hand it one was to paste the text into the chat box, dropping the anchor and the authorship on the way. The reference button attaches the comment as implicit context for the next question, so "is this right?" is a complete question. No IPC change was needed: agent:ask already carries referencedContext for the diff selection, and the two compose, since asking whether a comment is right about a selected region needs both. Cross-pane state goes in a store for the same reason the diff selection does: the comment surfaces and ChatPane are siblings with no common owner below App. Both comment surfaces get it, identically -- toggle to release, a chip with a clear button, released on send, cleared on PR switch. DraftZone does not: a draft is a comment not yet written by anyone else, so there is no claim to investigate. The glyph is the existing ShareIcon rather than something agent-flavoured, because that is already what "reference" looks like in this app (the finding reference button); one gesture should not have two icons. The prompt says what to do with the comment, not just what it says. Without that instruction a model tends to paraphrase the comment back instead of checking it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Investigating a comment usually ends in replying to it, and retyping the conclusion by hand was the remaining manual step. This needs the answer to know which comment it answers, so a referenced comment now hands off on send rather than being discarded: it detaches from the input bar but stays associated with the round it was asked in. The hand-off runs on every send, including unreferenced ones -- that is what ends a previous association, so an unrelated later answer never offers itself as a reply to an old comment. Memory only, one round: an association that outlived its round would attach an answer to the wrong comment, and that is worse than not offering the action at all. It produces a draft, not a posted reply. The words are the agent''s and publishing under the user''s name is the user''s call, so it lands in the same draft pool as every other pending reply -- editable, rejectable, published with the review batch. No IPC change: drafts:create already models a reply through replyTo, and the comment''s anchor carries over so an inline reply keeps its position in the diff. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ions The icon button sized itself to its glyph (line-height:1, its own padding) while the text buttons beside it size to a font-size line box, so it stood taller than the row it sits in. Both surfaces now take the same padding and font-size as the text buttons, so the line box -- and therefore the height -- is theirs; the inline zone simply stops overriding the shared rule it already belongs to. The svg is display:block so it does not sit on the text baseline and add descender space underneath. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat(comments): reference a comment into the agent, and turn its answer into a reply draft
Two regressions from the pr-agent 0.45.0 upgrade, both from new upstream defaults I did not pin, and neither reported as an error by anything. 0.45.0 stamps `<!-- pr-agent-generated -->` at the top of its output. That landed in the leading title-less section, whose body therefore counted as non-empty -- but an HTML comment renders to nothing, so every /describe result grew a card that looked blank. trimNoise already discarded blank lines, horizontal rules and the internal branch-name leak; it now also discards a line that is only an HTML comment, which makes the existing "no title and no content" rule cover this. 0.45.0 also added pr_diagram_direction='adaptive', which draws any chain past a few nodes top-down. Pinned to LR: upstream tunes that heuristic for a full-width page, while the diagram is read here in a narrow chat column, where a tall diagram pushes the rest of the result off screen and a wide one scrolls sideways and can be opened in the preview view. The pinning list in the design doc now separates the two ways an upstream default can be wrong here -- one adds output that parses as a bogus finding, the other changes presentation for a surface upstream cannot know about. The upgrade review only applied the first test, which is why the diagram default slipped through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(describe): drop the blank card and keep diagrams left-to-right
Version, changelog and lockfile for the 0.12.0 release: Unreleased renamed to the version section in both locales with their compare links, and the app version dropped from -dev. Proofread against v0.11.2..dev: 19 entries covering all ten merged PRs, the two locales matching in count and structure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release PR for 0.12.0.
Pre-release checklist
apps/desktop/package.jsonset to0.12.0(dropped-dev), lockfile synced vianpm install## [Unreleased]renamed to## [0.12.0] - 2026-09-09in bothCHANGELOG.mdandCHANGELOG.zh-CN.md, each with a[0.12.0]: …/compare/v0.11.2...v0.12.0link reference. No empty Unreleased left behind.v0.11.2..dev, covering all ten merged PRs; both locales match in entry count and section structure.lint/typecheck/test/buildpass.What is in this release
Ten PRs (#220–#229). Two of them are substantial dependency upgrades:
dompurifycopy behind most of the open advisories (4 remain, tracked in Track: DOMPurify advisories via monaco-editor's pinned dompurify@3.2.7 #208).The rest: comment→agent referencing with reply drafts (#228),
no_proxydirect-connect list (#222), mention pills (#223), post-action remote reconciliation (#221), and a set of failure-containment fixes (#221, #224) after a report of the window going permanently black.Post-release (mandatory, right after tagging)
masterintodev— verifygit log dev..masteris emptydevto the next-devNote for the tag
Tag must be
v0.12.0to match package.json. A mismatch does not fail CI — it only warns, and produces a wrong Release.🤖 Generated with Claude Code