Skip to content

fix(cli): derive projectId from prefixed issue id on spawn - #1330

Merged
harsh-batheja merged 4 commits into
mainfrom
feat/issue-1329
Apr 22, 2026
Merged

harsh-batheja merged 4 commits into
mainfrom
feat/issue-1329

Conversation

@harsh-batheja

@harsh-batheja harsh-batheja commented Apr 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes cross-project spawn tagging the session with the wrong projectId when ao start is running for a different project than the issue belongs to.

  • New core utility resolveSpawnTarget(projects, issueRef, fallback?) — parses <prefix>/<rest> and routes to the matched project. Accepts either a project id (yaml key) or sessionPrefix as the prefix; project id wins on collision. Returns null when nothing matches and no fallback is supplied.
  • ao spawn <projectId>/<issue> — now routes to the prefixed project and strips the prefix from the issue id. Previously projectId fell back to whichever project ao start was running for, giving the session the wrong prefix and dashboard column.
  • ao spawn <sessionPrefix>/<issue> — also works (e.g. xid/1 routes to the project whose sessionPrefix is xid).
  • ao batch-spawn — groups issues by resolved project and runs preflight once per group, instead of erroring on mixed prefixes.
  • Web /api/spawn — unchanged; it already requires an explicit projectId, so it's immune. Any future caller that wants free-form issue refs can opt in by calling resolveSpawnTarget before sessionManager.spawn().
  • SessionManager.spawn() — unchanged on purpose. Explicit { projectId, issueId } stays authoritative; the utility is opt-in for callers that want routing.

Fixes #1329

Design notes

The first commit was a CLI-only fix. The second commit lifts the routing into a core utility so any spawn entry point (CLI, web API, programmatic) can share the same behavior. The CLI now imports resolveSpawnTarget instead of duplicating logic.

Prefix disambiguation: project-id match wins over sessionPrefix match because yaml keys are unique by construction while sessionPrefix values are not guaranteed to be.

Test plan

  • pnpm --filter @aoagents/ao-core test — 800 tests pass, including 8 new unit tests for resolveSpawnTarget covering:
    • project-id prefix match
    • sessionPrefix prefix match
    • project-id wins on collision
    • fallback used when prefix doesn't match
    • returns null when nothing matches and no fallback
    • bare issue id passed through
    • leading/trailing slash inputs handled
    • undefined issueRef handled
  • pnpm --filter @aoagents/ao-cli test — 485 tests pass, including 3 new spawn.test.ts cases:
    • routes x402-identity/1 → projectId: x402-identity, issueId: 1
    • routes xid/7 via sessionPrefix → projectId: x402-identity, issueId: 7
    • leaves some-org/42 untouched when no project or prefix matches
  • pnpm --filter @aoagents/ao-core typecheck and pnpm --filter @aoagents/ao-cli typecheck pass
  • pnpm lint — 0 errors (33 pre-existing warnings unchanged)
  • Manual repro: multi-project config, ao start agent-orchestrator, ao spawn x402-identity/1 → session prefix xid-*, projectId = x402-identity, appears under x402-identity on the dashboard
  • Manual repro: ao batch-spawn agent-orchestrator/1 x402-identity/2 → one session per project, each routed correctly

When `ao spawn <projectId>/<issue>` is used in a multi-project config,
route the spawn to the prefixed project and strip the prefix from the
issue id. Previously the projectId fell back to whichever project
`ao start` was running for, tagging cross-project sessions with the
wrong project (and session prefix).

Applies to both `ao spawn` and `ao batch-spawn`; batch-spawn errors
out if issues mix multiple project prefixes.

Fixes #1329
@github-actions

github-actions Bot commented Apr 19, 2026 •

Copy link
Copy Markdown
Contributor

Test Coverage Report

Metric Value
Lines covered 263/342
Lines not covered 79/342
Overall coverage 76.9%
Per-file breakdown
File Coverage
packages/cli/src/commands/spawn.ts 252/331 (76.1%)
packages/core/src/spawn-target.ts 11/11 (100.0%)

Uncovered lines

  • packages/cli/src/commands/spawn.ts: L28-L29, L37-L38, L47-L48, L50-L51, L68-L74, L125-L126, L163-L169, L173-L176, L178-L179, L243-L245, L252-L254, L299-L305, L312-L314, L316-L322, L348-L350, L364-L367, L384-L387, L389-L390, L392-L401, L415-L417

Move the issue-prefix → project routing from the CLI into a reusable
core utility, `resolveSpawnTarget(projects, issueRef, fallback?)`.

- Exposes routing to any spawn entry point (CLI, web API, programmatic).
- Accepts either a project id or sessionPrefix as the prefix; project id
  wins on collision.
- `ao batch-spawn` now groups issues by resolved project and preflights
  once per group instead of erroring on mixed prefixes.

Tests:
- 8 unit tests for `resolveSpawnTarget` in core.
- CLI spawn.test.ts: adds a sessionPrefix routing test (23 tests total).

Refs #1329
Self-review found three gaps:

- `resolveSpawnTarget` accepted `undefined` issueRef and returned
  `{ issueId: "" }` with a fallback project. Dead path — both callers
  guard against undefined. Drop it and make issueRef required.
- No tests for `batch-spawn`. Added two:
  - routes cross-project prefixed issues to the correct project and
    lists each project's sessions separately
  - skips a prefixed issue when the target project already has an
    active session for it
- `spawn` and `batch-spawn` help text didn't document the
  `<projectId>/<issue>` or `<sessionPrefix>/<issue>` forms.
@harsh-batheja

Copy link
Copy Markdown
Contributor Author

Self-review

Reviewed the diff for bugs. Found three minor issues, all fixed in 9c636f1.

Issues found and fixed

1. Dead undefined branch in resolveSpawnTarget
The signature accepted issueRef: string | undefined and returned { issueId: "" } when given undefined with a fallback. No caller reaches that path — both the single-spawn and batch-spawn call sites guard against undefined. Tightened the signature to issueRef: string and removed the empty-string return.

2. No tests for batch-spawn
The per-group logic (grouping by resolved project, per-group preflight, per-group existing-issue map, per-group dupe set) had zero direct coverage. A regression would have slipped past CI. Added two tests:

  • Routes cross-project prefixed issues to the correct project; sm.list is called per project.
  • Skips a prefixed issue when the target project already has an active session for it.

3. Help text didn't document the new forms
ao spawn --help and ao batch-spawn --help still said "project is auto-detected." Updated both to mention <projectId>/<issue> and <sessionPrefix>/<issue>.

Non-issues considered and left alone

  • Multi-slash refs (a/b/c) route on first segment only. Intentional — session-manager's branch-name sanitizer already handles the slashy remainder.
  • Case-sensitive project-id match. Consistent with how yaml keys are compared everywhere else.
  • Pre-fix sessions with issueId: "x402-identity/1" under the wrong project won't collide with new correctly-routed sessions. Acceptable — anyone hitting this bug wants a freshly-tagged session anyway.
  • sessionPrefix collisions across projects — first match wins (insertion order). Documented in the function comment.

Final state

  • 27 CLI spawn tests + 7 core spawn-target tests, all passing.
  • pnpm typecheck clean on both packages.
  • pnpm lint — 0 errors (33 pre-existing warnings unchanged).

Second review pass found a latent bug:

Plain JS objects inherit `__proto__`, `constructor`, `toString`,
`hasOwnProperty`, etc. from Object.prototype. The previous
`if (projects[prefix])` check entered the routing branch for any of
these keys, mis-routing a user typing `ao spawn __proto__/42` with
`projectId: "__proto__"`. Downstream session-manager would then receive
a junk project object (Object.prototype itself) and fail with a
non-obvious error.

Fix: use `Object.prototype.hasOwnProperty.call(projects, prefix)` so
only actual configured project ids match.

Also:
- Document the case-sensitive matching semantic explicitly.
- Lock in the batch-spawn grouping contract by asserting exact
  `list()` and `spawn()` call counts in the cross-project test.
@harsh-batheja

Copy link
Copy Markdown
Contributor Author

Second self-review

Ran another pass (plus an independent agent review). Found one real correctness bug and two minor tightening opportunities. All addressed in b64a672.

Bug found and fixed

Prototype-key match in resolveSpawnTarget — the previous if (projects[prefix]) check traversed Object.prototype, so inherited keys (__proto__, constructor, toString, hasOwnProperty, …) returned truthy and entered the routing branch. A user typing ao spawn __proto__/42 would mis-route with projectId: "__proto__", and SessionManager.spawn() would then receive Object.prototype as the "project config" — leading to a non-obvious downstream failure (missing path, sessionPrefix, etc.).

Fix: Object.prototype.hasOwnProperty.call(projects, prefix). Added tests covering __proto__/42, constructor/42, toString/42.

Minor tightening

  • Docstring now calls out case-sensitivity explicitly — matching is literal, e.g. XID/7 does not match a project with sessionPrefix: "xid".
  • Batch-spawn grouping test now asserts exact call counts: list().toHaveBeenCalledTimes(2) and spawn().toHaveBeenCalledTimes(2). Previously used arrayContaining, which would have passed even if list() was called once per issue instead of once per group.

Considered and left alone

  • Two-pass scan over issues in batch-spawn (once to detect needsFallback, once to group). Could be fused; current form is clearer to read and the arrays are tiny.
  • Multi-slash refs like a/b/c still split on the first slash. Session-manager's branch sanitizer handles the slashy remainder; an explicit reject didn't seem worth the complexity.
  • Duplicate sessionPrefix across projects — first-in-insertion-order wins. Config validation in core should reject this upstream; not this PR's problem.

Final state

  • 8 core spawn-target tests + 25 CLI spawn tests passing.
  • pnpm typecheck clean on core and cli.
  • No other files touched.

@harsh-batheja

Copy link
Copy Markdown
Contributor Author

Third self-review (fresh eyes)

Re-read the final PR state end-to-end without referring to prior reviews. No new bugs found, but two observations worth flagging for the PR record.

Observations (not bugs — just behavioral changes reviewers may ask about)

Branch naming changes for prefixed issues.
In the old (buggy) behavior, ao spawn x402-identity/1 fell into the ad-hoc issue path in session-manager.ts (issue #1201-1209), sanitizing the literal string to feat/x402-identity-1. After the fix, the routed spawn calls tracker.getIssue("1", x402-identity-project), which succeeds, and the branch comes from the tracker's branchName() — for GitHub, feat/issue-1. This is more correct (the prefix is no longer leaked into the branch name) but it is a user-visible difference from the buggy state described in the issue body. Existing sessions created under the bug keep their old branches.

Per-group preflight even when the group spawns nothing.
ao batch-spawn x402-identity/20 where issue 20 already has an active session: the x402-identity group preflights (tmux check, gh auth check) and lists sessions before discovering the duplicate and skipping. Same cost as the pre-refactor version, but worth knowing that preflight scales with groups not with created sessions.

Dead-code sanity check

if (!target) at line 311 of spawn.ts is unreachable: the preceding needsFallback sweep guarantees that if any issue would return null without a fallback, fallbackProjectId is set. So the second-pass call always receives a fallback when one is needed. Defensive but correct — not removing it.

Re-verified invariants

  • warnIfAONotRunning(projectId) and runSpawnPreflight(config, projectId, …) both see the resolved projectId in every code path (single spawn, batch groups).
  • existingIssueMap is keyed on the persisted s.issueId (post-strip form) and queried with resolved.toLowerCase() — they match.
  • sm.spawn({ projectId, issueId: resolved }) receives the stripped form, so the tracker sees the real issue id and the session metadata stores "1", not "x402-identity/1".
  • --agent and --claim-pr flags still flow through untouched after routing.
  • Env-var fallback (AO_PROJECT_ID, set when a worker session spawns a child) is not consulted when a prefix match exists — prefix wins, which is the intended precedence.
  • Prototype-key guard (hasOwn) only applied to the project-id lookup; the sessionPrefix match uses Object.entries which already skips inherited keys. Correct.

Final tally

  • 4 commits, ~500 LOC including tests
  • 8 core spawn-target tests + 25 CLI spawn tests passing
  • pnpm typecheck clean on core and cli
  • pnpm lint — 0 errors (33 pre-existing warnings unchanged)
  • 5 files touched: 2 new (spawn-target.ts, spawn-target.test.ts), 3 modified (core/index.ts, cli/spawn.ts, cli/spawn.test.ts)

No further changes recommended. Ready for review.

@harsh-batheja
harsh-batheja merged commit 616c56c into main Apr 22, 2026
10 checks passed
harshitsinghbhandari pushed a commit to harshitsinghbhandari/agent-orchestrator that referenced this pull request May 1, 2026
- Rewrite the @aoagents/ao-core changeset to describe only what feat/1226
  contributes: event enrichment with schemaVersion: 2, issueTitle
  persistence, executeReaction refactor, maybeDispatchCIFailureDetails,
  and bugbot-comments enrichment. Drop the false claims about adding
  spawn-target and format-automated-comments (those modules came from
  upstream PRs OrchestratorInc#1330 and OrchestratorInc#1334).
- Delete stop-kill-all-resilience.md — its claims (kill-all loop,
  fd-safety in tryAcquire, allSessionPrefixes unify) are no longer
  in the branch after the rebase took upstream's canonical stop logic.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
harshitsinghbhandari pushed a commit that referenced this pull request May 1, 2026
* fix: serialize ao start and stop numbered orchestrators (#1306)

* fix: restore dead orchestrators on start (#1306)

* fix: harden startup lock handling (#1306)

* feat(core): enrich events with PR title, description, and URL

Adds PR and issue context to all event payloads sent to notifiers.
External consumers (Telegram, Discord, n8n) can now display meaningful
information without making additional API calls.

Changes:
- Add buildEventContext() helper to extract PR/issue context from session
- Enrich all createEvent() calls with context data (pr, issueId, issueTitle, branch)
- Store issueTitle in session metadata during spawn
- Add issueTitle field to SessionMetadata interface
- Update executeReaction() to accept session for context access
- Add tests for event enrichment

The context includes:
- pr: { url, title, number, branch } when PR exists
- issueId: issue identifier
- issueTitle: issue title (from tracker during spawn)
- branch: session branch name

Events before PR creation gracefully omit PR fields (pr: null).
Existing webhook consumers that ignore unknown fields are unaffected.

Closes #1226

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* fix(core): address review comments for event enrichment

- Add issueTitle to readMetadata/writeMetadata for proper persistence
- Create ReactionSessionContext type for type-safe system events
- Replace unsafe `as unknown as Session` cast with proper union type
- Add end-to-end test verifying issueTitle persistence during spawn

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* fix(cli): include lock file path in startup lock error message

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address review feedback — fd safety, kill-all resilience, issueTitle restore

- Restore try/catch/finally in tryAcquire for fd leak prevention
- Wrap stop command's sm.list()+kill in try/catch so dashboard shutdown
  always runs even on session listing failure
- Add per-iteration error handling in kill-all loop with partial failure
  reporting (spinner.warn for mixed results)
- Unify allSessionPrefixes derivation between start and stop commands
- Propagate issueTitle through archive restore path
- Add clarifying comments on agentInfo.summary fallback and intentional
  prNumber/prUrl duplication in event data
- Add ora warn mock for stop tests
- Update changeset to minor (event enrichment is a feature) and add CLI
  changeset for stop resilience
- Add tests for kill-all error mid-loop and issueTitle archive restore

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address harsh-batheja review — title/summary split, lock grace, context namespace

- Separate PR title from agent summary in EventContext: title is null
  until enrichment cache populates; summary is a distinct field so
  webhook consumers never confuse the task summary for a PR title.
- Restore UNPARSEABLE_LOCK_GRACE_MS (5s mtime grace) and
  isStaleUnparseableLock lost during merge conflict resolution —
  prevents lockfile-steal race when process A just created the file
  but hasn't written metadata yet.
- Fix orchestrator sort: extract numeric suffix instead of
  localeCompare so -10 sorts after -2, not before.
- Namespace context under data.context instead of spreading into data
  to prevent field collisions with reaction-specific keys.
- Add schemaVersion: 2 to all enriched events so consumers can
  migrate away from top-level prNumber/prUrl (kept for compat,
  marked for removal in v3).
- Update event enrichment tests for nested context structure.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: resolve merge conflicts — use formatReviewCommentsMessage, fix batch enrichment mock

- Replace formatAutomatedCommentsMessage with upstream's formatReviewCommentsMessage
  for automated review comment dispatch (fixes type mismatch with ReviewComment[])
- Make createMockSCM's enrichSessionsPRBatch dynamically resolve from individual
  method mocks so test overrides (e.g. getPRState("closed")) propagate correctly
- Add explicit enrichSessionsPRBatch to merge-conflict-tracking test to avoid
  unexpected getMergeability calls from the dynamic mock
- Remove all debug console.log statements added during troubleshooting

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: wire up maybeDispatchCIFailureDetails and record dispatch hash on transition

The function was defined but never called after merge conflict resolution
dropped the call site. Added it back to the Promise.allSettled alongside
maybeDispatchReviewBacklog and maybeDispatchMergeConflicts.

Also updated the transition-reaction early-return to record the dispatch
hash, since the transition path now enriches the CI message with detailed
check info from the batch cache — preventing duplicate sends on subsequent
polls.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* test: remove stale duplicate test from rebase

Removes the orphaned numbered-orchestrator restoration test left over
from feat/1226 history. Upstream's canonical model test (same name,
expects "app-orchestrator" via ensureOrchestrator) supersedes it.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* docs: remove misleading CLI CHANGELOG entry

The "Restore the most recently active dead orchestrator on ao start"
entry described upstream's ensureOrchestrator behavior (#1487), not
work contributed by this PR.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* docs(changeset): correct scope and drop stale CLI changeset

- Rewrite the @aoagents/ao-core changeset to describe only what feat/1226
  contributes: event enrichment with schemaVersion: 2, issueTitle
  persistence, executeReaction refactor, maybeDispatchCIFailureDetails,
  and bugbot-comments enrichment. Drop the false claims about adding
  spawn-target and format-automated-comments (those modules came from
  upstream PRs #1330 and #1334).
- Delete stop-kill-all-resilience.md — its claims (kill-all loop,
  fd-safety in tryAcquire, allSessionPrefixes unify) are no longer
  in the branch after the rebase took upstream's canonical stop logic.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
alerios pushed a commit to AIMagicFlow/agent-orchestrator that referenced this pull request Oct 14, 2026
…torInc#1330)

* fix(cli): derive projectId from prefixed issue id on spawn

When `ao spawn <projectId>/<issue>` is used in a multi-project config,
route the spawn to the prefixed project and strip the prefix from the
issue id. Previously the projectId fell back to whichever project
`ao start` was running for, tagging cross-project sessions with the
wrong project (and session prefix).

Applies to both `ao spawn` and `ao batch-spawn`; batch-spawn errors
out if issues mix multiple project prefixes.

Fixes OrchestratorInc#1329

* refactor(core,cli): lift spawn target resolution into core

Move the issue-prefix → project routing from the CLI into a reusable
core utility, `resolveSpawnTarget(projects, issueRef, fallback?)`.

- Exposes routing to any spawn entry point (CLI, web API, programmatic).
- Accepts either a project id or sessionPrefix as the prefix; project id
  wins on collision.
- `ao batch-spawn` now groups issues by resolved project and preflights
  once per group instead of erroring on mixed prefixes.

Tests:
- 8 unit tests for `resolveSpawnTarget` in core.
- CLI spawn.test.ts: adds a sessionPrefix routing test (23 tests total).

Refs OrchestratorInc#1329

* test(cli): cover batch-spawn grouping; tighten resolveSpawnTarget API

Self-review found three gaps:

- `resolveSpawnTarget` accepted `undefined` issueRef and returned
  `{ issueId: "" }` with a fallback project. Dead path — both callers
  guard against undefined. Drop it and make issueRef required.
- No tests for `batch-spawn`. Added two:
  - routes cross-project prefixed issues to the correct project and
    lists each project's sessions separately
  - skips a prefixed issue when the target project already has an
    active session for it
- `spawn` and `batch-spawn` help text didn't document the
  `<projectId>/<issue>` or `<sessionPrefix>/<issue>` forms.

* fix(core): guard resolveSpawnTarget against prototype-key matches

Second review pass found a latent bug:

Plain JS objects inherit `__proto__`, `constructor`, `toString`,
`hasOwnProperty`, etc. from Object.prototype. The previous
`if (projects[prefix])` check entered the routing branch for any of
these keys, mis-routing a user typing `ao spawn __proto__/42` with
`projectId: "__proto__"`. Downstream session-manager would then receive
a junk project object (Object.prototype itself) and fail with a
non-obvious error.

Fix: use `Object.prototype.hasOwnProperty.call(projects, prefix)` so
only actual configured project ids match.

Also:
- Document the case-sensitive matching semantic explicitly.
- Lock in the batch-spawn grouping contract by asserting exact
  `list()` and `spawn()` call counts in the cross-project test.
alerios pushed a commit to AIMagicFlow/agent-orchestrator that referenced this pull request Oct 14, 2026
…tratorInc#1326)

* fix: serialize ao start and stop numbered orchestrators (OrchestratorInc#1306)

* fix: restore dead orchestrators on start (OrchestratorInc#1306)

* fix: harden startup lock handling (OrchestratorInc#1306)

* feat(core): enrich events with PR title, description, and URL

Adds PR and issue context to all event payloads sent to notifiers.
External consumers (Telegram, Discord, n8n) can now display meaningful
information without making additional API calls.

Changes:
- Add buildEventContext() helper to extract PR/issue context from session
- Enrich all createEvent() calls with context data (pr, issueId, issueTitle, branch)
- Store issueTitle in session metadata during spawn
- Add issueTitle field to SessionMetadata interface
- Update executeReaction() to accept session for context access
- Add tests for event enrichment

The context includes:
- pr: { url, title, number, branch } when PR exists
- issueId: issue identifier
- issueTitle: issue title (from tracker during spawn)
- branch: session branch name

Events before PR creation gracefully omit PR fields (pr: null).
Existing webhook consumers that ignore unknown fields are unaffected.

Closes OrchestratorInc#1226

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* fix(core): address review comments for event enrichment

- Add issueTitle to readMetadata/writeMetadata for proper persistence
- Create ReactionSessionContext type for type-safe system events
- Replace unsafe `as unknown as Session` cast with proper union type
- Add end-to-end test verifying issueTitle persistence during spawn

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* fix(cli): include lock file path in startup lock error message

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address review feedback — fd safety, kill-all resilience, issueTitle restore

- Restore try/catch/finally in tryAcquire for fd leak prevention
- Wrap stop command's sm.list()+kill in try/catch so dashboard shutdown
  always runs even on session listing failure
- Add per-iteration error handling in kill-all loop with partial failure
  reporting (spinner.warn for mixed results)
- Unify allSessionPrefixes derivation between start and stop commands
- Propagate issueTitle through archive restore path
- Add clarifying comments on agentInfo.summary fallback and intentional
  prNumber/prUrl duplication in event data
- Add ora warn mock for stop tests
- Update changeset to minor (event enrichment is a feature) and add CLI
  changeset for stop resilience
- Add tests for kill-all error mid-loop and issueTitle archive restore

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address harsh-batheja review — title/summary split, lock grace, context namespace

- Separate PR title from agent summary in EventContext: title is null
  until enrichment cache populates; summary is a distinct field so
  webhook consumers never confuse the task summary for a PR title.
- Restore UNPARSEABLE_LOCK_GRACE_MS (5s mtime grace) and
  isStaleUnparseableLock lost during merge conflict resolution —
  prevents lockfile-steal race when process A just created the file
  but hasn't written metadata yet.
- Fix orchestrator sort: extract numeric suffix instead of
  localeCompare so -10 sorts after -2, not before.
- Namespace context under data.context instead of spreading into data
  to prevent field collisions with reaction-specific keys.
- Add schemaVersion: 2 to all enriched events so consumers can
  migrate away from top-level prNumber/prUrl (kept for compat,
  marked for removal in v3).
- Update event enrichment tests for nested context structure.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: resolve merge conflicts — use formatReviewCommentsMessage, fix batch enrichment mock

- Replace formatAutomatedCommentsMessage with upstream's formatReviewCommentsMessage
  for automated review comment dispatch (fixes type mismatch with ReviewComment[])
- Make createMockSCM's enrichSessionsPRBatch dynamically resolve from individual
  method mocks so test overrides (e.g. getPRState("closed")) propagate correctly
- Add explicit enrichSessionsPRBatch to merge-conflict-tracking test to avoid
  unexpected getMergeability calls from the dynamic mock
- Remove all debug console.log statements added during troubleshooting

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: wire up maybeDispatchCIFailureDetails and record dispatch hash on transition

The function was defined but never called after merge conflict resolution
dropped the call site. Added it back to the Promise.allSettled alongside
maybeDispatchReviewBacklog and maybeDispatchMergeConflicts.

Also updated the transition-reaction early-return to record the dispatch
hash, since the transition path now enriches the CI message with detailed
check info from the batch cache — preventing duplicate sends on subsequent
polls.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* test: remove stale duplicate test from rebase

Removes the orphaned numbered-orchestrator restoration test left over
from feat/1226 history. Upstream's canonical model test (same name,
expects "app-orchestrator" via ensureOrchestrator) supersedes it.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* docs: remove misleading CLI CHANGELOG entry

The "Restore the most recently active dead orchestrator on ao start"
entry described upstream's ensureOrchestrator behavior (OrchestratorInc#1487), not
work contributed by this PR.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* docs(changeset): correct scope and drop stale CLI changeset

- Rewrite the @aoagents/ao-core changeset to describe only what feat/1226
  contributes: event enrichment with schemaVersion: 2, issueTitle
  persistence, executeReaction refactor, maybeDispatchCIFailureDetails,
  and bugbot-comments enrichment. Drop the false claims about adding
  spawn-target and format-automated-comments (those modules came from
  upstream PRs OrchestratorInc#1330 and OrchestratorInc#1334).
- Delete stop-kill-all-resilience.md — its claims (kill-all loop,
  fd-safety in tryAcquire, allSessionPrefixes unify) are no longer
  in the branch after the rebase took upstream's canonical stop logic.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
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.

bug: cross-project spawn tags session with wrong projectId

2 participants