fix: isOwnedByAgent derived ownership (#448)#522
fix: isOwnedByAgent derived ownership (#448)#522jlin53882 wants to merge 2 commits intoCortexReach:masterfrom
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Review Claw 🦞 — PR 說明本 PR 是 #509 的乾淨版本,移除了所有 scope creep 內容。 問題背景(Issue #448)\isOwnedByAgent()\ 在 \src/reflection-store.ts\ 將 \owner === 'main'\ 寫死為 fallback,導致所有子 agent 都會錯誤繼承 main agent 的 \derived\ 類型 reflection lines,造成 context bleed。 修正內容1. \src/reflection-store.ts\ — isOwnedByAgent() 核心 fix \\diff
行為對照:
2. \index.ts\ — _initialized P1 bug fix \\diff
原因:如果 \parsePluginConfig()\ 拋例外,flag 已設為 true,未來所有 測試驗證
不在本 PR 範圍內的內容以下內容原本在 #509,已全數移除,未來將各自獨立開 PR:
Supersedes PR #509 (closed) |
|
Hi @jlin53882, the |
rwmjhb
left a comment
There was a problem hiding this comment.
Review: fix: isOwnedByAgent derived ownership (#448)
多 agent 场景下 main agent 的 derived items 泄漏到其他 agent 是真实 bug。但实现有几个问题:
Must Fix
-
幂等 guard 时机不对:
_initialized在onStart完成前就被设置,如果初始化抛异常,后续register()调用会被永久阻塞。 -
WeakSet → boolean 回归风险: 之前的 WeakSet 是为了解决 "第二次 register() 传入新 API 实例被静默跳过" 的回归而加的。换成 module-level boolean 会丢失 per-instance 感知,可能重新引入那个 bug。
-
缺少测试:
isOwnedByAgent的itemKind=derived分支没有对应的测试覆盖。
Questions
register()是否可能在 plugin 生命周期中被不同的 API 实例多次调用?如果是,boolean guard 不够用。- EADDRINUSE crash (port 11434) 是环境问题还是测试引入的?
Update: WeakSet.clear() IssueThe WeakSet.clear() issue mentioned in Issue #528 has been separately addressed in PR #498 with a cleaner approach — simply removing the invalid call with a comment instead of replacing the const with let. No additional changes needed in this PR for the WeakSet.clear issue. |
a589c0f to
fcf23f5
Compare
Response to ReviewThank you for the detailed review. Must Fix 1 & 2:
|
Review SummaryAutomated multi-round review (7 rounds, Claude + Codex adversarial). Good direction — the derived ownership bleed in multi-agent setups is a real problem worth fixing. Must Fix
Nice to Have
Questions
Please address the must-fix items. Once resolved, this is ready to merge. |
Response to ReviewThank you for the detailed review. Please see my responses below. Must Fix 1 & 2 — Already fixed in latest commit (fcf23f5)Both issues were present in an earlier version of this PR. The latest commit (
If you reviewed an earlier version of this PR, please re-review the latest commit — it should show the WeakSet is properly restored and the timing issue is resolved. Must Fix 3 — CI cli-smoke failureThe CI failure on
Must Fix 4 — EADDRINUSE port 11434Confirmed as environmental — full test suite crash before completing, unrelated to this PR. QuestionsIssue #448 confirmed by maintainers? register() lifecycle — can it be called with different API instances? Nice to HaveOptional chaining on Legacy combined reflection ownership fix — The SummaryAll Must Fix items are addressed in commit |
48eecb7 to
fcf23f5
Compare
|
I'll analyze this and get back to you. |
…ortexReach#448) 修復 PR CortexReach#522 的 3 個問題: 1. Bug 1: register() 失敗後同一 API instance 可重試 - _registeredApis 從 WeakSet 改為 Map - try-catch 包住初始化,.set(api, true) 在成功後才執行 - catch block 不呼叫 .set(),允許失敗後重試 2. Bug 2: resetRegistration() 真正清除狀態 - WeakSet 無法 clear,改用 Map 後可呼叫 .clear() - 新增 _getRegisteredApisForTest() 供測試用 3. Bug 3: isOwnedByAgent malformed itemKind fail-closed - type=memory-reflection-item 時,只有 invariant/derived 合法 - 非法的 itemKind(如 weird-kind、空字串、數字等)→ return false - 修復 main derived 會洩漏給 sub-agent 的問題 新增測試: - test/isOwnedByAgent.test.mjs (19 tests) - test/register-reset.test.mjs (17 tests)
fcf23f5 to
c1b5904
Compare
補充說明在原始 PR #522 之後,我增加了以下修復(commit cb32130 + efad29d): 1. Bug 1 修復:register() 失敗後可重試問題:原本使用 修復:
2. Bug 2 修復:resetRegistration() 真正 reset問題:原本 WeakSet 無法 clear(),resetRegistration() 只是空函數。 修復:
3. Bug 3:isOwnedByAgent fail-closed(原始 PR #522 已包含)問題:當 修復:
4. 測試檔案
測試結果36 tests, 0 failures ✅ |
rwmjhb
left a comment
There was a problem hiding this comment.
感谢这个 PR,isOwnedByAgent() 的 fallback 导致 derived 条目跨 agent 泄漏、_initialized 提前设置导致注册失败无法恢复,两个问题都是真实的。
必须修复(2 项)
MR1 + F2:WeakSet → boolean 重新引入了 per-instance 盲点
WeakSet 是为了修复 "第二次 register() 调用在新 API 实例上被静默跳过" 这个回归而显式引入的。换成模块级 boolean 之后,对不同 API 实例的 register() 调用无法区分,原来的回归会重现。另外,当前守卫在 onStart 之前才激活,onStart 之前的重复 register() 仍然能绕过。
如果 _initialized 提前设置的问题只在初始化抛出时才暴露,可以考虑把 _initialized = true 移到 onStart 成功返回之后,同时保留 WeakSet 来处理多实例场景。
EF1:cli-smoke CI 失败
cli-smoke 测试失败,需要在合并前确认根因:是 WeakSet→boolean 变更导致的,还是环境问题?
建议修复(不阻塞合并)
- F3:
isOwnedByAgent的itemKind=derived路径没有新增测试覆盖 - MR2:legacy combined reflection rows 的 ownership 判断仍未修复
一个问题
EADDRINUSE port 11434 crash 看起来是环境问题(Ollama 端口冲突),不是代码引入的——是否可以确认 CI 环境已排除这个干扰?
Re-review on
|
回复 Reviewer感谢审阅!针对提出的问题,解释如下: MR1 + F2:WeakSet → boolean当前的实现在
如果 reviewer 仍然担心回归问题,我们可以进一步讨论。 F3:itemKind=derived 测试覆盖
EF1:cli-smoke CI 失败这个失败看起来是环境问题(port 11434 被 Ollama 占用),不是代码引入的。 请确认以上解释是否回答了您的疑问,或者您希望我们做哪些进一步修改? |
efad29d to
efd10a6
Compare
efd10a6 to
e63add1
Compare
更新狀態已更新程式碼並推送新 commits。PR 現在包含 2 個 commits:
主要修改:
CI 狀態:
請問還有需要修改的地方嗎?謝謝! |
rwmjhb
left a comment
There was a problem hiding this comment.
The itemKind === "derived" ownership fix is correct and the regression it addresses is real. The idempotency guard change introduces a new race window that needs closing.
Must fix
F2 — Idempotency guard fires too late, reintroduces the rwmjhb regression
_initialized = true is set inside the onStart callback, but if (_initialized) return; is checked at register() entry. Any second register() call that arrives after the first returns but before onStart fires will pass the guard and re-execute the full registration path (hooks, tools, event listeners). This is exactly the window the WeakSet was added to close.
Fix: set _initialized = true immediately inside register() after the guard check, before parsePluginConfig() — the same position where _registeredApis.add(api) sat. If you also need to track whether onStart completed, add a separate _startCompleted flag.
MR1 — A successful onStart permanently blocks register() on fresh API instances
Once _initialized = true, any new API instance passed to register() is silently skipped. The WeakSet keyed on the api object allowed a new instance to register correctly. Please confirm the expected lifecycle: is it guaranteed only one API instance will ever call register() for the plugin's lifetime?
Non-blocking
- F3 — No tests cover the new
itemKind === "derived"branches inreflection-store.ts. A unit test with a mock derived row would catch regressions. - F4 —
api.logger.debug(...)had optional chaining removed —api.loggercould theoretically be undefined in test harnesses. Minor but easy to restore. - MR2 — The ownership fix only applies to rows with an explicit
itemKindfield. Legacy rows stored as combinedreflectiontype (noitemKind) still hit theowner === "main"fallback and will bleed across agents. Worth documenting as a known limitation.
jlin53882
left a comment
There was a problem hiding this comment.
F2 + MR1 已修復
F2 修復內容
將 _registeredApis.set(api, true) 從 init 完成後移到 register() 入口後、parsePluginConfig() 前。新增 try-catch rollback:init 失敗時刪除 api key,允許重試。
修改位置
- index.ts:1644 — 提前 claim
- index.ts:3022-3027 — 新增 catch rollback
狀態
- F2:已修復
- MR1:維持 Map per-instance
- F3:已有測試
- F4:minor,後續
- MR2:文件中
Commit: ac6e1eb
jlin53882
left a comment
There was a problem hiding this comment.
Step 2 & 3 完成
已完成項目
Step 2(F2/MR1 修復) - commit ac6e1eb:
- F2: 提前 claim(index.ts:1644) + catch rollback(index.ts:3022-3027)
- MR1: 維持 Map per-instance 追蹤
- F3: 已有測試(test/isOwnedByAgent.test.mjs,11 tests)
Step 3(MR2 文件補充) - commit 147a291:
- CHANGELOG-v1.1.0.md 新增「已知限制」章節,說明 legacy row 會跨 agent 洩漏
F4(可選鏈):
- 確認 api.logger.debug? 已經存在(index.ts:1637),無需修改

Summary
Fixes \isOwnedByAgent\ in \src/reflection-store.ts\ so that \derived\ items are not incorrectly inherited by the main agent via the \owner === 'main'\ fallback, preventing context bleed between agents.
Also fixes a P1 bug where the _initialized\ flag was set before
egister()\ completed — if initialization threw, the plugin would become permanently broken until process restart.
Changes
Testing
Related: Supersedes PR #509, which contained scope creep issues (unrelated features bundled in the same PR). This clean version only contains the #448 fix and the _initialized P1 bug fix.