Skip to content

Attention: a mutating git command is an action, not read-only inspection - #87

Merged
blast-hardcheese merged 4 commits into
wandercom:mainfrom
JingHao-Leon:fix/attention-readonly-git-subcommands
Oct 20, 2026
Merged

blast-hardcheese merged 4 commits into
wandercom:mainfrom
JingHao-Leon:fix/attention-readonly-git-subcommands

Conversation

@JingHao-Leon

@JingHao-Leon JingHao-Leon commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #86.

What broke

kin's attention decides whether a Bash tool call is pure read-only inspection (not worth interrupting the agent for) or a real action (worth interrupting for). For git it matched only the subcommand name:

if cmd == "git":
    return sub in config.attention.readonly_git_subcommands

The default list names branch, config, remote, stash, tag and reflog — subcommands that write in their common form. So git stash, git tag v1.0.0, git branch feature/x, git config user.email a@b.c, git remote add origin <url> and git reflog expire --all were classified as inspection, the attention hook returned early (src/kindex/cli.py:5769), and no reminder was injected for them.

The list's own comment states the opposite contract: "git and kin are read-only only for these subcommands; any other subcommand (push, commit, index, export, …) is an action and fires."

Root cause

  • src/kindex/attention.py:324-325 — the git check never looks at the arguments, so the reading form and the writing form of one subcommand cannot be told apart.
  • src/kindex/config.py:526-530 — the default list contains the six subcommands that have both forms.

The change

A listed git subcommand is now read-only only in its inspecting form:

  • branch / tag list, unless they name something to create, move or delete (git tag -a makes an annotated tag, so it is not a listing flag);
  • config reads a key, but writes when it is given a value or asked to unset one;
  • remote reads with show / get-url and writes with add / remove / rename / set-url / prune / update;
  • stash reads only with list / show — a bare git stash saves the working tree;
  • reflog reads unless it is asked to expire or delete entries.

A follow-up commit closes four forms the first one still got wrong:

  • a verbosity flag is not a listing switch — git branch -v feature/x creates the branch, and -v only makes a listing louder, so it must not turn a creation into inspection;
  • the listing switches (--list, --merged, --contains, --points-at, --remotes) take a pattern or a commit-ish, so their argument is never the name of a branch to create;
  • the option-shaped writes are writes — git branch --set-upstream-to=<upstream>, --unset-upstream and --edit-description change the branch configuration without naming a branch;
  • git config edit, git config -e and git config --edit open the file for editing, while git config get <key> reads a single value — the two were classified the wrong way round.

Every other listed subcommand (status, log, diff, show, blame, …) stays unconditionally read-only, so a subcommand added to attention.readonly_git_subcommands keeps its previous meaning.

How it was verified

Reproduced on the base commit 23490c5, before the change:

$ python - <<'EOF'
from kindex.attention import is_background_action
from kindex.config import Config

cfg = Config()
for cmd in ("git stash", "git tag v1.0.0", "git config user.email a@b.c"):
    payload = {"tool_name": "Bash", "tool_input": {"command": cmd}}
    print(cmd, "->", is_background_action(payload, cfg))
EOF
git stash -> True
git tag v1.0.0 -> True
git config user.email a@b.c -> True

True is "background", i.e. inspection. After the change the same three commands report False, and the reading forms still report True:

git status                                              background=True
git branch                                              background=True
git tag -l                                              background=True
git remote -v                                           background=True
git stash list                                          background=True
git config user.name                                    background=True
git reflog                                              background=True
git stash                                               background=False
git tag v1.0.0                                          background=False
git branch feature/x                                    background=False
git config user.email a@b.c                             background=False
git remote add origin https://example.com/x.git         background=False
git reflog expire --all                                 background=False

Regression tests: tests/test_attention.py::test_is_background_action_fires_on_mutating_git_subcommands and tests/test_attention.py::test_is_background_action_separates_git_listing_from_creation. The second one was added after an external review flagged four forms this change still got wrong; it fails on the previous commit of this branch and passes on the current one.

before the fix:  1 failed, 1 passed, 32 deselected
after the fix:   34 passed

Full suite, python -m pytest -q -n 4 --dist loadfile --timeout 300:

before:  184 failed, 3692 passed, 17 skipped, 17 errors
after:   184 failed, 3694 passed, 17 skipped, 17 errors

The 199 failing test IDs are the same set before and after (0 added, 0 removed) — the pre-existing failures are unrelated to this change and unchanged by it. The two extra passes are the new regression tests. The local runs used Python 3.13.9; the repository's CI workflow runs the suite on Python 3.12.

CI status

gh pr checks 87 currently reports no checks. The repository's own CI workflow is behind the fork approval gate, and the external review bot has not posted a check for the current head yet.

The CI workflow does have a run on this PR's current head — run 39362917061, status=completed, conclusion=action_required, 0 jobs. That is the fork approval gate: the workflow is waiting for a maintainer to approve running it, so the test suite has not run on the upstream runner for this PR. It is not a test failure — the same CI workflow on the base commit 23490c5 is green (run 38674146331, conclusion=success).

An earlier head of this branch (050a755) carried one further check, Adapt Review — an external review bot (app.adapt.com), not this repository's CI. It completed with the conclusion fail. It is not a test run of this pull request, but its report is reproducible: all four findings it raised reproduce on this branch, and the follow-up commit fixes all four. They were:

  • git branch -v feature/x creates a branch, but -v was treated as a listing flag, so the creation counted as inspection;
  • git branch --set-upstream-to=<upstream>, --unset-upstream and --edit-description write the branch configuration without naming a branch, so they counted as inspection;
  • git config edit, git config -e and git config --edit open the file for editing, while git config get <key> reads a single value — the two were classified the wrong way round;
  • git branch --merged, --contains and --points-at list filtered branches, but their commit-ish argument was read as the name of a branch to create, so they fired as actions.

This change was prepared with the assistance of an AI coding agent.

The read-only list named git subcommands, so the writing form of six of
them counted as pure inspection and attention dropped every reminder for
git stash, git tag v1, git branch feature, git config key value, git
remote add and git reflog expire. Those subcommands are read-only only
in their inspecting form.

@adaptcom adaptcom Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Confidence Score: 2/5

Summary

Score reconciled from 3/5 because the review includes a blocking finding.

Makes attention inspect arguments for branch/config/remote/stash/tag/reflog so their writing forms fire. Net safer, but -v/--verbose wrongly count as list flags, so git branch -v name (creates a branch) stays silent.

Important Files Changed

File Overview
src/kindex/attention.py Argument-aware read-only check for six form-dependent git subcommands
src/kindex/config.py Comment documenting form-dependent semantics of listed git subcommands
tests/test_attention.py Regression test for reading vs writing forms; misses branch flag edge cases
CHANGELOG.md Fixed entry under Unreleased

Reviewed 23490c5...050a755 · ↻ Re-run review · View in Adapt

Comment thread src/kindex/attention.py Outdated
Comment thread src/kindex/attention.py
Comment thread src/kindex/attention.py
Comment thread src/kindex/attention.py Outdated
Comment thread tests/test_attention.py
The read-only check for the form-dependent git subcommands still mis-read
four forms:

* `git branch -v feature/x` creates the branch — `-v` only makes a
  listing louder, it does not say that the command lists.
* `git branch --set-upstream-to=<upstream>`, `--unset-upstream` and
  `--edit-description` write the branch configuration without naming a
  branch, so "no positional argument" did not mean "nothing was written".
* `git config edit`, `git config -e` and `git config --edit` open the
  file for editing, while `git config get <key>` reads a single value.
* `git branch --merged` / `--contains` / `--points-at` list filtered
  branches and their argument is a commit-ish, not a name to create.

Adds a regression test for each form.
@blast-hardcheese

Copy link
Copy Markdown
Collaborator

Thank you for your contribution! After additional reviews there were a few more of these edge cases, I'm pushing up a fix now!

@blast-hardcheese
blast-hardcheese merged commit d82d2b1 into wandercom:main Oct 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Attention treats mutating git commands as read-only inspection, so reminders never fire for them

2 participants