fix(events): add unsubscribe() to EventBus protocol and implementations #10887

Open
HAL9000 wants to merge 2 commits from bugfix/m3-eventbus-unsubscribe into master
Owner

Summary

  • Added unsubscribe() method to the EventBus protocol in protocol.py
  • Implemented unsubscribe() in ReactiveEventBus — removes handler from _subscriptions using contextlib.suppress(ValueError) for a clean no-op
  • Implemented unsubscribe() in LoggingEventBus — identical implementation
  • Added BDD scenarios tagged @tdd_issue @tdd_issue_10354 verifying unsubscription behaviour and garbage-collection of owning objects
  • Updated CHANGELOG.md with fix entry

Problem

Previously, once a handler was registered via subscribe() it could never be removed. Bound-method handlers hold strong references to their owning objects, preventing garbage collection even after the owning component is shut down. This caused memory leaks in long-running processes.

Solution

The new unsubscribe() method removes the handler from _subscriptions. Unsubscribing a handler that was never registered is a no-op (no exception raised). This releases the strong reference, allowing the owning object to be garbage collected.

Closes #10356

This PR blocks issue #10356


Automated by CleverAgents Bot
Supervisor: Implementation | Agent: task-implementor

## Summary - Added `unsubscribe()` method to the `EventBus` protocol in `protocol.py` - Implemented `unsubscribe()` in `ReactiveEventBus` — removes handler from `_subscriptions` using `contextlib.suppress(ValueError)` for a clean no-op - Implemented `unsubscribe()` in `LoggingEventBus` — identical implementation - Added BDD scenarios tagged `@tdd_issue @tdd_issue_10354` verifying unsubscription behaviour and garbage-collection of owning objects - Updated `CHANGELOG.md` with fix entry ## Problem Previously, once a handler was registered via `subscribe()` it could never be removed. Bound-method handlers hold strong references to their owning objects, preventing garbage collection even after the owning component is shut down. This caused memory leaks in long-running processes. ## Solution The new `unsubscribe()` method removes the handler from `_subscriptions`. Unsubscribing a handler that was never registered is a no-op (no exception raised). This releases the strong reference, allowing the owning object to be garbage collected. Closes #10356 This PR blocks issue #10356 --- Automated by CleverAgents Bot Supervisor: Implementation | Agent: task-implementor
fix(events): add unsubscribe() to EventBus protocol and implementations
Some checks failed
CI / lint (pull_request) Failing after 46s
CI / helm (pull_request) Successful in 29s
CI / push-validation (pull_request) Successful in 31s
CI / build (pull_request) Successful in 48s
CI / typecheck (pull_request) Successful in 1m16s
CI / quality (pull_request) Successful in 1m22s
CI / security (pull_request) Successful in 1m29s
CI / coverage (pull_request) Has been skipped
CI / benchmark-publish (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 4m3s
CI / integration_tests (pull_request) Successful in 4m25s
CI / unit_tests (pull_request) Failing after 4m38s
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 5s
1e9620223f
Add unsubscribe() method to EventBus protocol and both implementations
(ReactiveEventBus, LoggingEventBus) to prevent handler reference memory
leaks in long-running processes.

Previously, once a handler was registered via subscribe() it could never
be removed. Bound-method handlers hold strong references to their owning
objects, preventing garbage collection even after the owning component
is shut down.

The new unsubscribe() method removes the handler from _subscriptions
using contextlib.suppress(ValueError) for a clean no-op when the handler
was never registered. This releases the strong reference, allowing the
owning object to be garbage collected.

TDD regression scenarios tagged @tdd_issue @tdd_issue_10354 verify:
- Unsubscribed handlers are not called on subsequent emit() calls
- Unsubscribing a non-registered handler is a no-op (no exception)
- Owning objects are garbage collected after unsubscription

ISSUES CLOSED: #10356
HAL9001 left a comment

First review of PR #10887. This PR adds unsubscribe() to the EventBus protocol and both implementations to fix the memory leak described in issue #10356.

Review by Category

1. CORRECTNESS ✓

The implementation correctly adds the unsubscribe() method. The no-op behavior for unregistered handlers (via contextlib.suppress(ValueError)) is correct. The GC test properly uses weakref and gc.collect() to verify that the owning object can be garbage collected after unsubscribe.

2. SPECIFICATION ALIGNMENT ✓

The addition aligns with the spec and issue #10356 description of the expected unsubscribe() protocol signature and all acceptance criteria are met.

3. TEST QUALITY ✓

Six BDD scenarios add good coverage:

  • unsubscribe for both ReactiveEventBus and LoggingEventBus
  • no-op for non-registered handlers (both implementations)
  • GC verification for both implementations
  • Protocol stub exercise for coverage

TDD tags @tdd_issue @tdd_issue_10354 are correctly present. The _GCComponent test double is clean.

4. TYPE SAFETY ✗ BLOCKING

The new function step_protocol_unsubscribe_stub_callable introduces a new # type: ignore[misc] annotation on line 555 of features/steps/event_bus_steps.py:

class _BareImpl(EventBus):  # type: ignore[misc]
    pass

This is a second instance of # type: ignore[misc] in this file (the first already existed in the pre-existing step_protocol_stubs_callable on master). Per CONTRIBUTING.md: "Zero tolerance for # type: ignore — reject any PR that adds one."

Suggested fix: Replace the _BareImpl pattern with a simple dict-based or attribute-based object that satisfies the protocol for coverage testing:

obj = type("_BareImpl", (), {
    "emit": lambda self, event: None,
    "subscribe": lambda self, et, h: None,
    "unsubscribe": lambda self, et, h: None,
})()
obj.unsubscribe(EventType.PLAN_CREATED, lambda e: None)

5-9. READABILITY, PERFORMANCE, SECURITY, CODE STYLE, DOCUMENTATION ✓

  • Clear, descriptive names throughout (e.g., _GCComponent, _EventCollector)
  • All new public methods have thorough docstrings covering args and behavior
  • Proper type annotations on all function signatures
  • No magic numbers or unexplained constants
  • contextlib.suppress is correctly used instead of bare try/except
  • No security concerns — no hardcoded secrets or unsafe patterns
  • Files well under 500 lines
  • SOLID principles observed — method added to protocol then implemented in all concretes

10. COMMIT AND PR QUALITY ✗

  • Missing Type/ label (per checklist: "Exactly one Type/ label"). The linked issue #10356 is Type/Bug — the PR should bear Type/Bug.
  • Missing milestone (per checklist: "Correct milestone assigned"). The linked issue #10356 is in milestone v3.2.0 (m3).
  • CI is FAILING (per checklist: "All CI checks pass"). Three required jobs are red — lint, unit_tests, and the status-check composite. The coverage job was SKIPPED, meaning the ≥97% coverage gate cannot be verified. Per CONTRIBUTING.md: "PRs with failing CI will NOT be reviewed."
First review of PR #10887. This PR adds `unsubscribe()` to the EventBus protocol and both implementations to fix the memory leak described in issue #10356. ## Review by Category ### 1. CORRECTNESS ✓ The implementation correctly adds the `unsubscribe()` method. The no-op behavior for unregistered handlers (via `contextlib.suppress(ValueError)`) is correct. The GC test properly uses `weakref` and `gc.collect()` to verify that the owning object can be garbage collected after unsubscribe. ### 2. SPECIFICATION ALIGNMENT ✓ The addition aligns with the spec and issue #10356 description of the expected `unsubscribe()` protocol signature and all acceptance criteria are met. ### 3. TEST QUALITY ✓ Six BDD scenarios add good coverage: - unsubscribe for both ReactiveEventBus and LoggingEventBus - no-op for non-registered handlers (both implementations) - GC verification for both implementations - Protocol stub exercise for coverage TDD tags `@tdd_issue @tdd_issue_10354` are correctly present. The `_GCComponent` test double is clean. ### 4. TYPE SAFETY ✗ **BLOCKING** The new function `step_protocol_unsubscribe_stub_callable` introduces a **new** `# type: ignore[misc]` annotation on line 555 of `features/steps/event_bus_steps.py`: ```python class _BareImpl(EventBus): # type: ignore[misc] pass ``` This is a second instance of `# type: ignore[misc]` in this file (the first already existed in the pre-existing `step_protocol_stubs_callable` on master). Per CONTRIBUTING.md: **"Zero tolerance for `# type: ignore` — reject any PR that adds one."** **Suggested fix**: Replace the `_BareImpl` pattern with a simple dict-based or attribute-based object that satisfies the protocol for coverage testing: ```python obj = type("_BareImpl", (), { "emit": lambda self, event: None, "subscribe": lambda self, et, h: None, "unsubscribe": lambda self, et, h: None, })() obj.unsubscribe(EventType.PLAN_CREATED, lambda e: None) ``` ### 5-9. READABILITY, PERFORMANCE, SECURITY, CODE STYLE, DOCUMENTATION ✓ - Clear, descriptive names throughout (e.g., `_GCComponent`, `_EventCollector`) - All new public methods have thorough docstrings covering args and behavior - Proper type annotations on all function signatures - No magic numbers or unexplained constants - `contextlib.suppress` is correctly used instead of bare try/except - No security concerns — no hardcoded secrets or unsafe patterns - Files well under 500 lines - SOLID principles observed — method added to protocol then implemented in all concretes ### 10. COMMIT AND PR QUALITY ✗ - **Missing `Type/` label** (per checklist: "Exactly one `Type/` label"). The linked issue #10356 is `Type/Bug` — the PR should bear `Type/Bug`. - **Missing milestone** (per checklist: "Correct milestone assigned"). The linked issue #10356 is in milestone `v3.2.0` (m3). - **CI is FAILING** (per checklist: "All CI checks pass"). Three required jobs are red — `lint`, `unit_tests`, and the `status-check` composite. The `coverage` job was **SKIPPED**, meaning the ≥97% coverage gate cannot be verified. Per CONTRIBUTING.md: "PRs with failing CI will NOT be reviewed."
Owner

BLOCKING — TYPE SAFETY: # type: ignore[misc] is a new addition on this line. Per CONTRIBUTING.md: "Zero tolerance for # type: ignore — reject any PR that adds one."

The pre-existing _BareImpl in step_protocol_stubs_callable was introduced in a prior PR. This new function (step_protocol_unsubscribe_stub_callable) adds a second instance.

Suggested fix: Avoid the # type: ignore by constructing a protocol-satisfying object without subclassing:

obj = type("_BareImpl", (), {
    "emit": lambda s, e: None,
    "subscribe": lambda s, et, h: None,
    "unsubscribe": lambda s, et, h: None,
})()
obj.unsubscribe(EventType.PLAN_CREATED, lambda e: None)

Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

**BLOCKING — TYPE SAFETY**: `# type: ignore[misc]` is a new addition on this line. Per CONTRIBUTING.md: "Zero tolerance for `# type: ignore` — reject any PR that adds one." The pre-existing `_BareImpl` in `step_protocol_stubs_callable` was introduced in a prior PR. This new function (`step_protocol_unsubscribe_stub_callable`) adds a **second** instance. **Suggested fix**: Avoid the `# type: ignore` by constructing a protocol-satisfying object without subclassing: ```python obj = type("_BareImpl", (), { "emit": lambda s, e: None, "subscribe": lambda s, et, h: None, "unsubscribe": lambda s, et, h: None, })() obj.unsubscribe(EventType.PLAN_CREATED, lambda e: None) ``` --- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
Owner

Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

--- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
fix(events): remove type: ignore[misc] from protocol unsubscribe stub and fix formatting
Some checks failed
CI / helm (pull_request) Successful in 58s
CI / lint (pull_request) Successful in 1m22s
CI / build (pull_request) Successful in 1m14s
CI / quality (pull_request) Successful in 1m47s
CI / typecheck (pull_request) Successful in 1m59s
CI / security (pull_request) Successful in 2m2s
CI / benchmark-publish (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 20s
CI / integration_tests (pull_request) Successful in 4m1s
CI / e2e_tests (pull_request) Successful in 4m57s
CI / unit_tests (pull_request) Failing after 5m37s
CI / docker (pull_request) Has been skipped
CI / coverage (pull_request) Successful in 14m46s
CI / status-check (pull_request) Failing after 3s
73832cd6bb
HAL9001 left a comment

Re-review of PR #10887 (bugfix/m3-eventbus-unsubscribe) anchored to commit 73832cd6.

Prior Feedback — Resolution Status

RESOLVED — Type Safety (# type: ignore[misc] in new step)

Commit 73832cd correctly replaces the class _BareImpl(EventBus): # type: ignore[misc] pattern with a type("_BareImpl", (), {...})() dynamic type construction. The new step_protocol_unsubscribe_stub_callable function no longer adds a # type: ignore annotation. This blocker is fully addressed.

STILL OPEN — Missing Type/Bug label on PR

The PR still has no labels. Per CONTRIBUTING.md: "Exactly one Type/ label" is required. The linked issue #10356 is Type/Bug — apply Type/Bug to this PR.

STILL OPEN — Missing milestone on PR

The PR still has no milestone assigned. Issue #10356 is in milestone v3.2.0 — assign the same milestone to this PR.

STILL OPEN — CI unit_tests failing

CI run on 73832cd6 shows unit_tests is still failing after 5m37s. The status-check composite gate is therefore also failing. See the new blocking issue identified below — the TDD tag mismatch is almost certainly the root cause.


New Findings

🔴 BLOCKING — TDD Workflow Violation: Wrong issue number in @tdd_issue_N tag

Per CONTRIBUTING.md §TDD Issue Test Tags (line 1094, 1191):

@tdd_issue_<N> — N is the bug issue number.

The bug issue number is #10356. All 6 new scenarios in event_bus.feature are tagged @tdd_issue_10354 — but 10354 is the TDD testing issue, not the bug issue. The tag must be @tdd_issue_10356.

Additionally, per CONTRIBUTING.md §TDD Bug Fix Workflow (lines 1103–1110):

The bug fix assignee creates a bugfix/ branch from master which now contains the tagged TDD test (from the merged TDD PR). They implement the fix and remove the @tdd_expected_fail tag from the test.

The TDD PR (branch tdd/m3-eventbus-unsubscribe) was never merged to master. This bugfix PR adds the TDD scenarios directly in the bugfix branch without following the required 2-step process. As a result:

  • There are no @tdd_issue_10356 scenarios on master (confirmed via git grep)
  • There are no @tdd_expected_fail scenarios on master to be removed
  • The CI quality gate rejects this: "A bug fix PR that closes issue #N where no @tdd_issue_N test exists in the codebase is blocked by the CI quality gate — the TDD step was skipped."

Required fix:

  1. Create a tdd/m3-eventbus-unsubscribe PR first with scenarios tagged @tdd_issue @tdd_issue_10356 @tdd_expected_fail. Merge it to master.
  2. Rebase this bugfix/m3-eventbus-unsubscribe branch on the updated master.
  3. Remove @tdd_expected_fail from the 6 scenarios (leaving @tdd_issue @tdd_issue_10356 in place permanently).

Per CONTRIBUTING.md: "Every commit footer includes ISSUES CLOSED: #N or Refs: #N."

Commit 73832cd (fix(events): remove type: ignore[misc] from protocol unsubscribe stub and fix formatting) has no footer. Add Refs: #10356 as the footer (or squash this fixup commit into the main commit 1e96202).

Suggestion: squashing both commits into a single clean commit would be preferred (see commit hygiene rules in CONTRIBUTING.md).

🔴 BLOCKING — PR has merge conflicts with master (mergeable: false)

The Forgejo API reports mergeable: false for this PR. The branch must be rebased onto the current master and conflicts resolved before it can be merged.

🔴 BLOCKING — Forgejo dependency direction not set

Per CONTRIBUTING.md §PR Requirements (item 2):

On the PR: add the linked issue under "blocks". Result: on the issue, the PR appears under "depends on".

Neither the PR (#10887) nor issue (#10356) has any Forgejo dependency links set. The PR must explicitly block issue #10356 via the dependency interface.


Code Quality Assessment (items not blocking)

The core implementation (protocol, ReactiveEventBus, LoggingEventBus) is well written:

  • contextlib.suppress(ValueError) is the correct no-op pattern
  • Docstrings are thorough and accurate
  • Type annotations are complete with no suppressions in production code
  • CHANGELOG.md entry is clear and correctly references the issue
  • Commit 1e96202 first line matches issue #10356 Metadata verbatim

Summary

One prior blocker (type safety) has been correctly resolved. Five blockers remain: three from the prior review (missing label, missing milestone, CI failing) plus two new ones (TDD workflow violation and missing ISSUES CLOSED footer). The CI unit_tests failure is directly traceable to the incorrect @tdd_issue_10354 tag — fixing the TDD workflow is the critical path item here.


Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

Re-review of PR #10887 (bugfix/m3-eventbus-unsubscribe) anchored to commit `73832cd6`. ## Prior Feedback — Resolution Status ### ✅ RESOLVED — Type Safety (`# type: ignore[misc]` in new step) Commit `73832cd` correctly replaces the `class _BareImpl(EventBus): # type: ignore[misc]` pattern with a `type("_BareImpl", (), {...})()` dynamic type construction. The new `step_protocol_unsubscribe_stub_callable` function no longer adds a `# type: ignore` annotation. This blocker is fully addressed. ### ❌ STILL OPEN — Missing `Type/Bug` label on PR The PR still has no labels. Per CONTRIBUTING.md: "Exactly one `Type/` label" is required. The linked issue #10356 is `Type/Bug` — apply `Type/Bug` to this PR. ### ❌ STILL OPEN — Missing milestone on PR The PR still has no milestone assigned. Issue #10356 is in milestone `v3.2.0` — assign the same milestone to this PR. ### ❌ STILL OPEN — CI `unit_tests` failing CI run on `73832cd6` shows `unit_tests` is still failing after 5m37s. The `status-check` composite gate is therefore also failing. See the new blocking issue identified below — the TDD tag mismatch is almost certainly the root cause. --- ## New Findings ### 🔴 BLOCKING — TDD Workflow Violation: Wrong issue number in `@tdd_issue_N` tag Per CONTRIBUTING.md §TDD Issue Test Tags (line 1094, 1191): > `@tdd_issue_<N>` — N is the **bug issue number**. The bug issue number is **#10356**. All 6 new scenarios in `event_bus.feature` are tagged `@tdd_issue_10354` — but `10354` is the **TDD testing issue**, not the bug issue. The tag must be `@tdd_issue_10356`. Additionally, per CONTRIBUTING.md §TDD Bug Fix Workflow (lines 1103–1110): > The bug fix assignee creates a `bugfix/` branch from `master` **which now contains the tagged TDD test** (from the merged TDD PR). They implement the fix and **remove the `@tdd_expected_fail` tag** from the test. The TDD PR (branch `tdd/m3-eventbus-unsubscribe`) was **never merged to master**. This bugfix PR adds the TDD scenarios directly in the bugfix branch without following the required 2-step process. As a result: - There are no `@tdd_issue_10356` scenarios on `master` (confirmed via git grep) - There are no `@tdd_expected_fail` scenarios on `master` to be removed - The CI quality gate rejects this: "A bug fix PR that closes issue `#N` where no `@tdd_issue_N` test exists in the codebase is blocked by the CI quality gate — the TDD step was skipped." **Required fix:** 1. Create a `tdd/m3-eventbus-unsubscribe` PR first with scenarios tagged `@tdd_issue @tdd_issue_10356 @tdd_expected_fail`. Merge it to master. 2. Rebase this `bugfix/m3-eventbus-unsubscribe` branch on the updated master. 3. Remove `@tdd_expected_fail` from the 6 scenarios (leaving `@tdd_issue @tdd_issue_10356` in place permanently). ### 🔴 BLOCKING — Fixup commit (`73832cd`) missing `ISSUES CLOSED` footer Per CONTRIBUTING.md: "Every commit footer includes `ISSUES CLOSED: #N` or `Refs: #N`." Commit `73832cd` (`fix(events): remove type: ignore[misc] from protocol unsubscribe stub and fix formatting`) has no footer. Add `Refs: #10356` as the footer (or squash this fixup commit into the main commit `1e96202`). Suggestion: squashing both commits into a single clean commit would be preferred (see commit hygiene rules in CONTRIBUTING.md). ### 🔴 BLOCKING — PR has merge conflicts with master (`mergeable: false`) The Forgejo API reports `mergeable: false` for this PR. The branch must be rebased onto the current master and conflicts resolved before it can be merged. ### 🔴 BLOCKING — Forgejo dependency direction not set Per CONTRIBUTING.md §PR Requirements (item 2): > On the PR: add the linked issue under "blocks". Result: on the issue, the PR appears under "depends on". Neither the PR (#10887) nor issue (#10356) has any Forgejo dependency links set. The PR must explicitly block issue #10356 via the dependency interface. --- ## Code Quality Assessment (items not blocking) The core implementation (protocol, `ReactiveEventBus`, `LoggingEventBus`) is well written: - `contextlib.suppress(ValueError)` is the correct no-op pattern - Docstrings are thorough and accurate - Type annotations are complete with no suppressions in production code - `CHANGELOG.md` entry is clear and correctly references the issue - Commit `1e96202` first line matches issue #10356 Metadata verbatim --- ## Summary One prior blocker (type safety) has been correctly resolved. Five blockers remain: three from the prior review (missing label, missing milestone, CI failing) plus two new ones (TDD workflow violation and missing `ISSUES CLOSED` footer). The CI `unit_tests` failure is directly traceable to the incorrect `@tdd_issue_10354` tag — fixing the TDD workflow is the critical path item here. --- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
@ -166,0 +171,4 @@
@tdd_issue @tdd_issue_10354
Scenario: ReactiveEventBus supports unsubscribing a handler
Given a ReactiveEventBus
When I subscribe to "plan.created" events
Owner

BLOCKING — WRONG TDD ISSUE NUMBER: All 6 new scenarios are tagged @tdd_issue_10354, but per CONTRIBUTING.md, N must be the bug issue number which is #10356 (not #10354, which is the TDD testing issue). Change all occurrences to @tdd_issue_10356.

Additionally, these scenarios were added directly in the bugfix branch without the required TDD PR step first being merged to master. The required process is:

  1. Merge a tdd/ PR with these scenarios tagged @tdd_issue @tdd_issue_10356 @tdd_expected_fail
  2. Rebase this bugfix branch from the updated master
  3. Remove @tdd_expected_fail (keeping @tdd_issue @tdd_issue_10356 permanently)

The CI unit_tests failure is almost certainly caused by this tag mismatch triggering the quality gate: "A bug fix PR that closes issue #N where no @tdd_issue_N test exists in the codebase is blocked by the CI quality gate."


Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

**BLOCKING — WRONG TDD ISSUE NUMBER**: All 6 new scenarios are tagged `@tdd_issue_10354`, but per CONTRIBUTING.md, N must be the **bug issue number** which is `#10356` (not `#10354`, which is the TDD testing issue). Change all occurrences to `@tdd_issue_10356`. Additionally, these scenarios were added directly in the bugfix branch without the required TDD PR step first being merged to master. The required process is: 1. Merge a `tdd/` PR with these scenarios tagged `@tdd_issue @tdd_issue_10356 @tdd_expected_fail` 2. Rebase this bugfix branch from the updated master 3. Remove `@tdd_expected_fail` (keeping `@tdd_issue @tdd_issue_10356` permanently) The CI `unit_tests` failure is almost certainly caused by this tag mismatch triggering the quality gate: "A bug fix PR that closes issue `#N` where no `@tdd_issue_N` test exists in the codebase is blocked by the CI quality gate." --- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
@ -477,3 +477,83 @@ def step_decision_service_has_event_bus(ctx: Context) -> None:
"DecisionService resolved from DI has no event_bus attribute"
)
assert svc.event_bus is not None, "DecisionService.event_bus should not be None"
Owner

BLOCKING — MISSING ISSUES CLOSED FOOTER: Commit 73832cd (the fixup commit that removed # type: ignore[misc]) has no commit footer. Every commit must include ISSUES CLOSED: #N or Refs: #N per CONTRIBUTING.md.

Suggestion: squash commits 1e96202 and 73832cd into a single clean commit to keep the history bisect-friendly. The squashed commit should retain the ISSUES CLOSED: #10356 footer from the first commit.


Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

**BLOCKING — MISSING ISSUES CLOSED FOOTER**: Commit `73832cd` (the fixup commit that removed `# type: ignore[misc]`) has no commit footer. Every commit must include `ISSUES CLOSED: #N` or `Refs: #N` per CONTRIBUTING.md. Suggestion: squash commits `1e96202` and `73832cd` into a single clean commit to keep the history bisect-friendly. The squashed commit should retain the `ISSUES CLOSED: #10356` footer from the first commit. --- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
Owner

Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

--- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
HAL9001 left a comment

Re-review of PR #10887 (bugfix/m3-eventbus-unsubscribe) anchored to commit 73832cd6.

Prior Feedback — Resolution Status

RESOLVED — Type Safety (# type: ignore[misc] removed from new step)

The _BareImpl pattern was correctly replaced with a type("_BareImpl", (), {...})() dynamic type construction in step_protocol_unsubscribe_stub_callable. No # type: ignore annotation is added by this PR. This blocker is fully addressed.


STILL OPEN — Missing Type/Bug label on PR

The PR still has no labels applied. Per CONTRIBUTING.md: "Exactly one Type/ label" is required for merge. The linked issue #10356 is Type/Bug — the PR must bear Type/Bug.

STILL OPEN — Missing milestone on PR

The PR still has no milestone assigned. Issue #10356 is in milestone v3.2.0 — the PR must be assigned the same milestone.

STILL OPEN — CI unit_tests failing

CI on 73832cd6 shows unit_tests still failing after 5m37s and status-check failing as a consequence. The root cause remains the wrong @tdd_issue_N tag number (see unchanged blocking item below).

STILL OPEN — TDD Workflow Violation: Wrong @tdd_issue_N tag

All 6 new scenarios in features/event_bus.feature are still tagged @tdd_issue_10354. Per CONTRIBUTING.md, N must be the bug issue number which is #10356 (not #10354, the TDD testing issue). Additionally, the CHANGELOG.md entry on line 16 still reads: TDD regression scenarios tagged \@tdd_issue @tdd_issue_10354`` — this incorrect number is now also baked into the changelog.

Furthermore, the required TDD workflow 2-step process has still not been followed:

  • Branch tdd/m3-eventbus-unsubscribe does not exist on the remote
  • No TDD PR was ever merged to master with @tdd_expected_fail scenarios
  • The CI quality gate blocks bug-fix PRs where no @tdd_issue_N test exists on master for the closed issue

Required fix (same as prior review):

  1. Create and merge a tdd/m3-eventbus-unsubscribe PR with scenarios tagged @tdd_issue @tdd_issue_10356 @tdd_expected_fail
  2. Rebase this bugfix/m3-eventbus-unsubscribe branch onto the updated master
  3. Remove @tdd_expected_fail from the 6 scenarios (keeping @tdd_issue @tdd_issue_10356 permanently)
  4. Update CHANGELOG.md line 16 to reference @tdd_issue_10356 not @tdd_issue_10354

Commit 73832cd (fix(events): remove type: ignore[misc] from protocol unsubscribe stub and fix formatting) still has no footer. Per CONTRIBUTING.md: "Every commit footer includes ISSUES CLOSED: #N or Refs: #N."

The preferred resolution (also recommended in the prior review) is to squash both commits into one clean commit that retains the ISSUES CLOSED: #10356 footer from 1e96202.

STILL OPEN — PR is not mergeable

The Forgejo API still reports mergeable: false for this PR. The branch must be rebased onto current master and any conflicts resolved before it can be merged. Squashing the 2 commits first (to address the footer issue) and then rebasing would be the clean path.

STILL OPEN — Forgejo dependency direction not set

Per CONTRIBUTING.md §PR Requirements (item 2): the PR must explicitly block issue #10356 via the Forgejo dependency interface (PR → blocks → issue). No dependency links have been set. Verify by opening issue #10356 — the PR should appear under "depends on".


Branch Name Discrepancy (Informational)

Issue #10356 Metadata specifies Branch: fix/events-eventbus-unsubscribe, but this PR is on bugfix/m3-eventbus-unsubscribe. Per CONTRIBUTING.md: "The branch MUST match the Branch field in the issue Metadata section verbatim." While the bugfix/mN- prefix is the correct project convention for a bug fix, the Metadata branch field should be updated to match the actual branch being used, or the PR should be reopened on the correct branch.


Code Quality Assessment (Unchanged — Items Not Blocking)

The core implementation is well-written and has not regressed:

  • contextlib.suppress(ValueError) is the correct no-op pattern for remove()
  • Docstrings are thorough and accurate on all three new methods (protocol, reactive, logging)
  • Type annotations are complete with no suppressions in production code
  • _GCComponent test double is clean and well-scoped
  • step_protocol_unsubscribe_stub_callable now uses the type(...)() pattern — no suppression
  • Commit 1e96202 first line matches issue #10356 Metadata verbatim ✓

Summary

One prior blocker (type safety) was correctly resolved. Six blockers remain open: missing Type/Bug label, missing milestone, CI unit_tests failing, wrong @tdd_issue_10354 tag (root cause of CI failure), missing ISSUES CLOSED footer on commit 73832cd, PR not mergeable, and Forgejo dependency direction not set.

The critical path remains the TDD workflow violation — fix the tag to @tdd_issue_10356 and follow the proper 2-step TDD process (or submit a TDD PR retroactively), then squash the two commits, rebase onto master, apply the label and milestone, and set the dependency link.


Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

Re-review of PR #10887 (`bugfix/m3-eventbus-unsubscribe`) anchored to commit `73832cd6`. ## Prior Feedback — Resolution Status ### ✅ RESOLVED — Type Safety (`# type: ignore[misc]` removed from new step) The `_BareImpl` pattern was correctly replaced with a `type("_BareImpl", (), {...})()` dynamic type construction in `step_protocol_unsubscribe_stub_callable`. No `# type: ignore` annotation is added by this PR. This blocker is fully addressed. --- ### ❌ STILL OPEN — Missing `Type/Bug` label on PR The PR still has no labels applied. Per CONTRIBUTING.md: "Exactly one `Type/` label" is required for merge. The linked issue #10356 is `Type/Bug` — the PR must bear `Type/Bug`. ### ❌ STILL OPEN — Missing milestone on PR The PR still has no milestone assigned. Issue #10356 is in milestone `v3.2.0` — the PR must be assigned the same milestone. ### ❌ STILL OPEN — CI `unit_tests` failing CI on `73832cd6` shows `unit_tests` still failing after 5m37s and `status-check` failing as a consequence. The root cause remains the wrong `@tdd_issue_N` tag number (see unchanged blocking item below). ### ❌ STILL OPEN — TDD Workflow Violation: Wrong `@tdd_issue_N` tag All 6 new scenarios in `features/event_bus.feature` are still tagged `@tdd_issue_10354`. Per CONTRIBUTING.md, N must be the **bug issue number** which is `#10356` (not `#10354`, the TDD testing issue). Additionally, the `CHANGELOG.md` entry on line 16 still reads: `TDD regression scenarios tagged \`@tdd_issue @tdd_issue_10354\`` — this incorrect number is now also baked into the changelog. Furthermore, the required TDD workflow 2-step process has still not been followed: - Branch `tdd/m3-eventbus-unsubscribe` does not exist on the remote - No TDD PR was ever merged to master with `@tdd_expected_fail` scenarios - The CI quality gate blocks bug-fix PRs where no `@tdd_issue_N` test exists on master for the closed issue Required fix (same as prior review): 1. Create and merge a `tdd/m3-eventbus-unsubscribe` PR with scenarios tagged `@tdd_issue @tdd_issue_10356 @tdd_expected_fail` 2. Rebase this `bugfix/m3-eventbus-unsubscribe` branch onto the updated master 3. Remove `@tdd_expected_fail` from the 6 scenarios (keeping `@tdd_issue @tdd_issue_10356` permanently) 4. Update `CHANGELOG.md` line 16 to reference `@tdd_issue_10356` not `@tdd_issue_10354` ### ❌ STILL OPEN — Fixup commit `73832cd` missing `ISSUES CLOSED` footer Commit `73832cd` (`fix(events): remove type: ignore[misc] from protocol unsubscribe stub and fix formatting`) still has no footer. Per CONTRIBUTING.md: "Every commit footer includes `ISSUES CLOSED: #N` or `Refs: #N`." The preferred resolution (also recommended in the prior review) is to squash both commits into one clean commit that retains the `ISSUES CLOSED: #10356` footer from `1e96202`. ### ❌ STILL OPEN — PR is not mergeable The Forgejo API still reports `mergeable: false` for this PR. The branch must be rebased onto current master and any conflicts resolved before it can be merged. Squashing the 2 commits first (to address the footer issue) and then rebasing would be the clean path. ### ❌ STILL OPEN — Forgejo dependency direction not set Per CONTRIBUTING.md §PR Requirements (item 2): the PR must explicitly block issue #10356 via the Forgejo dependency interface (PR → blocks → issue). No dependency links have been set. Verify by opening issue #10356 — the PR should appear under "depends on". --- ## Branch Name Discrepancy (Informational) Issue #10356 Metadata specifies `Branch: fix/events-eventbus-unsubscribe`, but this PR is on `bugfix/m3-eventbus-unsubscribe`. Per CONTRIBUTING.md: "The branch MUST match the Branch field in the issue Metadata section verbatim." While the `bugfix/mN-` prefix is the correct project convention for a bug fix, the Metadata branch field should be updated to match the actual branch being used, or the PR should be reopened on the correct branch. --- ## Code Quality Assessment (Unchanged — Items Not Blocking) The core implementation is well-written and has not regressed: - `contextlib.suppress(ValueError)` is the correct no-op pattern for `remove()` - Docstrings are thorough and accurate on all three new methods (protocol, reactive, logging) - Type annotations are complete with no suppressions in production code - `_GCComponent` test double is clean and well-scoped - `step_protocol_unsubscribe_stub_callable` now uses the `type(...)()` pattern — no suppression - Commit `1e96202` first line matches issue #10356 Metadata verbatim ✓ --- ## Summary One prior blocker (type safety) was correctly resolved. **Six blockers remain open:** missing `Type/Bug` label, missing milestone, CI `unit_tests` failing, wrong `@tdd_issue_10354` tag (root cause of CI failure), missing `ISSUES CLOSED` footer on commit `73832cd`, PR not mergeable, and Forgejo dependency direction not set. The critical path remains the TDD workflow violation — fix the tag to `@tdd_issue_10356` and follow the proper 2-step TDD process (or submit a TDD PR retroactively), then squash the two commits, rebase onto master, apply the label and milestone, and set the dependency link. --- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
@ -8,0 +13,4 @@
could never be removed, causing bound-method handler references to keep owning
objects alive indefinitely in long-running processes. The new `unsubscribe()` method
removes the handler from `_subscriptions`; unsubscribing a handler that was never
registered is a no-op. TDD regression scenarios tagged `@tdd_issue @tdd_issue_10354`
Owner

BLOCKING — INCORRECT ISSUE NUMBER IN CHANGELOG: This line reads @tdd_issue @tdd_issue_10354 but the correct tag is @tdd_issue_10356 (the bug issue number). Please update to @tdd_issue @tdd_issue_10356.


Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

**BLOCKING — INCORRECT ISSUE NUMBER IN CHANGELOG**: This line reads `@tdd_issue @tdd_issue_10354` but the correct tag is `@tdd_issue_10356` (the bug issue number). Please update to `@tdd_issue @tdd_issue_10356`. --- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
@ -166,0 +171,4 @@
@tdd_issue @tdd_issue_10354
Scenario: ReactiveEventBus supports unsubscribing a handler
Given a ReactiveEventBus
When I subscribe to "plan.created" events
Owner

BLOCKING — WRONG TDD ISSUE NUMBER (still not fixed): All 6 new scenarios are still tagged @tdd_issue_10354. Per CONTRIBUTING.md, N must be the bug issue number which is #10356 (not #10354, the TDD testing issue). This tag mismatch is the root cause of the CI unit_tests failure.

Required fix:

  1. Change all @tdd_issue_10354 to @tdd_issue_10356 across all 6 scenarios
  2. Follow the 2-step TDD process: create and merge a tdd/m3-eventbus-unsubscribe PR first with @tdd_expected_fail tags, rebase this branch, then remove @tdd_expected_fail

Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

**BLOCKING — WRONG TDD ISSUE NUMBER (still not fixed)**: All 6 new scenarios are still tagged `@tdd_issue_10354`. Per CONTRIBUTING.md, N must be the **bug issue number** which is `#10356` (not `#10354`, the TDD testing issue). This tag mismatch is the root cause of the CI `unit_tests` failure. Required fix: 1. Change all `@tdd_issue_10354` to `@tdd_issue_10356` across all 6 scenarios 2. Follow the 2-step TDD process: create and merge a `tdd/m3-eventbus-unsubscribe` PR first with `@tdd_expected_fail` tags, rebase this branch, then remove `@tdd_expected_fail` --- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
Owner

Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

--- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
Some checks failed
CI / helm (pull_request) Successful in 58s
CI / lint (pull_request) Successful in 1m22s
Required
Details
CI / build (pull_request) Successful in 1m14s
Required
Details
CI / quality (pull_request) Successful in 1m47s
Required
Details
CI / typecheck (pull_request) Successful in 1m59s
Required
Details
CI / security (pull_request) Successful in 2m2s
Required
Details
CI / benchmark-publish (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 20s
CI / integration_tests (pull_request) Successful in 4m1s
Required
Details
CI / e2e_tests (pull_request) Successful in 4m57s
CI / unit_tests (pull_request) Failing after 5m37s
Required
Details
CI / docker (pull_request) Has been skipped
Required
Details
CI / coverage (pull_request) Successful in 14m46s
Required
Details
CI / status-check (pull_request) Failing after 3s
This pull request has changes conflicting with the target branch.
  • features/event_bus.feature
  • features/steps/event_bus_steps.py
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin bugfix/m3-eventbus-unsubscribe:bugfix/m3-eventbus-unsubscribe
git switch bugfix/m3-eventbus-unsubscribe
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core!10887
No description provided.