Skip to content

fix: fall back to local git watcher for chat diff drawer#24512

Merged
ibetitsmike merged 19 commits into
mainfrom
mike/fix-exp-agents-diff-local-fallback
Apr 22, 2026
Merged

fix: fall back to local git watcher for chat diff drawer#24512
ibetitsmike merged 19 commits into
mainfrom
mike/fix-exp-agents-diff-local-fallback

Conversation

@ibetitsmike
Copy link
Copy Markdown
Collaborator

The Ctrl+D diff drawer in coder exp agents only rendered PR-backed
diffs returned by /api/experimental/chats/{id}/diff. Local working
tree changes in a chat's workspace returned an empty diff, so the
drawer showed "No diff contents" with no file summary.

Centralise diff loading behind a single fetchChatDiffContents helper
that first hits /diff, then falls back to the chat git watcher
WebSocket (/stream/git) when the remote diff is empty. Aggregate the
agent's WorkspaceAgentRepoChanges into a ChatDiffContents value so
the drawer can derive the file summary and styled body from the local
unified diff. Missing workspaces, missing agents, and watcher timeouts
are treated as graceful fallbacks that render the empty-diff
placeholder instead of a hard error.

Mux is opening this PR on Mike's behalf.

@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

The failing test-go-pg (ubuntu-latest) and test-go-pg-17 jobs both fail in provisioner/terraform.TestInstall with:

unable to verify checksums signature: openpgp: key expired

That test downloads and verifies HashiCorp's published terraform_1.14.5_SHA256SUMS.72D7468F.sig against the embedded HashiCorp PGP public key, and the key expired earlier today. It is unrelated to this PR, which only touches cli/exp_agents_*.

Evidence:

  • The same assertion path (provisioner/terraform/install.go:108) and signature URL (terraform_1.14.5_SHA256SUMS.72D7468F.sig) fail identically in both failing jobs.
  • test-go-pg (macos-latest), test-go-pg (windows-2022), and test-go-race-pg all pass here, as does everything that does not exercise the HashiCorp installer: fmt, gen, lint, check-build, test-e2e.
  • Main's last merged run on 2026-04-18 was green for test-go-pg (ubuntu-latest), so the delta is an external key rotation, not something introduced here.

Happy to rebase once the key bundle is refreshed (or leave this to a follow-up if someone is already on it).

Mux is posting this on Mike's behalf.

The Ctrl+D diff drawer in `coder exp agents` only rendered PR-backed
diffs returned by `/api/experimental/chats/{id}/diff`. Local working
tree changes in a chat's workspace produced an empty response, which
showed "No diff contents" in the drawer and no file summary.

Centralise diff loading behind a single `fetchChatDiffContents` helper
that first hits `/diff`, then falls back to the chat git watcher
WebSocket (`/stream/git`) when the remote diff is empty. Aggregate the
agent's `WorkspaceAgentRepoChanges` into a `ChatDiffContents` value so
the drawer can derive the file summary and styled body from the local
unified diff. Treat missing workspaces, missing agents, and watcher
timeouts as graceful fallbacks that render the empty-diff placeholder
instead of a hard error.
@ibetitsmike ibetitsmike force-pushed the mike/fix-exp-agents-diff-local-fallback branch from 6991a5e to 7f05c14 Compare April 20, 2026 17:58
@ibetitsmike ibetitsmike requested a review from mafredri April 20, 2026 18:43
@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

@codex review

Mux is running the Codex review loop on behalf of Mike.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f05c14333

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/exp_agents_render.go Outdated
…-quotes

The previous parser split the unquoted header with strings.Fields, so any
path containing spaces (e.g., 'a/foo bar.txt b/foo bar.txt') was treated
as four separate tokens and marked as a rename with truncated filenames.
For binary or mode-only diffs that lack '--- '/'+++ ' lines, those wrong
paths then leaked into the diff drawer summary.

The new parser recognizes both header forms:
  - Quoted form ('diff --git "a/<old>" "b/<new>"') via strconv.Unquote,
    which handles C-escaped paths (e.g., octal byte escapes).
  - Unquoted form by searching for a ' b/' split point where the two
    halves are equal after stripping a/ and b/ (the non-rename case),
    falling back to the single-space split for simple renames.

When the header is ambiguous (rename with spaces in the path), the parser
now returns ok=false. The caller still creates a placeholder entry so the
follow-up 'rename from'/'rename to'/'--- '/'+++ ' lines can fill in the
correct paths; flush() already drops entries with an empty FilePath.
@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

@codex review

Mux is running the Codex review loop on behalf of Mike.

Copy link
Copy Markdown
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First-pass review (Netero). This is a mechanical scan; the full review panel has not yet reviewed this PR.

The diff-fetching fallback and unified-diff parser are well-structured, and the test coverage is solid. One P2 finding: dead code left behind after removing the gitChangesMsg producer.

🤖 This review was automatically generated with Coder Agents.

Comment thread cli/exp_agents_model.go
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a94843fc15

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/exp_agents_diff.go Outdated
shouldIgnoreLocalDiffFallbackError only recognized two of the 400
responses watchChatGit can return. When the chat pointed at a deleted
workspace the server returned 'Chat workspace not found.' and the
diff drawer regressed from showing 'No diff to display.' to a hard
error.

Treat all of the watchChatGit 400 variants (no workspace bound,
workspace deleted, no agents, agent not connected) as ignorable and
fall back to the empty remote diff, matching the pre-fallback behavior
of the drawer. Add subtests covering each message.
The earlier fetchChatDiffContents refactor dropped the GetChatGitChanges
dispatch from toggleDiffDrawerCmd, which orphaned the whole gitChangesMsg
consumer chain. m.chat.gitChanges is never populated, so every downstream
consumer worked against a permanently nil slice and resolvedChatDiffChanges
always fell through to parseChatGitChangesFromUnifiedDiff.

Drop:
- gitChangesMsg struct and its chat handler branch
- gitChanges field on chatViewModel and its reset in setChat
- Draft/gitChangesMsg stale-generation test case
- resolvedChatDiffChanges helper now that the parsed-from-diff path is
  the only one we ever hit
- changes parameter from renderChatDiffSummary and renderDiffDrawer
  (both are now driven purely by the ChatDiffContents input)

Callers and tests are updated accordingly. No behavior change: the
diff drawer still derives its summary from the unified diff via
parseChatGitChangesFromUnifiedDiff.
@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

@codex review

Mux is running the Codex review loop on behalf of Mike.

@ibetitsmike ibetitsmike requested a review from mafredri April 20, 2026 19:49
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 957cfc3fc3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/exp_agents_render.go Outdated
Git emits C-quoted paths on 'rename from'/'rename to' lines whenever
the name contains non-ASCII bytes or control characters (with the
default core.quotepath setting). The parser was storing the raw
token, so a quoted rename like

    rename from "b\\303\\244r old.txt"
    rename to "b\\303\\244r new.txt"

overwrote the already-decoded header paths and the diff summary
showed the octal-escaped form. Route these lines through
trimUnifiedDiffPath just like '--- '/'+++ ' so the paths decode to
'bär old.txt' and 'bär new.txt'.
@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

@codex review

Mux is running the Codex review loop on behalf of Mike.

@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

@codex review

Re-pinging Codex for head 873d208. Mux is running the Codex review loop on behalf of Mike.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 873d208dbd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/exp_agents_render.go Outdated
Comment thread cli/exp_agents_render.go Outdated
Two follow-ups from the Codex review of the earlier rename-path fix:

1. Added or removed lines inside a hunk can legitimately start with
   '+++ ' or '--- ' (the content happens to begin with '++ ' or '-- '),
   so treating every such line as a file header would overwrite
   FilePath and OldPath and miscount additions/deletions. Track an
   inHunk flag that flips on the first '@@' marker after each
   'diff --git' header and gate the header-only cases (new/deleted
   file mode, rename from/rename to, ---, +++) on !inHunk. Inside the
   hunk we instead credit lines starting with '+' or '-' to the
   addition/deletion counters.

2. 'rename from'/'rename to' paths are repository-relative and never
   carry the a/ or b/ prefix, so trimUnifiedDiffPath (which strips
   a/ and b/) would truncate real directories. Introduce a
   decodeQuotedDiffLinePath helper that only handles C-quote decoding
   and TrimSpace, and route the rename-line parsing through it.

Added tests covering a rename that really lives under a literal
'a/' directory and a hunk body that contains '--- '/'+++ ' content
lines.
Copy link
Copy Markdown
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Panel review (12 reviewers). DEREM-1 from Round 1 was cleanly addressed in 957cfc3.

The consolidation from two parallel API calls into a single sequential fetch with WebSocket fallback is well-structured. The unified diff parser handles quoted paths, renames, and multi-repo aggregation correctly. Test coverage is solid across the main paths.

1 P2, 3 P3, 5 Nit. The P2 is a parser bug that corrupts file summaries for diffs containing SQL-style comments. The P3s are a missing 403 fallback case, server message coupling, and test coverage gaps.

Takumi verified all concurrency paths are clean (goroutine lifecycle, timeout handling, generation guards, race detector). Pariston confirmed the problem framing and solution alignment are correct. Mafu-san observed the clean response to R1 feedback.

"A deleted line whose original content starts with -- becomes --- Comment text in the diff. The parser matches this as a file header, sets current.OldPath to garbage, and skips the deletion counter." (Hisoka)

🤖 This review was automatically generated with Coder Agents.

Comment thread cli/exp_agents_render.go Outdated
Comment thread cli/exp_agents_diff.go
Comment thread cli/exp_agents_diff.go Outdated
Comment thread cli/exp_agents_diff_test.go
Comment thread cli/exp_agents_diff.go
Comment thread cli/exp_agents_diff_test.go Outdated
Comment thread cli/exp_agents_diff_test.go Outdated
Comment thread cli/exp_agents_diff_test.go Outdated
Comment thread cli/exp_agents_render.go Outdated
Comment thread cli/exp_agents_diff.go Outdated
@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

@codex review

Mux is running the Codex review loop on behalf of Mike.

@ibetitsmike ibetitsmike requested a review from mafredri April 20, 2026 20:44
Copy link
Copy Markdown
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review blocked. DEREM-4 (P2, inHunk parser bug) was addressed in 59a1318, confirmed via the parallel Codex thread.

10 findings from Round 2 have no author response or code change. Further review is blocked until the author responds or pushes fixes.

P3 findings needing response:

  • DEREM-5: Server message substring matching (no shared constants or error codes)
  • DEREM-6: HTTP 403 not treated as graceful fallback
  • DEREM-7: Four test coverage gaps (non-empty remote diff path, error propagation, edge cases)
  • DEREM-8: Missing doc comment on dialChatGit explaining why Client.Dial can't be used

Nit findings needing response:

  • DEREM-2: Stale tc := tc loop capture
  • DEREM-3: Stale message := message loop capture
  • DEREM-9: time.Sleep in test handler
  • DEREM-10: context.Background() vs t.Context()
  • DEREM-11: strings.Split vs strings.SplitSeq
  • DEREM-12: 1 << 22 without // 4MiB annotation

Acknowledging findings (with or without fixing) unblocks the next round.

🤖 This review was automatically generated with Coder Agents.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59a1318884

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/exp_agents_render.go
shouldIgnoreLocalDiffFallbackError matched the server-side 400 messages
from watchChatGit as lowercased substrings, so a server-side reword
would silently stop the fallback and surface the raw error in the TUI
diff drawer. It also only handled 404 and the 400 variants, so a 403
from authorizeChatWorkspaceExec when the chat owner's workspace
permissions were revoked still bubbled up as a hard error for a
scenario the remote diff endpoint handles gracefully.

Expose the ignorable messages as shared codersdk constants, add
ChatGitWatchAgentStateMessage for the formatted agent-state case, and
IsChatGitWatchFallbackMessage that both the server and client now
reference. shouldIgnoreLocalDiffFallbackError calls the predicate and
also treats 403 as ignorable to match the remote diff endpoint's
graceful behavior.

Addresses mafredri Round 2 DEREM-5, DEREM-6.

> Mux is running the Codex review loop on behalf of Mike.
- drop the redundant tc := tc loop-variable capture in
  parseUnifiedDiffHeaderPaths table test; Go 1.22+ scopes range
  variables per iteration (DEREM-2).
- replace time.Sleep in the watcher timeout test with conn.Read on
  the accepted WebSocket so the handler drains cleanly when the
  client disconnects instead of blocking on a fixed wait (DEREM-9).
- thread t.Context() through the fallback tests so the handlers and
  requests auto-cancel on test cleanup (DEREM-10).
- replace strings.Split with strings.SplitSeq in the unified-diff
  parser so a 4 MiB diff does not allocate a full []string just to
  iterate once (DEREM-11).
- expand IgnoresMissingWorkspaceFallbackErrors into a subtest per
  ignorable message so each 400 variant has coverage.

> Mux is running the Codex review loop on behalf of Mike.
renderDiffDrawer called renderChatDiffSummary on every View() pass,
and that helper walked the full unified diff through
parseChatGitChangesFromUnifiedDiff each time. For 4 MiB local diffs
(the watcher's read limit) routine redraw triggers like keypresses,
resize, and status ticks repeatedly re-split and re-parse the same
text and can stall the TUI.

Cache the rendered summary on chatViewModel.diffSummary when the
diffContentsMsg arrives and pass it into renderDiffDrawer instead.
setChat clears the cache alongside diffContents so a new chat does
not inherit a stale summary.

Addresses Codex P2 feedback on 59a1318.

> Mux is running the Codex review loop on behalf of Mike.
…r paths

Add four subtests for the branches that were previously uncovered:
- ReturnsRemoteDiffWithoutDialingWatcher asserts the fast path at
  fetchChatDiffContents returns the non-empty remote diff without
  dialing the WebSocket fallback. The git stream handler fails the
  request loudly if it ever fires, so inverting the condition
  regresses noisily.
- PropagatesRemoteDiffAPIErrors asserts a 500 from /diff is returned
  verbatim and the local watcher is skipped.
- SurfacesNonIgnorableWatcherErrors asserts a 500 from /stream/git
  is returned verbatim instead of collapsing to the empty remote diff.
- Split TestBuildLocalChatDiffContents into subtests and add two edge
  cases covering the empty-repositories early return and the
  removed/empty UnifiedDiff filter, plus the all-skipped variant.

Addresses mafredri Round 2 DEREM-7.

> Mux is running the Codex review loop on behalf of Mike.
@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

Round 7: addressed all 5 findings from @mafredri's latest review on head 759c7ab in 6aa1579:

  • [DEREM-15 P3] Removed orphan codersdk.ExperimentalClient.GetChatGitChanges (zero callers, no server route). Kept ChatGitChange type since the CLI diff parser still uses it.
  • [DEREM-16 P3] Added IgnoresForbiddenWatcherFallbackErrors subtest for the 403 branch of shouldIgnoreLocalDiffFallbackError.
  • [DEREM-17 P3] Cached lipgloss-styled diff body in chatViewModel.diffStyledBody, invalidated on the same trigger as diffSummary (stable across redraws since setRenderer runs once at startup). renderDiffDrawer accepts the cached body with an empty-string fallback for test callers.
  • [DEREM-18 P3] Added BackfillsRemoteMetadataWhenLocalDiffIsSingleRepo subtest: remote metadata (Provider, PR URL) + empty remote Diff + single-repo local watcher result; asserts Branch/RemoteOrigin stay local while Provider/PullRequestURL are backfilled from remote.
  • [DEREM-20 Nit] Drove the fallback-message subtest matrix from codersdk.ChatGitWatch* constants (plus ChatGitWatchAgentStateMessage helper) so a server-side rewording flows automatically.

Validation: make lint (0 errors), focused tests (TestFetchChatDiffContents, TestBuildLocalChatDiffContents, DiffDrawerMemoizesSummary, TestRenderChat*) pass, full codersdk/... short tests pass. Mafredri's 4 inline threads resolved.

Mux acting on behalf of Mike.

@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

@codex review

@ibetitsmike ibetitsmike requested a review from mafredri April 21, 2026 01:51
@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review (3 reviewers). All 20 prior findings addressed. CI green. Netero clean. Mafuuu and Meruem: no findings.

20 findings raised across 5 rounds: 2 P2 correctness bugs (dead code chain, hunk body misparse), 8 P3 (403 fallback, string coupling, test gaps, dead SDK method, diff body caching, metadata backfill), 7 Nits. All addressed with dedicated commits.

One remaining P3: the 404 branch in shouldIgnoreLocalDiffFallbackError lacks a test (same class as DEREM-16). Otherwise the PR is clean and ready for human review.

🤖 This review was automatically generated with Coder Agents.

Comment thread cli/exp_agents_diff.go
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6aa1579e04

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/exp_agents_diff.go Outdated
…ackfill

Addresses two findings from the Codex + mafredri review on 6aa1579:

- [Codex P2] Metadata backfill was gated on `localDiff.Branch != nil || localDiff.RemoteOrigin != nil`, but that is an unreliable proxy for "single contributing repository". A repo in detached HEAD with no origin remote legitimately leaves both fields nil even though exactly one repository produced the diff. Codex correctly pointed out that this case silently dropped remote Provider/PullRequestURL from the drawer.

  Replace the proxy with an explicit `singleRepo` flag returned from buildLocalChatDiffContents so the backfill invariant ("single contributing repo is safe to backfill, multi-repo is not") is expressed in the type. Propagate the flag through fetchLocalChatDiffContents and gate the merge block on it. Update the block comment so it documents the invariant rather than the old proxy.

- [DEREM-21 P3, mafredri] shouldIgnoreLocalDiffFallbackError's 404 branch had no test even though every other ignorable-error branch (context.DeadlineExceeded, 403, and each 400 fallback message) now has a dedicated subtest. Add IgnoresNotFoundWatcherFallbackErrors mirroring the 403 coverage added for DEREM-16 so the branch is mechanically covered and a future regression cannot silently remove it.

Also add BackfillsRemoteMetadataWhenSingleRepoHasBlankBranchAndOrigin to cover the singleRepo edge case end-to-end: single contributing repo with empty Branch/RemoteOrigin strings plus non-empty remote metadata asserts that every remote field is backfilled.

> Mux is Mike's coding agent. This commit was made on Mike's behalf.
@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

Round 8: addressed the two review findings on head 6aa1579 in 0d3f417:

  • [Codex P2] Metadata backfill is now gated on an explicit singleRepo flag returned by buildLocalChatDiffContents rather than the localDiff.Branch != nil || localDiff.RemoteOrigin != nil proxy. A single repo in detached HEAD with no origin remote legitimately leaves both nil, but the backfill must still run so remote Provider/PullRequestURL flow through. The flag propagates through fetchLocalChatDiffContents and the block comment now documents the invariant ("single contributing repo is safe to backfill, multi-repo is not") instead of the old proxy.
  • [DEREM-21 P3, mafredri] Added IgnoresNotFoundWatcherFallbackErrors mirroring the 403 coverage from DEREM-16 so shouldIgnoreLocalDiffFallbackError's 404 branch has mechanical coverage.

Also added BackfillsRemoteMetadataWhenSingleRepoHasBlankBranchAndOrigin to exercise the Codex edge case end-to-end (single contributing repo with empty branch/origin strings plus non-empty remote metadata, assert every remote field is backfilled).

Updated the four buildLocalChatDiffContents unit tests to consume the new (diff, singleRepo) return and assert the expected flag in each case.

Pre-commit passed (gen/fmt/lint/build). ./cli tests pass (63.7s). Ready for re-review.

Mux is Mike's coding agent. This update was posted on Mike's behalf.

@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

@codex review

@ibetitsmike ibetitsmike requested a review from mafredri April 21, 2026 04:10
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d3f417c38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/exp_agents_diff.go Outdated
Addresses Codex P2 on head 0d3f417: the local chat git watcher set a
4 MiB websocket read limit but agentgit caps each repository's
UnifiedDiff at 3 MiB (maxTotalDiffSize), so a Changes message
aggregating two or more maxed-out repos legitimately exceeds 4 MiB and
triggers a coder/websocket close with StatusMessageTooBig. Because
wsjson.Decoder swallowed the read error (log at Debug, close the
channel), fetchLocalChatDiffContents surfaced a generic
"git watch connection closed" error and the diff drawer crashed with
a hard error in exactly the large-multi-repo case the local fallback
was meant to improve: a regression from the pre-fallback behavior.

Fix it with a narrowly scoped, two-layered degrade path:

1. Bump the client-side read limit from 4 MiB to 32 MiB
   (localChatDiffReadLimit), which covers ~10 maxed-out repos and makes
   realistic multi-repo worktrees just work without any fallback.

2. For pathological payloads beyond 32 MiB, treat the specific
   StatusMessageTooBig close as ignorable. fetchLocalChatDiffContents
   now reads from the websocket directly (bypassing wsjson.NewStream)
   so it can inspect websocket.CloseStatus(err), returning the narrow
   errLocalDiffMessageTooLarge sentinel on read-limit violations.
   shouldIgnoreLocalDiffFallbackError maps that sentinel onto the
   same remote-empty-diff fallback we already use for 404/403/400.
   Generic websocket close / decode / protocol errors continue to
   surface as hard errors so real regressions do not silently
   disappear behind the fallback.

Tests:

- IgnoresWatcherMessageTooBigCloses accepts the websocket, reads the
  refresh, then closes with StatusMessageTooBig. The client must
  degrade to the empty remote diff without error.
- SurfacesUnexpectedWatcherCloseErrors closes with StatusInternalError
  instead and pins that the fallback is scoped to MessageTooBig only,
  so a future attempt to blanket-ignore every close reason immediately
  breaks the test.

Pre-commit (gen/fmt/lint/build) passed. Full cli/ tests pass in 60.9s.

> Mux is Mike's coding agent. This commit was made on Mike's behalf.
@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

Round 9: addressed Codex P2 on head 0d3f417 in fcd8b7b.

[Codex P2] The local chat git watcher set a 4 MiB websocket read limit but agentgit caps each repo's UnifiedDiff at 3 MiB (maxTotalDiffSize), so a Changes message aggregating two or more maxed-out repos legitimately exceeded 4 MiB and triggered a StatusMessageTooBig close. wsjson.Decoder swallowed the read error, so fetchLocalChatDiffContents returned a generic "connection closed" and the diff drawer crashed in exactly the large-multi-repo case the local fallback was meant to improve.

Fixed with a two-layered degrade path:

  1. Bump read limit from 4 MiB to 32 MiB (localChatDiffReadLimit), which covers ~10 maxed-out repos and makes realistic multi-repo worktrees just work without any fallback.
  2. Narrow sentinel for pathological payloads. fetchLocalChatDiffContents now reads from the websocket directly (bypassing wsjson.NewStream) so it can inspect websocket.CloseStatus(err). StatusMessageTooBig maps onto errLocalDiffMessageTooLarge and shouldIgnoreLocalDiffFallbackError degrades to the remote empty diff. Generic close / decode / protocol errors still surface as hard errors so real regressions are not silently hidden behind the fallback.

Tests:

  • IgnoresWatcherMessageTooBigCloses: handler closes with StatusMessageTooBig, client must degrade to empty remote diff.
  • SurfacesUnexpectedWatcherCloseErrors: handler closes with StatusInternalError, client must surface the error. Pins the fallback scope so a future attempt to blanket-ignore close reasons immediately breaks.

Pre-commit (gen/fmt/lint/build) passed. Full ./cli tests pass in 60.9s.

Mux is Mike's coding agent. This update was posted on Mike's behalf.

@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status: APPROVED

All 23 findings resolved across 6 rounds. CI green. Netero clean. Panel (Mafuuu, Meruem, Hisoka): no findings.

Over 6 rounds this review raised 2 P2 correctness bugs, 9 P3 defects, 7 Nits, and 5 dropped findings. Every finding was addressed with dedicated commits. The author improved the code beyond the original findings: shared constants replaced string coupling, diff body caching was added alongside summary caching, and the metadata backfill was refactored from a nil-check proxy to an explicit singleRepo flag.

937 test lines cover 666 production lines. Every branch in shouldIgnoreLocalDiffFallbackError has a dedicated subtest. The unified diff parser handles quoted paths, renames, spaces, and hunk-body lookalikes. Concurrency paths are clean.

🤖 This review was automatically generated with Coder Agents.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ibetitsmike ibetitsmike marked this pull request as ready for review April 21, 2026 13:08
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fcd8b7b858

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/exp_agents_render.go
Mirrors Round 7 DEREM-15 on the frontend side. site/src/api/api.ts
defined ExperimentalApiMethods.getChatGitChanges (plus the internal
ChatGitChangeResponse interface backing it) that targeted
GET /api/experimental/chats/{id}/git-changes, but:

- No call site exists anywhere under site/src/. The method was a pure
  orphan, exactly like codersdk.ExperimentalClient.GetChatGitChanges
  was on the Go side before Round 7 removed it.
- The HTTP route is not registered in coderd's chat router. The
  frontend's live diff panels rely on GET /diff (RemoteDiffPanel via
  chatDiffContents query) and the /stream/git websocket (useGitWatcher
  + GitPanel + LocalDiffPanel). /git-changes was never part of that
  wiring.

Drop the method and the ChatGitChangeResponse interface. The
TypesGen.ChatGitChange generated type stays because the Go codersdk
type is still referenced by parseChatGitChangesFromUnifiedDiff in cli.

No behavior change.
@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

@codex review

@ibetitsmike ibetitsmike requested a review from mafredri April 21, 2026 15:21
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 03d5021385

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/exp_agents_diff.go Outdated
…-repo diff

Round 10 addresses both Codex findings on head 03d5021.

[Codex P1] Fallback on upstream oversize close statuses.
fetchLocalChatDiffContents previously degraded only on
StatusMessageTooBig, but the coderd watchChatGit proxy
(coderd/exp_chats.go) always closes the client stream with
StatusGoingAway regardless of why the upstream agent->coderd hop
failed. In particular, when that hop's 4 MiB read limit
(workspacesdk/agentconn.go SetReadLimit) is exceeded, the agent
closes its end with StatusMessageTooBig but the proxy does not
propagate that status. So for diffs between the 4 MiB upstream
limit and our 32 MiB client read limit the client only ever sees
StatusGoingAway and Round 9's narrow match fails to degrade,
returning a hard error in exactly the multi-repo worktree case
that local fallback was meant to improve.

Broaden the close-status match to cover both StatusMessageTooBig
and StatusGoingAway while keeping every other close status
(StatusInternalError, StatusProtocolError, ...) and every
non-close read error propagating so real protocol regressions
still reach the user. Rename the narrow errLocalDiffMessageTooLarge
sentinel to errLocalDiffWatchClosed to reflect the broader
semantic. Pin the new behavior with
TestFetchChatDiffContents/IgnoresWatcherGoingAwayCloses alongside
the existing MessageTooBig test; the SurfacesUnexpectedWatcherCloseErrors
test (which uses StatusInternalError) continues to enforce that
unrelated close reasons still surface.

[Codex P2] Flag partially unparsable diffs in summary.
renderChatDiffSummary only fell back to 'Changes present but
could not be summarized.' when parseChatGitChangesFromUnifiedDiff
returned zero entries, but a multi-repo aggregate assembled by
buildLocalChatDiffContents can legitimately mix real 'diff --git'
chunks from small repos with agent/agentgit's 'Total diff too
large to show...' placeholder for repos that exceed
maxTotalDiffSize (3 MiB). In that case the function counted the
real chunks, returned a normal 'N files changed' summary, and
silently dropped the oversized repo. The drawer then
underreported the changeset in exactly the multi-repo case this
feature is meant to improve.

Detect the placeholder prefix separately via
hasOversizedRepoPlaceholder and append
'(some repositories omitted: diff too large to summarize)' to the
summary whenever one is present. Scoped to lines that start with
the placeholder prefix so a legitimate '+' added line that happens
to contain the phrase cannot trigger a false positive. Covered by
TestExpAgentsRender/RenderDiffDrawer/FlagsPartiallyUnparsableMultiRepoDiff
alongside the existing ShowsFallbackForUnparsableNonEmptyDiff case
(placeholder-only input still goes through the zero-changes branch).
@ibetitsmike
Copy link
Copy Markdown
Collaborator Author

Round 10: addressed both Codex findings on head 03d5021 in 2668c33.

  • [Codex P1] fetchLocalChatDiffContents previously degraded only on StatusMessageTooBig, but the coderd watchChatGit proxy (coderd/exp_chats.go) always closes the client stream with StatusGoingAway regardless of why the upstream agent->coderd hop failed. When that hop's 4 MiB read limit (workspacesdk/agentconn.go SetReadLimit) is exceeded, the agent closes its end with StatusMessageTooBig but the proxy does not propagate that status. For diffs between the 4 MiB upstream limit and our 32 MiB client read limit the client only ever saw StatusGoingAway and Round 9's narrow match failed to degrade. Broaden the match to cover StatusMessageTooBig AND StatusGoingAway while keeping every other close status (StatusInternalError, StatusProtocolError, ...) and every non-close read error propagating. Rename the sentinel errLocalDiffMessageTooLarge -> errLocalDiffWatchClosed. New test IgnoresWatcherGoingAwayCloses pins the behavior; existing SurfacesUnexpectedWatcherCloseErrors still enforces that unrelated closes surface.
  • [Codex P2] renderChatDiffSummary fell back to the 'Changes present but could not be summarized.' message only when parseChatGitChangesFromUnifiedDiff returned zero entries, but a multi-repo aggregate assembled by buildLocalChatDiffContents can mix real diff chunks with agent/agentgit's 'Total diff too large to show...' placeholder for repos that exceed maxTotalDiffSize. In that case the summary rendered a normal 'N files changed' line and silently dropped the oversized repo. Added hasOversizedRepoPlaceholder (scoped to line-start prefix matching to avoid false positives from diff bodies that happen to contain the phrase) and append '(some repositories omitted: diff too large to summarize)' whenever one is detected. New test FlagsPartiallyUnparsableMultiRepoDiff pins the behavior.

Local validation: relevant cli tests (TestFetchChatDiffContents, TestExpAgentsRender, TestBuildLocalChatDiffContents) all pass. make pre-commit green.

Mux working on behalf of Mike.

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Contributor

@coder-agents-review coder-agents-review Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status: APPROVED

All clear. Netero clean. Panel (Mafuuu, Meruem, Hisoka): no findings.

The two new commits since R6 (dead getChatGitChanges TS removal, oversized watcher payload graceful degradation with 32 MiB read limit + StatusMessageTooBig sentinel) are clean. Test coverage now includes the oversized-payload fallback path.

23 findings raised and resolved across 7 rounds. No open findings. CI green. Ready for human review.

🤖 This review was automatically generated with Coder Agents.

@ibetitsmike ibetitsmike merged commit 7904bed into main Apr 22, 2026
27 of 29 checks passed
@ibetitsmike ibetitsmike deleted the mike/fix-exp-agents-diff-local-fallback branch April 22, 2026 16:08
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants