fix(cursor): detect Cursor by its project state dir, not just .cursor/ - #2112
fix(cursor): detect Cursor by its project state dir, not just .cursor/#2112Soph wants to merge 2 commits into
Conversation
Every agent's DetectPresence asks "does <repo>/.<agent>/ exist". That is a
fair test for Claude Code, Codex, and Gemini, which all create repo-local
config as a matter of course. It is structurally unfair to Cursor: project
rules are optional, and Cursor's real per-project state lives in
~/.cursor/projects/<sanitized-repo-path>.
A repo driven daily from Cursor therefore reported "not present", and
`entire enable` enables only detected agents when non-interactive and
pre-selects only detected ones on first run (selectAgents, setup.go). The
observed result is not merely that Cursor is skipped — enable falls back to
the *default* agent, wires Claude Code instead, and prints "Ready.":
repo with ~/.cursor/projects/<repo> present, no repo-local .cursor/
before: "Agent: Claude Code (use --agent to change)" -> .claude wired
after: "Detected agent: Cursor" -> 7 Cursor hooks
This is the missing counterpart to #1262. That issue fixed the case where a
Cursor session in a Claude-Code-only repo was *claimed* by Claude's
forwarded hooks and misattributed; hook_guard.go now correctly declines it.
But nothing was added to ensure the right agent is wired to claim it, so a
user in that repo state went from misattributed sessions to no sessions at
all — correct attribution of nothing. The guard stops the wrong agent from
claiming; this makes the right one get installed.
DetectPresence keeps the repo-local check first and adds the state dir as a
second signal. GetSessionDir and DetectPresence both resolve it through the
new projectStateDir helper so the two cannot disagree about where Cursor
keeps a repo's state. The second signal deliberately tolerates a false
positive (a repo opened in Cursor once, never used with its agent): that
installs hooks which never fire, whereas a false negative captures nothing
and says nothing.
Tests set HOME/USERPROFILE to a temp dir so they never read the developer's
real ~/.cursor. initGitRepo now runs a real `git init`: the hand-written
.git/HEAD it created before is not a repo the git CLI accepts, so
paths.WorktreeRoot failed with exit 128 on it. Existing tests never noticed
because DetectPresence falls back to CWD on that error. This package cannot
use testutil.InitRepo — architecture_test.go forbids agent packages from
importing cli internals.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01M0T3348JH1NZVB61N736W5E6
There was a problem hiding this comment.
Pull request overview
This PR improves Cursor agent detection so entire enable can correctly auto-select Cursor in repos that don’t have a repo-local .cursor/ directory, by also treating Cursor’s home-directory per-project state (~/.cursor/projects/<sanitized-repo-path>) as a presence signal.
Changes:
- Extend
CursorAgent.DetectPresenceto check Cursor’s per-project state dir as a second signal after repo-local.cursor/. - Refactor Cursor path resolution via a shared
projectStateDirhelper soDetectPresenceandGetSessionDircan’t diverge. - Add regression tests covering the new Cursor presence signal and update git repo test setup to use a real
git init.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| cmd/entire/cli/agent/cursor/cursor.go | Adds home-dir Cursor project-state detection and shares path resolution via projectStateDir. |
| cmd/entire/cli/agent/cursor/cursor_test.go | Adds tests for the new detection signal and switches test repo setup to run git init. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| cmd.Env = append(os.Environ(), | ||
| "GIT_CONFIG_GLOBAL=/dev/null", | ||
| "GIT_CONFIG_SYSTEM=/dev/null", | ||
| ) |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 42d4a7b. Configure here.
| cmd.Env = append(os.Environ(), | ||
| "GIT_CONFIG_GLOBAL=/dev/null", | ||
| "GIT_CONFIG_SYSTEM=/dev/null", | ||
| ) |
There was a problem hiding this comment.
Windows-broken git config isolation
Medium Severity
initGitRepo sets GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM to /dev/null. That path is not a usable git config on Windows (NUL also fails), so git init can fatal and these new DetectPresence tests fail on a full Windows run. The repo already documents this pitfall and isolates via a real empty config file instead.
Reviewed by Cursor Bugbot for commit 42d4a7b. Configure here.
Review feedback on #2112. initGitRepo pointed GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM at /dev/null, which does not exist on Windows. Point both at paths inside a temp dir instead: git treats a missing config file as empty, so a real filesystem path isolates just as well and is portable. This matches the GIT_CONFIG_GLOBAL convention in gitrepo/reftable_test.go. Scope note, since both reviewers called this a Windows CI failure: it is not one. ci.yml's test-windows job runs only `go test ./cmd/entire/cli/... -run '(Windows|MSYS)'`, selecting tests by top-level name so POSIX-only harnesses are skipped there by design. No TestDetectPresence_* name matches, so these never ran on Windows CI. Taken anyway because the portable form costs nothing and reads better; also worth noting reftable_test.go still passes GIT_CONFIG_SYSTEM=/dev/null, so the existing convention is only half-portable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M0WJQ4SDTKP1W96Y67MTVK8R


https://entire.io/gh/entireio/cli/trails/1132
Every agent's
DetectPresenceasks "does<repo>/.<agent>/exist". That's a fair test for Claude Code, Codex, and Gemini, which all create repo-local config as a matter of course. It's structurally unfair to Cursor: project rules are optional, and Cursor's real per-project state lives in~/.cursor/projects/<sanitized-repo-path>.So a repo driven daily from Cursor reports "not present".
entire enableenables only detected agents when non-interactive, and pre-selects only detected ones on first run (selectAgents,setup.go:1771-1795). The observed result is worse than Cursor merely being skipped — enable falls back to the default agent, wires Claude Code, and prints "Ready.":Agent: Claude Code (use --agent to change)Detected agent: Cursor.cursor/hooks.json.claude/settings.jsonA/B'd with real binaries in a fresh repo with
~/.cursor/projects/<repo>/present and no repo-local.cursor/.Why this is load-bearing: the missing counterpart to #1262
#1262 reported this exact repo state — Claude Code hooks installed,
.cursor/hooks.jsonabsent, Cursor in use — and its consequence: the Cursor session was claimed by Claude's forwarded hooks and misattributed.fadc4a06afixed that withhook_guard.go, which inspects the transcript path and makes the forwarded hook no-op when the session belongs to another agent.That fix is correct, and it changes the failure mode for anyone in that state:
#1262 shipped one half. The guard stops the wrong agent from claiming a Cursor session; nothing ensured the right agent is wired to claim it. Users in that state went from misattributed sessions to correct attribution of nothing at all. This is that second half.
The user-visible symptom has been reported from outside more than once — #574 ("Unable to see agent steps on dashboard") and #898 ("empty Cursor session panel").
What changed
DetectPresencekeeps the repo-local check first and adds the state dir as a second signal.GetSessionDirandDetectPresenceboth resolve it through a newprojectStateDirhelper, so the two cannot disagree about where Cursor keeps a repo's state.The second signal deliberately tolerates a false positive — a repo opened in Cursor once and never used with its agent. That installs hooks which never fire, which is harmless; a false negative captures nothing and says nothing.
Verification
Cursor capture itself is healthy once hooks exist —
TestSingleSessionManualCommitwithcursor-clipasses on Cursor CLI2026.08.11, full lifecycle (SessionStart→TurnStart→TurnEnd→ checkpoint saved → condensed), commit traileredEntire-Checkpoint, v1 checkpoint carryingEntire-Agent: Cursor. Detection was the only thing standing between a Cursor user and that path.Four tests, two RED-verified (they fail with the second signal stubbed, pass with it). Tests set
HOME/USERPROFILEto a temp dir so they never read the developer's real~/.cursor.initGitReponow runs a realgit init: the hand-written.git/HEADit created before is not a repo the git CLI accepts, sopaths.WorktreeRootfailed with exit 128 on it — existing tests never noticed becauseDetectPresencefalls back to CWD on that error. This package can't usetestutil.InitRepo;architecture_test.goforbids agent packages from importingcliinternals.mise run checkgreen.Not addressed here
Found while investigating, each worth its own issue: headless (
cursor-agent -p) captures nothing at all because onlysessionStart/sessionEndfire so no session state is created — though the transcript is written to disk, so it's fixable fromsessionEnd; Cursor has noContextInjector, andsessionStart'sadditional_contextis verified working, so that's now viable; and nothing anywhere detects "hooks installed, never fired", which is the state every one of these leaves you in.🤖 Generated with Claude Code
Note
Medium Risk
Changes which agent
entire enableauto-selects and installs hooks for. False positives can wire unused Cursor hooks; false negatives previously caused silent missed capture.Overview
Cursor is now treated as present when
~/.cursor/projects/<sanitized-repo-path>exists, even if the repo has no.cursor/directory. That was the common case (project rules are optional), soentire enableskipped Cursor, installed the default agent instead, and captured nothing.DetectPresencestill checks repo-local.cursor/first, then the home-dir project state dir.GetSessionDirand detection share a newprojectStateDirhelper so they cannot disagree on the path. An empty project dir still counts as present (hooks that never fire beat silent data loss).Tests isolate
HOME/USERPROFILEand use a realgit initsoWorktreeRootresolves the same path Cursor would sanitize.Reviewed by Cursor Bugbot for commit 42d4a7b. Configure here.