Fix git diff chip flicker by not running shell fallback under GitRepoStatusModel#9412
Fix git diff chip flicker by not running shell fallback under GitRepoStatusModel#9412anshul-garg27 wants to merge 3 commits intowarpdotdev:masterfrom
Conversation
|
I'm starting a first review of this pull request. I requested changes on this pull request and posted feedback. Comment I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I requested changes on this pull request and posted feedback. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR changes externally-driven context chips so only chips with contextual initial generators run an initial fetch, avoiding the GitDiffStats shell fallback that disagrees with GitRepoStatusModel on untracked files.
Concerns
- Skipping the initial fetch without applying existing
GitRepoStatusModelmetadata can leaveGitDiffStatsempty for subscribers that attach after metadata has already been computed. - The added regression test does not set up a prompt context/session, so the old shell fallback would not have reached the recording executor and the test would pass without the production fix.
- Security pass: no security-specific findings in the changed prompt-chip refresh logic.
Verdict
Found: 0 critical, 2 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| // `GitDiffStats`), which can produce a different value than the | ||
| // external source and cause a brief flicker before the next external | ||
| // update overwrites it (see issue #9228). | ||
| if let Some(initial_gen) = chip_kind.initial_value_generator() { |
There was a problem hiding this comment.
GitRepoStatusModel metadata; when subscribe() returns an existing model, no MetadataChanged event is emitted, so GitDiffStats can stay empty until a later refresh/file event. Apply current metadata in set_git_repo_status or trigger a metadata refresh before relying solely on events.
| app.add_model(move |_| GitRepoStatusModel::new_for_test(repo_handle, None)); | ||
|
|
||
| let executor = Arc::new(RecordingCommandExecutor::default()); | ||
| let sessions = |
There was a problem hiding this comment.
latest_context, so the old shell fallback exits before calling the recording executor; add a bootstrapped session, set latest_context, and await generators so the test fails on the pre-fix code.
…ession test Oz raised two concerns on the previous version: 1. Skipping the shell fallback also skipped applying any already-computed `GitRepoStatusModel` metadata. Subscribing to the model does not by itself fire `MetadataChanged`, so a chip attaching to a model whose metadata was loaded earlier would stay empty until the next file event. Extracted the metadata-application logic out of the subscription closure into `apply_git_repo_status_metadata` and call it once up front in `set_git_repo_status` after subscribing. The `MetadataChanged` callback now delegates to the same method so there is a single hydration code path. 2. The regression test never set up a session or `latest_context`, so the pre-fix shell fallback was short-circuiting on the chip-disabled path before reaching the recording executor — meaning the test would have passed even without the production fix. Restructured the test to bootstrap a real session, populate `latest_context`, mark `git` as a known external command (so the chip is `Enabled` and the pre-fix code would actually call into the shell generator), and `await_generators` before asserting that no commands were recorded. Also added `test_set_git_repo_status_applies_existing_metadata` which exercises Oz's first concern directly: it constructs a `GitRepoStatusModel` with metadata already populated, attaches it via `set_git_repo_status`, and asserts that both `ShellGitBranch` and `GitDiffStats` chip values are visible immediately — without any subsequent `MetadataChanged` event firing.
|
Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Anshul Garg.
|
|
Pushed 1. Hydration on subscribe — extracted the metadata-application body of the 2. Test that actually fails on the pre-fix code — restructured
The recording executor's command list is asserted empty after the flush — which would have been non-empty under the pre-fix code's shell fallback. Also added a second test, Running /oz-review |
There was a problem hiding this comment.
Overview
This PR prevents externally-driven GitDiffStats from running the shell fallback and factors git status metadata application into a reusable helper for subscription events and initial hydration.
Concerns
- When a newly attached
GitRepoStatusModelhas not produced metadata yet, the new early return preserves the previous chip value whilerun_chipsnow skips the shell fallback. This can show stale diff stats from the old repo until the async metadata refresh emits.
Verdict
Found: 0 critical, 1 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| .as_ref() | ||
| .and_then(|w| w.upgrade(ctx)) | ||
| .and_then(|h| h.as_ref(ctx).metadata().cloned()) | ||
| else { |
There was a problem hiding this comment.
GitRepoStatusModel has no metadata yet, returning here preserves the previous repo's GitDiffStats because run_chips now skips the shell fallback; clear the externally-driven chip values on None metadata or on rebind so stale counts are not shown until the async refresh completes.
…ession test Oz raised two concerns on the previous version: 1. Skipping the shell fallback also skipped applying any already-computed `GitRepoStatusModel` metadata. Subscribing to the model does not by itself fire `MetadataChanged`, so a chip attaching to a model whose metadata was loaded earlier would stay empty until the next file event. Extracted the metadata-application logic out of the subscription closure into `apply_git_repo_status_metadata` and call it once up front in `set_git_repo_status` after subscribing. The `MetadataChanged` callback now delegates to the same method so there is a single hydration code path. 2. The regression test never set up a session or `latest_context`, so the pre-fix shell fallback was short-circuiting on the chip-disabled path before reaching the recording executor — meaning the test would have passed even without the production fix. Restructured the test to bootstrap a real session, populate `latest_context`, mark `git` as a known external command (so the chip is `Enabled` and the pre-fix code would actually call into the shell generator), and `await_generators` before asserting that no commands were recorded. Also added `test_set_git_repo_status_applies_existing_metadata` which exercises Oz's first concern directly: it constructs a `GitRepoStatusModel` with metadata already populated, attaches it via `set_git_repo_status`, and asserts that both `ShellGitBranch` and `GitDiffStats` chip values are visible immediately — without any subsequent `MetadataChanged` event firing.
5168a4b to
e5cf806
Compare
…atusModel Fixes warpdotdev#9228. When `set_git_repo_status` wires a `GitRepoStatusModel` up to a chip, `is_updated_externally` is what tells `run_chips` not to set up the periodic timer. But the same branch was still doing one initial fetch through the chip's main `generator()` whenever the chip didn't have a contextual `initial_value_generator`. For `GitDiffStats` that means running `git diff --shortstat HEAD` once on every block completion, which (a) excludes untracked files and (b) races with the watcher's own update — exactly the flicker the issue describes (correct value -> shell-fallback value -> correct value again). Only run the initial fetch when the chip has a contextual initial generator (today: `ShellGitBranch`, which reads the PreCmd value out of `PromptContext`). Those can't disagree with the watcher. For chips that have no contextual initial generator (today: `GitDiffStats`), skip the initial fetch entirely and let the watcher populate the value via its `MetadataChanged` events. Adds a regression test that asserts the `GitDiffStats` chip neither sets a `generator_handle` nor invokes any shell command when an external `GitRepoStatusModel` is providing values.
…ession test Oz raised two concerns on the previous version: 1. Skipping the shell fallback also skipped applying any already-computed `GitRepoStatusModel` metadata. Subscribing to the model does not by itself fire `MetadataChanged`, so a chip attaching to a model whose metadata was loaded earlier would stay empty until the next file event. Extracted the metadata-application logic out of the subscription closure into `apply_git_repo_status_metadata` and call it once up front in `set_git_repo_status` after subscribing. The `MetadataChanged` callback now delegates to the same method so there is a single hydration code path. 2. The regression test never set up a session or `latest_context`, so the pre-fix shell fallback was short-circuiting on the chip-disabled path before reaching the recording executor — meaning the test would have passed even without the production fix. Restructured the test to bootstrap a real session, populate `latest_context`, mark `git` as a known external command (so the chip is `Enabled` and the pre-fix code would actually call into the shell generator), and `await_generators` before asserting that no commands were recorded. Also added `test_set_git_repo_status_applies_existing_metadata` which exercises Oz's first concern directly: it constructs a `GitRepoStatusModel` with metadata already populated, attaches it via `set_git_repo_status`, and asserts that both `ShellGitBranch` and `GitDiffStats` chip values are visible immediately — without any subsequent `MetadataChanged` event firing.
…e-binding without metadata Oz's second review flagged: "When a newly attached `GitRepoStatusModel` has not produced metadata yet, the new early return preserves the previous chip value while `run_chips` now skips the shell fallback. This can show stale diff stats from the old repo until the async metadata refresh emits." The fix from the previous review hydrates chip values immediately on attach when the new model already has metadata, but it does nothing when the new model is in the more common state of "freshly created, async fetch hasn't completed yet." In that case the early return left the chips holding values from the *previous* repo's `GitRepoStatusModel`, which is visible whenever a session re-binds to a different repo. Changed `apply_git_repo_status_metadata` to clear `ShellGitBranch` and `GitDiffStats` to `None` when the current `git_repo_status` has no metadata — so the only state the chips ever reflect is the *current* model's, even before its first refresh emits. Once the model's async fetch completes and `MetadataChanged` fires, the chips populate as before. Also adds `test_set_git_repo_status_clears_stale_chip_values_when_new_ model_has_no_metadata`, which: 1. Binds the prompt to a model with metadata for repo A (`feature-x`). 2. Sanity-checks the chip is showing repo A's branch. 3. Re-binds to a freshly-constructed model with no metadata. 4. Asserts both chips are now `None`. The test would have failed under the previous version because the early return preserved repo A's values.
e5cf806 to
2c9ded5
Compare
|
Pushed The concern: the previous version's early return on The fix: Test: added
Branch is rebased onto current /oz-review |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I reviewed this pull request and requested human review from: @warpdotdev/oss-maintainers. Comment Powered by Oz |
Oz no longer requests changes for this pull request after the latest automated review.
There was a problem hiding this comment.
Overview
This PR removes the shell fallback fetch for externally-driven GitDiffStats chips and hydrates git chip state from existing GitRepoStatusModel metadata when attaching. The added regression coverage checks that the externally-driven diff stats path avoids shell commands and that metadata attach/empty states are handled.
Concerns
- No blocking correctness issues found in the changed lines.
- Security pass found no security concerns in this diff.
Verdict
Found: 0 critical, 0 important, 0 suggestions
Approve
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
|
Update — also confirmed locally with Command: |
…e-binding without metadata Oz's second review flagged: "When a newly attached `GitRepoStatusModel` has not produced metadata yet, the new early return preserves the previous chip value while `run_chips` now skips the shell fallback. This can show stale diff stats from the old repo until the async metadata refresh emits." The fix from the previous review hydrates chip values immediately on attach when the new model already has metadata, but it does nothing when the new model is in the more common state of "freshly created, async fetch hasn't completed yet." In that case the early return left the chips holding values from the *previous* repo's `GitRepoStatusModel`, which is visible whenever a session re-binds to a different repo. Changed `apply_git_repo_status_metadata` to clear `ShellGitBranch` and `GitDiffStats` to `None` when the current `git_repo_status` has no metadata — so the only state the chips ever reflect is the *current* model's, even before its first refresh emits. Once the model's async fetch completes and `MetadataChanged` fires, the chips populate as before. Also adds `test_set_git_repo_status_clears_stale_chip_values_when_new_ model_has_no_metadata`, which: 1. Binds the prompt to a model with metadata for repo A (`feature-x`). 2. Sanity-checks the chip is showing repo A's branch. 3. Re-binds to a freshly-constructed model with no metadata. 4. Asserts both chips are now `None`. The test would have failed under the previous version because the early return preserved repo A's values.
2c9ded5 to
7314f8c
Compare
Closes #9228.
Description
`GitDiffStats` flickers between the right value and a tracked-only-count value on every block completion when both modified-tracked and untracked files exist in the repo. The cause is exactly what the issue calls out: the chip has two update paths and they disagree.
The watcher path (`GitRepoStatusModel` -> `DiffStateModel::diff_metadata_against_head`) uses `git status --untracked-files=all` and includes untracked files. The shell-fallback path (`shell_git_line_changes`) uses `git diff --shortstat HEAD` and excludes untracked files.
`is_updated_externally` already disables the periodic timer for chips that have a watcher attached, but `run_chips` was still doing one shell-driven fetch each time the prompt context updated whenever the chip didn't have a contextual `initial_value_generator`:
```rust
if self.is_updated_externally(chip_kind) {
let initial_gen = chip_kind.initial_value_generator();
let generator = initial_gen.as_ref().unwrap_or(chip.generator()); // <-- shell fallback
self.fetch_chip_value_once(...);
}
```
For `ShellGitBranch` that's fine — its `initial_value_generator` is contextual, reads from `PromptContext`, and can't disagree with the watcher. For `GitDiffStats` it's `None`, so the call falls through to the chip's shell command and races with the watcher.
This change keeps the contextual initial fetch for `ShellGitBranch` but skips the fetch entirely for chips like `GitDiffStats` that don't have a contextual initial generator. The watcher's `MetadataChanged` event already populates the value (and `refresh_metadata` is called explicitly after block-completion events), so we don't need a parallel shell fetch.
Testing
~inwarp://action/new_tab?path=URLs #9277).Server API
No server changes.
Agent Mode
Not applicable.
Changelog Entries
`CHANGELOG-BUG-FIX`: Stop the git diff context chip from briefly flickering to a tracked-only count when a command completes in a repo that also has untracked files.