Skip to content

Fix git diff chip flicker by not running shell fallback under GitRepoStatusModel#9412

Open
anshul-garg27 wants to merge 3 commits intowarpdotdev:masterfrom
anshul-garg27:fix/git-diff-chip-flicker
Open

Fix git diff chip flicker by not running shell fallback under GitRepoStatusModel#9412
anshul-garg27 wants to merge 3 commits intowarpdotdev:masterfrom
anshul-garg27:fix/git-diff-chip-flicker

Conversation

@anshul-garg27
Copy link
Copy Markdown
Contributor

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

  • Added `test_externally_driven_git_diff_stats_does_not_run_shell_command` in `current_prompt_test.rs`. It mirrors the existing `test_externally_driven_chip_skips_periodic_timer` setup but asserts both `state.generator_handle.is_none()` and that the recording `CommandExecutor` saw no commands.
  • `cargo fmt -p warp -- --check` passes.
  • Couldn't run the full `cargo nextest` locally (Metal toolchain isn't available on this machine — same situation as Expand ~ in warp://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.

@cla-bot cla-bot Bot added the cla-signed label Apr 29, 2026
@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented Apr 29, 2026

@anshul-garg27

I'm starting a first review of this pull request.

I requested changes on this pull request and posted feedback.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

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 /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

oz-for-oss[bot]
oz-for-oss Bot previously requested changes Apr 29, 2026
Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

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

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 GitRepoStatusModel metadata can leave GitDiffStats empty 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() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ [IMPORTANT] Skipping the fetch here also skips applying already-computed 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 =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ [IMPORTANT] This test never initializes a session or 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.

anshul-garg27 pushed a commit to anshul-garg27/warp that referenced this pull request Apr 29, 2026
…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.
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Apr 29, 2026

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Anshul Garg.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email [email protected]
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@cla-bot cla-bot Bot removed the cla-signed label Apr 29, 2026
@anshul-garg27
Copy link
Copy Markdown
Contributor Author

Pushed 5168a4b addressing both concerns:

1. Hydration on subscribe — extracted the metadata-application body of the MetadataChanged closure into apply_git_repo_status_metadata and call it once up front in set_git_repo_status right after subscribing. The subscription callback now delegates to the same method, so existing-metadata hydration and on-change updates share a single code path. This covers the case where the model has metadata loaded before any subscriber attaches.

2. Test that actually fails on the pre-fix code — restructured test_externally_driven_git_diff_stats_does_not_run_shell_command to:

  • Bootstrap a real session (initialize_bootstrapped_session + load_external_commands)
  • Mark git as a known external command via the recording executor's first response, so the chip is Enabled and the pre-fix code would have called fetch_chip_value_once with the shell generator.
  • Set latest_context to a populated PromptContext.
  • await_generators after update_states_with_new_context.

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, test_set_git_repo_status_applies_existing_metadata, which exercises the hydration path directly by attaching a model whose metadata is already set and asserting both ShellGitBranch and GitDiffStats chip values are populated immediately — no MetadataChanged event needed.

Running /oz-review.

/oz-review

oz-for-oss[bot]
oz-for-oss Bot previously requested changes Apr 29, 2026
Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

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

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 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.

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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ [IMPORTANT] When the newly attached 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.

anshul-garg27 added a commit to anshul-garg27/warp that referenced this pull request Apr 30, 2026
…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.
@anshul-garg27 anshul-garg27 force-pushed the fix/git-diff-chip-flicker branch from 5168a4b to e5cf806 Compare April 30, 2026 06:16
@cla-bot cla-bot Bot added the cla-signed label Apr 30, 2026
@captainsafia captainsafia added the external-contributor Indicates that a PR has been opened by someone outside the Warp team. label Apr 30, 2026 — with Warp Dev Github Integration
anshul-garg27 and others added 2 commits May 1, 2026 06:09
…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.
anshul-garg27 added a commit to anshul-garg27/warp that referenced this pull request May 1, 2026
…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.
@anshul-garg27 anshul-garg27 force-pushed the fix/git-diff-chip-flicker branch from e5cf806 to 2c9ded5 Compare May 1, 2026 00:46
@anshul-garg27
Copy link
Copy Markdown
Contributor Author

Pushed 2c9ded5 addressing your second review.

The concern: the previous version's early return on metadata().is_none() left the chip values from the previous GitRepoStatusModel in place — visible whenever a session re-binds to a different repo and the new model's async fetch hasn't completed yet.

The fix: apply_git_repo_status_metadata now clears 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. The path is exercised both during set_git_repo_status (initial-attach hydration) and from the MetadataChanged callback (defensive — in practice the event always carries metadata, but the clearing branch is harmless if it ever doesn't).

Test: added test_set_git_repo_status_clears_stale_chip_values_when_new_model_has_no_metadata (after test_set_git_repo_status_applies_existing_metadata). It binds to a model with repo A's metadata, sanity-checks the chip shows feature-x, re-binds to a freshly-constructed model with no metadata, and asserts both chips are now None. The pre-fix version would have failed because the early return preserved repo A's values.

cargo fmt -p warp -- --check and cargo check --features=gui --tests -p warp both pass. cargo nextest skipped locally for the same Metal-toolchain reason as #9277.

Branch is rebased onto current master (91dee6d). Running /oz-review.

/oz-review

@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 1, 2026

@anshul-garg27

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 /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot dismissed stale reviews from themself May 1, 2026 00:50

Oz no longer requests changes for this pull request after the latest automated review.

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

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

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

@anshul-garg27
Copy link
Copy Markdown
Contributor Author

Update — also confirmed locally with cargo nextest (Xcode is now installed on my machine, so the earlier "Metal toolchain unavailable" caveat no longer applies):

Starting 3 tests across 1 binary (3685 tests skipped)
    PASS [0.052s] (1/3) warp context_chips::current_prompt::tests::test_set_git_repo_status_applies_existing_metadata
    PASS [0.052s] (2/3) warp context_chips::current_prompt::tests::test_set_git_repo_status_clears_stale_chip_values_when_new_model_has_no_metadata
    PASS [0.059s] (3/3) warp context_chips::current_prompt::tests::test_externally_driven_git_diff_stats_does_not_run_shell_command
────────────
     Summary [0.062s] 3 tests run: 3 passed, 3685 skipped

Command: cargo nextest run --features=gui -p warp -E 'test(test_set_git_repo_status_clears_stale_chip_values_when_new_model_has_no_metadata) | test(test_set_git_repo_status_applies_existing_metadata) | test(test_externally_driven_git_diff_stats_does_not_run_shell_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.
@anshul-garg27 anshul-garg27 force-pushed the fix/git-diff-chip-flicker branch from 2c9ded5 to 7314f8c Compare May 3, 2026 21:03
@captainsafia captainsafia requested review from a team and kevinchevalier and removed request for a team May 4, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed external-contributor Indicates that a PR has been opened by someone outside the Warp team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Git diff chip flickers between tracked-only and all-files count when untracked files are present

2 participants