Skip to content

Generate Commit Message reads the wrong repository in multi-root workspaces ("No staged changes" with files staged) #11447

Description

@petrk643-arch

Before opening, please confirm:

Operating System

Windows 11

Kiro Version

1.1.14

Bug Description

Investigated and written by Kiro, reading its own shipped bundles
(kiro.kiro-agent/dist/extension.js, extensions/git/dist/main.js) and its own
extension-host logs. Findings come from static reading of that minified code plus
the vscode.git log, so identifier names are de-minified by hand and the
scm/inputBox argument claim is inferred from behaviour rather than observed
directly — see the note in "The identity match never succeeds" below. Everything
else was verified by running it.

Environment

Kiro 1.1.14 (commit f694ef1b025756b1ae27ae7c3d9ed4215b0160fe, 2026-09-10)
kiro-agent 1.1.28
VS Code base 1.131.0
OS Windows 11
Workspace multi-root, 11 folders, 14 git repositories open

Summary

kiroAgent.generateCommitMessage reports

No staged changes for commit message

for a repository that has staged files. It is reading a different repository
than the one whose commit box was clicked, and it does so silently.

The cause is findRepositoryUri in kiro.kiro-agent/dist/extension.js:

repos = await command("git.api.getRepositories")   // string[] of rootUri
if (arg?.rootUri) {
  const hit = repos.find(s => s === arg.rootUri.toString())
  if (hit) return hit
}
return repos[0]                                     // <- silent fallback

Two independent problems combine:

1. The identity match never succeeds. The command is contributed to
scm/inputBox. The argument delivered there apparently does not carry a rootUri
that stringifies to any value returned by git.api.getRepositories, so find
misses and the fallback decides.

This part is inferred, not observed: the repository whose commit box was clicked
was open, listed, and the only one visible in the Source Control view, yet a
different one was used — which only happens if the find missed. Whether the
argument is undefined, a marshalled plain object whose toString() yields
[object Object], or a Uri that stringifies differently, we could not tell from
outside. You can, and it should be checked before fixing only the fallback.

Either way the fallback hides it completely: no log line, no message, no
indication that the intended repository was not found.

2. repos[0] is not a meaningful default. git.api.getRepositories returns
Model.repositories, backed by Model.openRepositories. The git extension's own
lookup sorts that array in place:

// extensions/git/src/model.ts - Model.getOpenRepository
for (const liveRepository of this.openRepositories.sort(
    (a, b) => b.repository.root.length - a.repository.root.length)) { ... }

getOpenRepository runs on every visible-editor change and every quick-diff
lookup, so within seconds of startup openRepositories is permanently ordered
longest-root-path-first. repos[0] is therefore whichever repository is nested
deepest in the workspace — close to the least likely one the user means.

Effective order in the affected workspace, longest root path first:

[ 0]  <workspace>/app-control-panel/app-control-panel   <- chosen, index empty
[ 1]  ~/.kiro/powers/repos/<power>.partial-<id>-1       <- Kiro Powers temp clones
 ...                                                       (six of these)
[ 6]  ~/.kiro/powers/repos/<power>.partial-<id>-3
[ 7]  <workspace>/main-repo/docs-submodule
[ 8]  <workspace>/service-b
[ 9]  <workspace>/main-repo/nested-docs
[10]  <workspace>/wiki-a
[11]  <workspace>/wiki-b
[12]  <workspace>/tooling-c
[13]  <workspace>/main-repo                             <- staged files, sorts LAST

The workspace-folder repository being committed to sorted last of fourteen,
because it is the shallowest path present - which is typical for a workspace-folder
root. The repository actually chosen had an empty index, so the staged-changes
check short-circuited.

The in-place sort is upstream VS Code behaviour and arguably fine there. It is only
harmful because a consumer treats repositories[0] as if it carried meaning.

Steps to reproduce

  1. Open a multi-root workspace with at least two git repositories, where one
    repository is nested more deeply than the workspace-folder repository - e.g.
    folder A/ is a repo, and B/nested/deep/ is also a repo.
  2. Open a file from A/ so the SCM view targets it.
  3. Stage a change in A/. Leave B/nested/deep/ with a clean index.
  4. Click the wand in A's commit message box.

Expected: a commit message generated from A's staged diff.
Actual: "No staged changes for commit message".

Reproduces reliably. Which repository wins depends only on path lengths, so the
symptom appears and disappears as repositories are added or removed - including
temporary clones that Kiro Powers leaves under ~/.kiro/powers/repos/, some of
which no longer exist on disk yet remain registered.

Confirming it from logs

git.api.getDiff is called only after the staged-changes check passes, and maps
to git diff --cached:

Select-String -SimpleMatch -Pattern "diff --cached" `
  -Path "$env:APPDATA\Kiro\logs\<session>\window1\exthost\vscode.git\Git.log"

Zero occurrences across 43,645 lines and 25 hours of use before the change below;
present immediately after.

Not the cause

Worth stating, because each of these misdirects diagnosis:

  • Relocated git directories are irrelevant. These repositories use
    --separate-git-dir to keep git internals off a synced drive.
    git rev-parse --git-dir and core.worktree resolve correctly, the git
    extension opens them without error, and it ran git status in the same second
    the index was written.
  • The git extension's state is correct. The Source Control view listed the
    staged files throughout. Only the commit message generator disagreed.
  • Not git.statusLimit. git status -uall yields ~240 entries here, far under
    the 10,000 default.

Suggested fix

Give the fallback the same heuristic Kiro already uses elsewhere in the same bundle
for remote-URL lookup, preferring in order:

  1. the repository whose root is the longest prefix of the active editor's path;
  2. the first workspace folder that is itself a repository root;
  3. repos[0], if nothing else matches.

Separately, and more valuable than the fallback itself: log when the rootUri
match fails
, or name the chosen repository in the progress notification. We got
an error only because the wrongly-chosen repository happened to have an empty
index. Had it held staged changes, the command would have produced a confident,
well-formed commit message describing a completely different repository, with
nothing to indicate it. That is the real severity here.

Fixing the scm/inputBox argument so the identity match works would make the
fallback almost unreachable. Both are worth doing.

Local patch

Applied to kiro.kiro-agent/dist/extension.js, inserted before the existing
return repos[0] (kept as the last resort), wrapped in try/catch so an
unexpected API shape degrades to current behaviour:

/*kiro-scm-repo-fix*/try{
  let u = vscode.window.activeTextEditor?.document?.uri;
  if (u && u.scheme === "file") {
    let s = u.toString(),
        c = repos.filter(x => s === x || s.startsWith(x.endsWith("/") ? x : x + "/"));
    if (c.length) return c.sort((a, b) => b.length - a.length)[0];
  }
  let w = vscode.workspace.workspaceFolders;
  if (w) for (let f of w) {
    let m = repos.find(x => x === f.uri.toString());
    if (m) return m;
  }
}catch{}
return repos[0]

Verified against the full 14-repository set: an editor in the workspace-folder
repository selects that repository; an editor inside a nested submodule selects the
submodule; no active editor still selects the workspace-folder repository rather
than the deepest one. node --check passes on the patched bundle.

Every update ships a pristine bundle and removes this, so it has to be re-applied
by a SessionStart hook - which is why the fix belongs upstream.

Workaround for anyone hitting this

Open a file belonging to the repository you want before clicking the wand. That
does not help on current builds, since the active editor is not consulted - so in
practice: write the message by hand, or ask the agent to read git diff --cached
and write one. Closing unwanted repositories in the Source Control view does
not help: the deepest remaining repository still wins, and a workspace-folder
root is usually the shallowest path present.

Steps to Reproduce

  1. Open a multi-root workspace with at least two git repositories, where one
    repository is nested more deeply than the workspace-folder repository - e.g.
    folder A/ is a repo, and B/nested/deep/ is also a repo.
  2. Open a file from A/ so the SCM view targets it.
  3. Stage a change in A/. Leave B/nested/deep/ with a clean index.
  4. Click the wand in A's commit message box.

Expected: a commit message generated from A's staged diff.
Actual: "No staged changes for commit message".

Reproduces reliably. Which repository wins depends only on path lengths, so the
symptom appears and disappears as repositories are added or removed - including
temporary clones that Kiro Powers leaves under ~/.kiro/powers/repos/, some of
which no longer exist on disk yet remain registered.

Expected Behavior

Expected: a commit message generated from A's staged diff.
Actual: "No staged changes for commit message".

Conversation ID

No response

Additional Context

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions