Skip to content

fix(resolve): detect a type's agent process from its manifest, not a fixed list - #631

Open
kappa4 wants to merge 1 commit into
fujibee:mainfrom
kappa4:fix/626-detect-proc-from-manifest
Open

fix(resolve): detect a type's agent process from its manifest, not a fixed list#631
kappa4 wants to merge 1 commit into
fujibee:mainfrom
kappa4:fix/626-detect-proc-from-manifest

Conversation

@kappa4

@kappa4 kappa4 commented Aug 5, 2026

Copy link
Copy Markdown

Fixes #626.

_agmsg_agent_binaries (scripts/lib/resolve-project.sh) maps an agent type to the process names that identify it using a hardcoded case. Its last arm answers claude codex gemini, and that arm is not the exception it reads as — every type added since the list was written and never given an arm lands there:

type type.conf detect_proc _agmsg_agent_binaries (before)
cursor cursor-agent cursor-agent-* claude codex gemini
grok-build grok grok-* claude codex gemini
hermes (none) claude codex gemini

The manifests' detect_proc was never consulted at all, which also made this a live counterexample to the "types are discovered from manifests instead of hardcoded whitelists" contract in type-registry.sh's header.

Consequence

agmsg_pid_is_agent <pid> cursor accepted a Claude Code process, because claude is in the fallback list. agmsg_agent_pid, walking up from a slash command, therefore found the caller's claude process and called it the cursor member's own agent process; agmsg_resolve_project step 1 then read that session's project marker. A cursor member's project resolved to the project of whoever was asking:

resolve("<member worktree>", cursor)
  before -> .../leader-project     (the caller's project)
  after  -> .../member-worktree    (the member's own)

Steps 2 and 3 would both have got it right — the ancestor walk returns the member's own registered path — but step 1 preempts them.

The same misresolution reaches actas lock ownership and instance-id keying (#93) for these types, which is why this is fixed at the mapping rather than at each call site.

What changes

_agmsg_agent_binaries reads detect_proc from the type registry, dropping its glob tokens since the matcher already tries "<bin>-*" for each entry. The case is kept as the fallback for a manifest with no such key (antigravity, copilot), so those types are unaffected.

The lookup is memoized per type: it runs inside agmsg_agent_pid's ppid walk of up to 20 hops, and a manifest read is a filesystem scan per hop.

Tests

Three regression tests in tests/test_resolve_project.bats:

  • agent-binaries: process names come from the type manifest, not a hardcoded listcursorcursor-agent, grok-buildgrok, codex unchanged
  • pid-is-agent: a claude process is not accepted as an agent of another type — and is still accepted as its own
  • resolve: a member's project is not rewritten to the caller's by a cross-type marker — the end-to-end shape

The last two are skipped on Windows (they fake argv[0] via exec -a, per #349). The manifest-less fallback arm (antigravity, copilot) is preserved but not pinned by a new test.

tests/test_resolve_project.bats passes on this branch; the full suite is left to CI.

Relationship to #625

Filed alongside the despawn/reset defect cluster in #625, whose third cause is where I hit this. This change stands on its own — it touches no file that cluster does, and needs nothing from it.

…fixed list

_agmsg_agent_binaries mapped a type to the process names that identify it with
a hardcoded case whose last arm answered "claude codex gemini". That arm was
not the exception it reads as: every type added since the list was written and
never given an arm -- cursor, grok-build, hermes -- landed there, and their
type.conf detect_proc (cursor-agent, grok) was never consulted at all.

The consequence is a cross-type identity mix-up. agmsg_pid_is_agent accepted an
enclosing Claude Code process as a cursor agent, so agmsg_agent_pid walking up
from a slash command found the CALLER's claude process and called it the cursor
member's own. agmsg_resolve_project step 1 then read that session's project
marker, and a cursor member's project resolved to the project of whoever was
asking:

  resolve("<member worktree>", cursor)
    before -> .../leader-project     (the caller's project)
    after  -> .../member-worktree    (the member's own)

Read detect_proc from the type registry, dropping its glob tokens since the
matcher already tries "<bin>-*" for each entry, and keep the case as the
fallback for a manifest without the key (antigravity, copilot). Memoized per
type: this runs inside a ppid walk of up to 20 hops, and a manifest read is a
filesystem scan.

The same misresolution reaches actas lock ownership and instance-id keying for
those types, which is the wider reason to fix it at the mapping rather than at
each caller.

Closes fujibee#626.
fujibee added a commit that referenced this pull request Aug 7, 2026
…t/--type/--team

doctor previously required <project> <type> up front, which was backwards: a
reporter who does not already know which project/type to look at cannot use
a doctor that demands one. Positional <project> <type> is dropped, not kept
for compatibility -- doctor has few enough callers that carrying a stale
positional form alongside flags that mean something different by default
would be its own source of confusion.

New interface:
  doctor                    default = whole install (every team, project, type)
  doctor --project <path>   narrow to one project
  doctor --type <type>      narrow to one type
  doctor --team <team>      narrow to one team
  (all three combine; --redacted and --help unchanged)

Argument parsing stays separate from scope-building, so a future flag change
is a parsing-only change.

Also fixes three defects found by running the rebuilt scope against the real
installation:

- A type whose delivery_modes is nothing but "off" (agmsg-app, hermes -- the
  desktop app owns its own send/receive UI) was queried against delivery.sh
  anyway, which exits 1 by design (nothing to report); doctor turned that
  into a warning, breaking the exit-code contract on an otherwise healthy
  install. Now checked via the manifest before querying, the same
  agmsg_type_get/delivery_modes key PR #631 already reads.
- A (project, type) group with nothing held, no warning, and nothing beyond
  a bare idle mode line now collapses to one line instead of a 6-line block
  -- unreadable at real scale (dozens of such groups on a real install).
- The "watch processes: N alive, M stale pidfiles" line default runtime
  status emits is an installation-wide fact (it scans the whole run/
  directory), not a per-(project, type) fact; it and the warning it can
  produce now surface once, not once per group using default runtime status.

Read-only behavior, the 0/1/2 exit-code contract, and --redacted's masking
behavior are unchanged. SKILL.md and the per-type templates are not wired
to this yet -- deliberately deferred to a separate PR once this scope shape
is settled, so the 9 templates + the Windows dispatcher aren't rewritten
twice.

Addresses part of #267.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

_agmsg_agent_binaries ignores type.conf detect_proc, so cursor/grok-build/hermes match against claude codex gemini

1 participant