You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commits list remains stale after switching agents and creating new commits #7416
When working across multiple agents, new commits made by an agent are not reflected in the commits list after switching to or continuing the session with another agent. The commits view continues to display a stale commit history instead of automatically refreshing or syncing with the latest repository state.
Expected behavior:
The commit history should refresh automatically or reflect the latest HEAD/commits whenever an agent creates new commits, even when continuing across different agents.
Steps to reproduce
Open a workspace and start a session with an agent.
Have the agent create one or more commits (or let another agent make new commits on the branch).
Continue the workflow with another agent via the "Continue with another agent" option
Make additional commits using the new agent.
Inspect the commits list in the UI.
Observe that the list remains stale and does not show the newly created commits.
The report is plausible and I can point at a concrete gap in the desktop renderer. New commits made outside the app's own commit button — i.e. by an agent in a terminal, which is exactly what the "have the agent commit / continue with another agent / commit again" flow does — do refresh the changed-files list and status badge, but not the commits list. The server side is fine (git.listCommits shells out to git log on every call with no caching), and the host-side git:changed event does fire on an agent commit. The renderer just never invalidates the git.listCommits query when that event arrives, so the cached commit list sits there until something unrelated happens to refetch it. Switching agents is incidental to the cause — it's a reliable way to generate commits from outside the UI while the app window never loses focus.
Affected code
apps/desktop/src/renderer/hooks/host-service/useGitStatus/useGitStatus.ts:65-87 — the single owner of the git:changed subscription for a v2 workspace. Its invalidate callback refreshes git.getStatus (via the trailing scheduler), git.getDiffPatch, git.getDiff, and git.getBaseBranch. git.listCommits is not in the list.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx:138-141 — the sidebar's commits query. Its only automatic refresh is refetchOnWindowFocus: true; the default staleTime is 5s (packages/workspace-client/src/providers/WorkspaceClientProvider/WorkspaceClientProvider.tsx:7). There's no interval and no event subscription. utils.git.listCommits.invalidate is called in exactly one place in the renderer — the base-branch mutation at useChangesTab.tsx:113-119.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/ChangesControl/components/ShipControl/ShipControl.tsx:96-107 — issues the same query key ({ workspaceId, baseBranch }) with refetchInterval: 10_000, so while that component is mounted its poll incidentally keeps the sidebar list fresh. It's gated on canCreatePr = projectId != null (ShipControl.tsx:62) and only rendered on the no-PR half of the top-bar control, so the list goes permanently stale in session workspaces (no project) and once a PR exists. That's the likely reason this looks intermittent between workspaces.
For contrast, the legacy v1 Changes view reads commits out of the status payload itself (.../RightSidebar/ChangesView/ChangesView.tsx:521, shared/changes-types.ts:46), so it rode along with the status refresh. The v2 sidebar split commits into a separate query and that query was left out of the invalidation set.
Suspected root cause
Missing git.listCommits invalidation in the git:changed handler at useGitStatus.ts:65-85. An agent commit produces a broad git:changed (no paths, since .git/ was touched — packages/host-service/src/events/git-watcher.ts:482-524), the handler runs, status/diffs refresh, and the commits query is left untouched. With the Electron window continuously focused during an agent session, refetchOnWindowFocus never fires either, so the list stays as it was until the user blurs and refocuses the app, changes the base branch, commits through the UI (ShipControl.tsx:113-122 refetches explicitly), or happens to have ShipControl's 10s poll mounted.
Ruled out: server-side staleness. git.listCommits (packages/host-service/src/trpc/router/git/git.ts:313-351) runs git log <baseRef>..HEAD per request with no cache or worker dedupe, and packages/host-service/test/integration/git-history.integration.test.ts covers it returning fresh commits.
One adjacent gap worth a look while in here, though I don't think it's what this report hit: GitWatcher.attachWatcher watches git rev-parse --git-dir (git-watcher.ts:625-654), which for a linked worktree is <repo>/.git/worktrees/<name> — the common dir holding refs/heads/* and packed-refs is not watched. A commit in the workspace's own worktree still writes index/COMMIT_EDITMSG under the watched dir so the event fires, but ref movement originating elsewhere in the repo (a fetch moving the base ref, a commit in a sibling worktree, git update-ref) is invisible to the watcher. packages/host-service/src/trpc/router/git/utils/base-ref-freshness.ts:37 already reaches for --git-common-dir for this reason.
Reproduction
Open a v2 workspace whose project has an open PR already (or a session workspace with no project) so ShipControl's incidental 10s poll isn't mounted.
Open the sidebar Changes tab and open the scope dropdown — note the commits listed.
In a terminal inside that worktree (or via an agent), run git commit --allow-empty -m "probe". Keep the app window focused the whole time.
The changed-files list and the top-bar diff stats update within ~1s; reopen the scope dropdown — the new commit is absent.
Cmd-Tab away and back: the list now shows it. That focus-only recovery is the tell.
Unit-level sketch (bun:test) against useGitStatus: mock workspaceTrpc.useUtils() and useWorkspaceEvent so the registered handler is capturable, render the hook, invoke the handler with {} (broad change) and with { paths: ["a.ts"] }, and assert utils.git.listCommits.invalidate was called with { workspaceId } for the broad case. There's no existing test file for this hook, so it would be a new useGitStatus.test.ts next to it.
Suggested fix direction
Add git.listCommits to the broad-change branch of invalidate in useGitStatus.ts:72-82, alongside the existing getBaseBranch invalidation:
Gating it on the no-paths branch is deliberate and cheap: paths present means the batch was worktree-only, which can't move HEAD, so ordinary file-edit churn won't spawn a git log per keystroke. Note that listCommits's key includes baseBranch, so invalidating with just { workspaceId } covers every base variant (the existing call at useChangesTab.tsx:117 already relies on that). git.getBranchSyncStatus is in the same boat conceptually but is already polled at 10s by usePRFlowState, so it's a smaller win.
Adding a refetchInterval to the sidebar query would also mask the symptom, but the event-driven invalidation is the established pattern in this hook and avoids a permanent git log poll per open workspace.
Activity
github-actions commentedon Sep 11, 2026
Summary
The report is plausible and I can point at a concrete gap in the desktop renderer. New commits made outside the app's own commit button — i.e. by an agent in a terminal, which is exactly what the "have the agent commit / continue with another agent / commit again" flow does — do refresh the changed-files list and status badge, but not the commits list. The server side is fine (
git.listCommitsshells out togit logon every call with no caching), and the host-sidegit:changedevent does fire on an agent commit. The renderer just never invalidates thegit.listCommitsquery when that event arrives, so the cached commit list sits there until something unrelated happens to refetch it. Switching agents is incidental to the cause — it's a reliable way to generate commits from outside the UI while the app window never loses focus.Affected code
apps/desktop/src/renderer/hooks/host-service/useGitStatus/useGitStatus.ts:65-87— the single owner of thegit:changedsubscription for a v2 workspace. Itsinvalidatecallback refreshesgit.getStatus(via the trailing scheduler),git.getDiffPatch,git.getDiff, andgit.getBaseBranch.git.listCommitsis not in the list.apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx:138-141— the sidebar's commits query. Its only automatic refresh isrefetchOnWindowFocus: true; the defaultstaleTimeis 5s (packages/workspace-client/src/providers/WorkspaceClientProvider/WorkspaceClientProvider.tsx:7). There's no interval and no event subscription.utils.git.listCommits.invalidateis called in exactly one place in the renderer — the base-branch mutation atuseChangesTab.tsx:113-119.apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/ChangesControl/components/ShipControl/ShipControl.tsx:96-107— issues the same query key ({ workspaceId, baseBranch }) withrefetchInterval: 10_000, so while that component is mounted its poll incidentally keeps the sidebar list fresh. It's gated oncanCreatePr = projectId != null(ShipControl.tsx:62) and only rendered on the no-PR half of the top-bar control, so the list goes permanently stale in session workspaces (no project) and once a PR exists. That's the likely reason this looks intermittent between workspaces..../RightSidebar/ChangesView/ChangesView.tsx:521,shared/changes-types.ts:46), so it rode along with the status refresh. The v2 sidebar split commits into a separate query and that query was left out of the invalidation set.Suspected root cause
Missing
git.listCommitsinvalidation in thegit:changedhandler atuseGitStatus.ts:65-85. An agent commit produces a broadgit:changed(nopaths, since.git/was touched —packages/host-service/src/events/git-watcher.ts:482-524), the handler runs, status/diffs refresh, and the commits query is left untouched. With the Electron window continuously focused during an agent session,refetchOnWindowFocusnever fires either, so the list stays as it was until the user blurs and refocuses the app, changes the base branch, commits through the UI (ShipControl.tsx:113-122refetches explicitly), or happens to have ShipControl's 10s poll mounted.Ruled out: server-side staleness.
git.listCommits(packages/host-service/src/trpc/router/git/git.ts:313-351) runsgit log <baseRef>..HEADper request with no cache or worker dedupe, andpackages/host-service/test/integration/git-history.integration.test.tscovers it returning fresh commits.One adjacent gap worth a look while in here, though I don't think it's what this report hit:
GitWatcher.attachWatcherwatchesgit rev-parse --git-dir(git-watcher.ts:625-654), which for a linked worktree is<repo>/.git/worktrees/<name>— the common dir holdingrefs/heads/*andpacked-refsis not watched. A commit in the workspace's own worktree still writesindex/COMMIT_EDITMSGunder the watched dir so the event fires, but ref movement originating elsewhere in the repo (a fetch moving the base ref, a commit in a sibling worktree,git update-ref) is invisible to the watcher.packages/host-service/src/trpc/router/git/utils/base-ref-freshness.ts:37already reaches for--git-common-dirfor this reason.Reproduction
git commit --allow-empty -m "probe". Keep the app window focused the whole time.Unit-level sketch (
bun:test) againstuseGitStatus: mockworkspaceTrpc.useUtils()anduseWorkspaceEventso the registered handler is capturable, render the hook, invoke the handler with{}(broad change) and with{ paths: ["a.ts"] }, and assertutils.git.listCommits.invalidatewas called with{ workspaceId }for the broad case. There's no existing test file for this hook, so it would be a newuseGitStatus.test.tsnext to it.Suggested fix direction
Add
git.listCommitsto the broad-change branch ofinvalidateinuseGitStatus.ts:72-82, alongside the existinggetBaseBranchinvalidation:Gating it on the no-
pathsbranch is deliberate and cheap:pathspresent means the batch was worktree-only, which can't move HEAD, so ordinary file-edit churn won't spawn agit logper keystroke. Note thatlistCommits's key includesbaseBranch, so invalidating with just{ workspaceId }covers every base variant (the existing call atuseChangesTab.tsx:117already relies on that).git.getBranchSyncStatusis in the same boat conceptually but is already polled at 10s byusePRFlowState, so it's a smaller win.Adding a
refetchIntervalto the sidebar query would also mask the symptom, but the event-driven invalidation is the established pattern in this hook and avoids a permanentgit logpoll per open workspace.