Skip to content

fix: keep Ableton tools available across MCP clients - #13

Open
HelloThisIsFlo wants to merge 17 commits into
hidingwill:mainfrom
HelloThisIsFlo:fix/mcp-singleton-collision
Open

fix: keep Ableton tools available across MCP clients#13
HelloThisIsFlo wants to merge 17 commits into
hidingwill:mainfrom
HelloThisIsFlo:fix/mcp-singleton-collision

Conversation

@HelloThisIsFlo

@HelloThisIsFlo HelloThisIsFlo commented Jul 15, 2026

Copy link
Copy Markdown

Caution

AI implementation disclosure

This PR's code was written by Codex (GPT-5.6, SOL, extra-high). I designed the intended behaviour, iterated on it, and discussed the architecture and engineering trade-offs with the model. I tested the implementation end to end and it works in my environment. However, I have not personally reviewed the generated code, so please treat it as tested but not human-code-reviewed.

Summary

  • keep every stdio MCP process alive and expose the complete 347-tool surface
  • coordinate exactly one Ableton backend owner with automatic claim, explicit release, and shutdown cleanup
  • report ownership-aware Ableton and M4L status without claiming control or probing another process
  • serialize timeout, cancellation, background work, and handoff so a new owner cannot inherit stale sockets or partially cleaned-up state
  • report best-effort owner metadata and distinguish another AbletonBridge owner from an unknown port occupant

Root cause

Codex and similar clients can start a private AbletonBridge stdio process per task. The previous machine-wide singleton exited every later process when port 9881 was occupied, so those tasks completed without Ableton tools even though Live and the first bridge were healthy.

The original standby status also treated the absence of a local backend as proof that Ableton was disconnected. The status contract now separates ownership, local backend state, and remote health that this process cannot observe.

Design

  • reuse port 9881 as the atomic lock and loopback-only JSON status responder
  • auto-claim for normal tools; keep get_server_capabilities and release_ableton_control claim-free
  • owners return verified connection booleans
  • standbys return null with not_started, owned_elsewhere, or unknown
  • passively validate local Ableton sockets without sending commands or consuming protocol bytes
  • serialize M4L status, reconnect, warmup publication, teardown, and connection generations
  • protect foreground and timed-out operations while treating warmups as cancellable background services
  • bound caller response time across queueing and execution without overlapping an uncancellable worker
  • roll back only the exact new ownership generation created by an abandoned invocation
  • retain ownership whenever cleanup is incomplete
  • keep the existing stdio deployment with no HTTP transport, Codex configuration, Remote Script, or wire-protocol changes
  • use explicit release and shutdown cleanup with no idle timeout, force-steal path, or public claim tool

User impact

Multiple MCP tasks can see Ableton tools simultaneously. One task controls Live, while standby tasks remain healthy, accurately describe what they can observe, and can take over after an explicit release.

Failed Live activation leaves MCP healthy, rolls back ownership, and returns a structured tool error.

Validation

  • uv run --frozen pytest -q
  • 277 tests passed
  • 81 focused connection, ownership, status, and tool-wrapper tests passed
  • subprocess coverage starts two stdio clients and verifies the same 347-tool surface
  • concurrency coverage includes simultaneous claims, release/reclaim, shutdown, startup failure, queued response deadlines, abandoned-claim rollback, timeout, cancellation, dead peers, M4L connection generations, and incomplete cleanup
  • tests use mocks, local socket pairs, and temporary ports; they do not claim the live Ableton instance
  • the complete PR diff passes git diff --check

Summary by CodeRabbit

  • New Features
    • Added single-owner Ableton control coordination across multiple MCP processes, including detailed ownership status in server capabilities.
    • Added release_ableton_control to enable intentional control handoffs.
    • Tools now track control ownership during execution; browser-cache warmup and Ableton commands support cooperative cancellation.
  • Bug Fixes
    • Improved ownership conflict handling with structured errors.
    • Hardened dashboard/server shutdown, timeouts, and background-worker cleanup.
  • Documentation
    • Updated architecture and ownership lifecycle documentation.
  • Tests
    • Expanded ownership, tool-handler concurrency, and shutdown/cancellation coverage.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@HelloThisIsFlo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a7b915e3-ccef-4a48-aa9b-290e8dc017c0

📥 Commits

Reviewing files that changed from the base of the PR and between b067f12 and 209ce67.

📒 Files selected for processing (6)
  • MCP_Server/ownership.py
  • MCP_Server/tools/_base.py
  • README.md
  • docs/ARCHITECTURE.md
  • tests/test_ownership.py
  • tests/test_tool_handler.py
📝 Walkthrough

Walkthrough

The MCP server now coordinates a single Ableton backend owner across processes, manages ownership-aware startup and shutdown, tracks controlled operations, supports cancellable backend work, exposes ownership status and release tools, and updates dashboard lifecycle handling, tests, instructions, and architecture documentation.

Changes

Ownership-backed MCP server

Layer / File(s) Summary
Ownership manager and coordination protocol
MCP_Server/ownership.py
Adds loopback-based ownership claims, standby status probing, lifecycle callbacks, operation tracking, release safeguards, metadata, and cleanup behavior.
Ownership-controlled backend lifecycle
MCP_Server/server.py, MCP_Server/state.py
Wires ownership into server lifespan, starts cancellable backend workers for the owner, and replaces the previous singleton lock state.
Cancellable Ableton commands and cache warmup
MCP_Server/connections/ableton.py, MCP_Server/cache/browser.py, MCP_Server/connections/m4l.py
Propagates shutdown events through Ableton command reception, retry waits, browser scans, and coordinated M4L connection state.
Ownership-aware status and dashboard lifecycle
MCP_Server/status.py, MCP_Server/dashboard/server.py, MCP_Server/state.py
Adds tri-state connection reporting, synchronized M4L status checks, dedicated dashboard event-loop management, bounded shutdown joins, and conditional state cleanup.
Controlled tools, resources, and ownership commands
MCP_Server/tools/*, MCP_Server/server.py
Adds ownership-aware tool execution, structured control errors, capability status, controlled resources, and release_ableton_control.
Ownership validation and project documentation
tests/*, README.md, docs/ARCHITECTURE.md, MCP_Server/instructions.py
Adds ownership, cancellation, shutdown, status, concurrency, and timeout coverage and documents the updated backend ownership model, instructions, and tool counts.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPClient
  participant ToolHandler
  participant OwnershipManager
  participant ControlBackend
  participant AbletonConnection
  MCPClient->>ToolHandler: invoke Ableton tool
  ToolHandler->>OwnershipManager: ensure_control()
  OwnershipManager->>ControlBackend: start backend when ownership is acquired
  ToolHandler->>OwnershipManager: begin_operation()
  ToolHandler->>AbletonConnection: send_command()
  AbletonConnection-->>ToolHandler: command result
  ToolHandler->>OwnershipManager: end_operation()
  ToolHandler-->>MCPClient: result or structured ownership status
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 95.14% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: keeping Ableton tools available across multiple MCP clients via ownership coordination.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

qodo-code-review Bot commented Jul 15, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Keep Ableton tools available across MCP clients via ownership port coordination

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Decouple MCP tool availability from exclusive Ableton backend ownership.
• Add loopback-port ownership with explicit release and safe shutdown cleanup.
• Make status/dashboard ownership-aware with truthful tri-state connection reporting.
Diagram

graph TD
  C{{"MCP client(s)"}} --> S["MCP stdio server"] --> T["Tools & resources"] --> O["OwnershipManager :9881"] --> B["Owner-only backend"] --> A{{"Ableton Remote Script :9877"}}
  B --> M{{"M4L bridge UDP"}}
  B --> D["Dashboard :9880"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. File lock + metadata file (fcntl/portalocker)
  • ➕ Simple mental model: lock file implies owner, adjacent file holds metadata
  • ➕ Avoids consuming a TCP port and potential port collisions
  • ➖ Harder to make metadata atomic with lock acquisition/release across crashes
  • ➖ Platform differences (Windows vs POSIX) and stale-lock cleanup complexity
2. Dedicated local coordinator process (IPC/gRPC)
  • ➕ Centralizes lifecycle/ownership policy; richer status reporting possible
  • ➕ Can support advanced features (force-steal, leases) safely in one place
  • ➖ Adds an extra process/service to deploy and supervise
  • ➖ More moving parts than needed for a local-only single-owner constraint

Recommendation: The PR’s approach—reusing the existing loopback port as both atomic lock and minimal JSON status responder—is a strong fit: it keeps ownership acquisition atomic, requires no new deployment surface, and avoids stale filesystem state. The main review focus should be on race-safety around lifecycle transitions (transition_lock/active_operations), and ensuring all owner-only background work is cooperatively cancellable so a handoff cannot inherit partial state.

Files changed (19) +3720 / -360

Enhancement (4) +851 / -32
ableton.pyAdd passive socket liveness checks and cooperative command cancellation +124/-16

Add passive socket liveness checks and cooperative command cancellation

• Introduces AbletonConnection.is_connected() using non-consuming peeks and send-lock coordination to avoid disturbing in-flight commands. Extends send_command/receive_full_response with stop_event support to cancel work during backend shutdown and to prevent timeouts from blocking release.

MCP_Server/connections/ableton.py

ownership.pyAdd cross-process ownership manager with status responder and safe handoff +575/-0

Add cross-process ownership manager with status responder and safe handoff

• Implements OwnershipManager using an exclusive loopback listener (port 9881) as the atomic lock plus a JSON owner-metadata responder. Adds automatic claim, explicit release, shutdown cleanup, active-operation tracking, and generation-safe rollback for abandoned claims.

MCP_Server/ownership.py

status.pyIntroduce ownership-aware connection status helpers +117/-0

Introduce ownership-aware connection status helpers

• Adds build_connection_status() that returns truthful owner vs standby connection fields (nulls + explicit state for standbys). Provides a guarded get_m4l_status() that serializes with release and connection replacement, and uses passive Ableton socket liveness for owners.

MCP_Server/status.py

session.pyMake capabilities ownership-aware and add explicit release tool +35/-16

Make capabilities ownership-aware and add explicit release tool

• Updates get_server_capabilities to avoid claiming control and to report ownership + tri-state connection fields. Adds release_ableton_control as a claim-free recovery tool that releases only local ownership and returns structured status.

MCP_Server/tools/session.py

Bug fix (5) +599 / -244
browser.pyMake browser cache warmup cancellable and shutdown-safe +39/-7

Make browser cache warmup cancellable and shutdown-safe

• Adds an optional stop_event to cancel live browser scans during ownership release/shutdown. Ensures retries and rate-limits respect cancellation and that a cancelled scan does not overwrite an existing cache.

MCP_Server/cache/browser.py

m4l.pySerialize M4L connection generation and status snapshot updates +66/-39

Serialize M4L connection generation and status snapshot updates

• Wraps get_m4l_connection() in a shared lock and publishes coherent (sockets_ready, connected) snapshots plus ping-cache updates. Ensures reconnect/replace is transactional so status readers and tools don’t observe mixed generations.

MCP_Server/connections/m4l.py

server.pyMake dashboard status ownership-aware and teardown race-safe +68/-45

Make dashboard status ownership-aware and teardown race-safe

• Replaces ad-hoc connection probing with ownership-aware build_connection_status(). Tracks dashboard thread/server lifecycle explicitly and returns a boolean from stop_dashboard_server() to support “retain ownership until cleanup completes”.

MCP_Server/dashboard/server.py

server.pyDecouple MCP server lifespan from backend ownership; start backend on claim +223/-139

Decouple MCP server lifespan from backend ownership; start backend on claim

• Removes the prior process-wide singleton exit-on-port-in-use behavior so multiple stdio MCP processes can stay alive. Introduces owner-only backend start/stop hooks wired into ownership.py, adds cooperative cancellation for background threads, and makes resources use the same ownership/operation contract as tools.

MCP_Server/server.py

_base.pyAdd ownership-aware tool wrapper with bounded deadlines and abandonment handling +203/-14

Add ownership-aware tool wrapper with bounded deadlines and abandonment handling

• Extends the tool decorator to auto-claim ownership for control-required tools, while exempting status/release tools. Adds a response deadline that includes queue time, abandons timed-out/cancelled invocations before their body starts, and prevents released control from being used by queued work; also enriches tool_error() with optional data payloads.

MCP_Server/tools/_base.py

Refactor (1) +6 / -3
state.pyAdd ownership lifecycle state and M4L synchronization primitives +6/-3

Add ownership lifecycle state and M4L synchronization primitives

• Adds m4l_connection_lock and m4l_status_snapshot to keep M4L status coherent across threads. Replaces the old singleton-lock socket state with control_stop_event/control_background_threads and adds dashboard_thread tracking.

MCP_Server/state.py

Tests (6) +2127 / -10
conftest.pyReset new M4L status snapshot and ping cache between tests +8/-4

Reset new M4L status snapshot and ping cache between tests

• Extends the global state reset fixture to restore m4l_ping_cache and m4l_status_snapshot, preventing cross-test leakage with the new status semantics.

tests/conftest.py

test_browser_cache.pyAdd regression coverage for cancellable browser cache live scan +47/-1

Add regression coverage for cancellable browser cache live scan

• Adds a test ensuring populate_browser_cache(stop_event=...) aborts cleanly on cancellation and preserves any existing cache content while performing required cleanup.

tests/test_browser_cache.py

test_connections.pyAdd coverage for Ableton cancellation and passive liveness behavior +120/-5

Add coverage for Ableton cancellation and passive liveness behavior

• Adds tests for stop_event-driven cancellation in receive_full_response/send_command retry delays and for non-consuming socket liveness checks (including busy-send-lock behavior). Updates get_ableton_connection tests to use is_connected() and to assert ableton_connected_event is signaled.

tests/test_connections.py

test_ownership.pyAdd comprehensive OwnershipManager concurrency and lifecycle test suite +1045/-0

Add comprehensive OwnershipManager concurrency and lifecycle test suite

• Introduces extensive tests covering single-winner claims, standby owner metadata, explicit release enabling next owner, shutdown cleanup, generation-safe rollback, and occupied-unknown behavior. Provides the primary regression coverage for multi-process MCP availability expectations.

tests/test_ownership.py

test_status.pyAdd tests for ownership-aware status and M4L serialization guarantees +416/-0

Add tests for ownership-aware status and M4L serialization guarantees

• Adds tests ensuring standbys never probe local sockets, owners report verified booleans, and M4L status pings serialize with release and connection replacement. Covers snapshot fallback when operation leases cannot be acquired.

tests/test_status.py

test_tool_handler.pyExpand tool handler tests for control exemptions, deadlines, and rollback +491/-0

Expand tool handler tests for control exemptions, deadlines, and rollback

• Adds tests ensuring requires_control=False tools never trigger ownership claims, best-effort client metadata does not break control tools, and timed-out/cancelled calls are abandoned safely without corrupting ownership state. Covers structured error payloads and semaphore/operation interactions.

tests/test_tool_handler.py

Documentation (3) +137 / -71
instructions.pyUpdate server instructions for ownership + tri-state connection status +5/-3

Update server instructions for ownership + tri-state connection status

• Updates startup guidance to explain control_role/control_availability, null connection booleans for standbys, and explicit release via release_ableton_control. Updates reported tool count to 347.

MCP_Server/instructions.py

README.mdDocument multi-client ownership behavior and updated tool counts +43/-27

Document multi-client ownership behavior and updated tool counts

• Adds a “Multiple MCP Clients” section describing owner/standby semantics, structured ownership errors, and explicit release behavior. Updates documented tool counts and architecture module list to include ownership.py/status.py.

README.md

ARCHITECTURE.mdDocument ownership lifecycle, status contract, and module import layering +89/-41

Document ownership lifecycle, status contract, and module import layering

• Expands architecture docs to describe the single-owner backend model, ownership port status responder, tri-state connection reporting, and lifecycle guarantees around release/shutdown. Updates module map/import levels to include ownership.py and status.py and reflects tool module count changes.

docs/ARCHITECTURE.md

@qodo-code-review

qodo-code-review Bot commented Jul 15, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Dashboard join race ✓ Resolved 🐞 Bug ☼ Reliability
Description
Threads (the dashboard thread and owner-only control backend background threads) are published into
shared state before thread.start() is called, while teardown later blindly join()s those
references. If forced shutdown/ownership release overlaps startup, join() can raise `RuntimeError:
cannot join thread before it is started`, aborting the rest of backend cleanup and potentially
leaving owner resources partially torn down.
Code

MCP_Server/dashboard/server.py[R187-200]

thread = threading.Thread(target=_run, daemon=True, name="dashboard-http")
+    state.dashboard_thread = thread
thread.start()
logger.info("Dashboard started at http://127.0.0.1:%d", state.DASHBOARD_PORT)
def stop_dashboard_server():
"""Signal the dashboard server to shut down."""
-    if state.dashboard_server:
-        state.dashboard_server.should_exit = True
+    server = state.dashboard_server
+    thread = state.dashboard_thread
+    if server:
+        server.should_exit = True
+    if thread and thread is not threading.current_thread():
+        thread.join(timeout=3.0)
Evidence
The cited code paths assign or append newly created Thread objects into shared state
(state.dashboard_thread and state.control_background_threads) prior to starting them, and the
corresponding shutdown routines (stop_dashboard_server() and _stop_control_backend()) later
retrieve those shared references and call join() without checking whether the threads were ever
started. This creates a race window where teardown can run after publication but before start(),
and Python’s Thread.join() raises RuntimeError when invoked on an unstarted thread, which would
interrupt the remaining teardown/cleanup logic.

MCP_Server/dashboard/server.py[187-206]
MCP_Server/server.py[200-207]
MCP_Server/server.py[170-226]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Thread lifecycle state is published too early: the dashboard thread and control-backend background threads are stored in shared state before `thread.start()` is called, but teardown later unconditionally `join()`s them. During forced shutdown/ownership release that overlaps startup, this can trigger `RuntimeError: cannot join thread before it is started` and interrupt backend cleanup.
## Issue Context
This race can happen in the narrow window between creating/publishing a `Thread` and calling `start()`, especially during forced ownership release or process shutdown while the owner backend (dashboard/control backend) is still starting. Teardown should be best-effort and exception-safe so that other shutdown steps still run even if a thread is mid-transition.
## Fix Focus Areas
- MCP_Server/dashboard/server.py[175-206]
- MCP_Server/server.py[170-226]
## Suggested fix
- Publish threads to shared state only after `thread.start()` succeeds (e.g., start first, then assign/append), or if publication must happen before start, make teardown robust to the unstarted state.
- In `stop_dashboard_server()` and `_stop_control_backend()`, guard joins by checking start-state (e.g., `thread.ident is not None`) and/or wrap `thread.join(...)` in `try/except RuntimeError`.
- Ensure shutdown/teardown continues performing remaining cleanup even if one thread cannot be joined (best-effort cleanup without aborting subsequent steps).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread MCP_Server/dashboard/server.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (1)
MCP_Server/tools/_base.py (1)

113-118: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider logging swallowed background exceptions.

_consume_background_result silently discards any exception (including _ControlReleasedError or a real backend error) from a task that outlived its timeout. Since the client already received a timeout response, this is the only place such failures could ever surface for debugging.

♻️ Suggested tweak
 def _consume_background_result(task: asyncio.Task) -> None:
     """Retrieve a timed-out task's result so late exceptions are not leaked."""
     try:
-        task.exception()
+        exc = task.exception()
+        if exc is not None:
+            logger.warning("Timed-out tool task finished with error: %s", exc)
     except (asyncio.CancelledError, Exception):
         pass
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MCP_Server/tools/_base.py` around lines 113 - 118, Update
_consume_background_result to log exceptions retrieved from the timed-out task
instead of silently swallowing them, while continuing to ignore normal
cancellation and preserving the existing late-result consumption behavior. Use
the module’s established logging mechanism and include the exception details for
debugging.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@MCP_Server/dashboard/server.py`:
- Around line 199-204: Update the dashboard shutdown logic around
state.dashboard_server and state.dashboard_thread so references are cleared only
after the thread has exited. After thread.join(timeout=3.0), check
thread.is_alive(); if it remains alive, report the incomplete shutdown and
retain both state references, otherwise perform the existing cleanup.

In `@MCP_Server/ownership.py`:
- Around line 151-158: Update the claim startup flow around the lock-protected
transition that sets self._phase to "owner" and the corresponding cleanup in
shutdown() so a forced shutdown cannot race an in-progress start_backend().
Serialize startup and shutdown lifecycle transitions, or revalidate ownership
and listener state after start_backend() before publishing "owner"; if startup
was cancelled, clean up the backend resources and return a failed ClaimResult.
Apply the same protection to the related transition at the noted second
location.
- Around line 184-195: Update MCP_Server/ownership.py lines 184-195 so ownership
is released only after every owner-only cleanup succeeds; on cleanup failure,
return ReleaseResult with released=False and retain local ownership. In
MCP_Server/server.py lines 150-151, make the live cache scan cooperatively
cancellable or wait for it to finish. In MCP_Server/server.py lines 218-223,
retain live thread references and report shutdown timeouts. In
MCP_Server/dashboard/server.py lines 199-204, clear dashboard references only
after confirmed thread termination.
- Around line 306-316: Update the payload validation in the status-reading flow
before any payload.get calls to require that payload is a JSON
object/dictionary. Return self._standby_status("occupied_unknown") for valid
non-object JSON values such as lists, while preserving the existing validation
for service, protocol, and owner.

In `@MCP_Server/server.py`:
- Around line 150-151: Update the live browser scan flow around
populate_browser_cache() to observe stop_event after the scan begins, including
during its socket waits and sleeps. Propagate the shutdown signal through the
cache-population logic so owner-only work stops promptly after handoff, while
preserving normal scan behavior when stop_event remains unset.

In `@MCP_Server/tools/_base.py`:
- Around line 91-93: Update the _ControlReleasedError handler to obtain
ownership status through asyncio.to_thread rather than calling
ownership.get_status() directly, preserving the existing tool_error response and
status payload while preventing blocking remote-owner probing on the event loop.
- Around line 47-77: Update the tool wrapper so claim-free operations do not
acquire the Ableton semaphore, allowing get_server_capabilities and
release_ableton_control to remain usable during ownership recovery. Also bound
the ownership.ensure_control call with _TOOL_TIMEOUT_SECONDS and handle timeout
consistently, while preserving existing claim error responses for completed
attempts.

In `@tests/test_ownership.py`:
- Around line 176-190: Move the cleanup scope in the ownership test to begin
immediately after creating the owner via _configured_manager, enclosing the
ensure_control assertion and all subsequent setup. Ensure owner.shutdown()
remains in the corresponding finally block so it runs when the assertion fails
or raises, while preserving the existing test behavior.

---

Nitpick comments:
In `@MCP_Server/tools/_base.py`:
- Around line 113-118: Update _consume_background_result to log exceptions
retrieved from the timed-out task instead of silently swallowing them, while
continuing to ignore normal cancellation and preserving the existing late-result
consumption behavior. Use the module’s established logging mechanism and include
the exception details for debugging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d834b24f-dfd1-42d0-b819-e6f81276fb8e

📥 Commits

Reviewing files that changed from the base of the PR and between 01c31c4 and 1b405eb.

📒 Files selected for processing (11)
  • MCP_Server/dashboard/server.py
  • MCP_Server/instructions.py
  • MCP_Server/ownership.py
  • MCP_Server/server.py
  • MCP_Server/state.py
  • MCP_Server/tools/_base.py
  • MCP_Server/tools/session.py
  • README.md
  • docs/ARCHITECTURE.md
  • tests/test_ownership.py
  • tests/test_tool_handler.py

Comment thread MCP_Server/dashboard/server.py Outdated
Comment thread MCP_Server/ownership.py Outdated
Comment thread MCP_Server/ownership.py Outdated
Comment thread MCP_Server/ownership.py
Comment thread MCP_Server/server.py Outdated
Comment thread MCP_Server/tools/_base.py Outdated
Comment thread MCP_Server/tools/_base.py
Comment thread tests/test_ownership.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@MCP_Server/connections/ableton.py`:
- Around line 143-148: Update the CommandCancelled raises in the socket timeout
shutdown path and the retry-delay cancellation path to explicitly suppress
exception chaining with a direct-from-None raise. Preserve the existing
cancellation message and stop_event behavior in both locations.

In `@MCP_Server/ownership.py`:
- Around line 208-220: Update shutdown cleanup after _stop_backend_once()
succeeds to recheck _active_operations under _lock before calling
_close_local_ownership(). If timed-out _run_sync_tool workers remain active,
keep ownership and the listener, set _phase to "cleanup_failed", and return a
failed ReleaseResult with the existing retry-style status; only release local
ownership once no active operations remain.

In `@MCP_Server/tools/_base.py`:
- Around line 57-94: The controlled-tool flow around invoke and
_ableton_semaphore currently releases the semaphore when the caller times out or
is cancelled, even though shielded claim_task or task continues running. Keep
the semaphore leased until the underlying work completes by moving semaphore
release responsibility to a completion callback for timeout and
caller-cancellation paths, while returning or propagating the
timeout/cancellation immediately; apply this consistently to both claim and tool
execution tasks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bdf6bc73-226a-4d0e-b5f5-7ddfe0974f81

📥 Commits

Reviewing files that changed from the base of the PR and between 1b405eb and 5ded56f.

📒 Files selected for processing (12)
  • MCP_Server/cache/browser.py
  • MCP_Server/connections/ableton.py
  • MCP_Server/dashboard/server.py
  • MCP_Server/ownership.py
  • MCP_Server/server.py
  • MCP_Server/tools/_base.py
  • README.md
  • docs/ARCHITECTURE.md
  • tests/test_browser_cache.py
  • tests/test_connections.py
  • tests/test_ownership.py
  • tests/test_tool_handler.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • MCP_Server/dashboard/server.py
  • docs/ARCHITECTURE.md
  • README.md

Comment thread MCP_Server/connections/ableton.py Outdated
Comment thread MCP_Server/ownership.py
Comment thread MCP_Server/tools/_base.py Outdated
@HelloThisIsFlo

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@HelloThisIsFlo

Copy link
Copy Markdown
Author

This was very fun to watch btw. Seeing your agents work giving feedback to my agents. 😂

@hidingwill The last commit didn't trigger a new review, I tried triggering it manually with @ coderabbitai review, but it seems it didn't do much. It's my first time seeing coderabbit so I'm not sure how if no comments means pass, or just didn't run.

Either way. Thanks for this amazing project!

@HelloThisIsFlo

Copy link
Copy Markdown
Author

Quick heads-up: testing uncovered an existing issue where get_server_capabilities can report ableton_connected: false, even though the next tool call connects successfully. The underlying status and reconnect behaviour predates this PR, but the new standby ownership flow makes it more apparent.

I’m investigating a fix. If it’s small, I’ll include it in this PR; otherwise I’ll open a follow-up.

@HelloThisIsFlo
HelloThisIsFlo marked this pull request as draft July 19, 2026 19:11
@HelloThisIsFlo

HelloThisIsFlo commented Jul 20, 2026

Copy link
Copy Markdown
Author

Caution

AI-authored update

This update was written by Codex 5.6 Sol Ultra. I directed the intended behaviour, discussed the architecture and engineering trade-offs, and tested the result end to end. I have not personally reviewed the generated code line by line.

Update: the status fix and review hardening are included

My previous comment said I was investigating the misleading ableton_connected: false report. That fix is now included in this PR.

Since the previously published head (c97e352), I added 12 commits. This is not 12 separate features. It is one ownership-aware status correction plus review-driven hardening needed to keep the new ownership lifecycle safe under timeout, cancellation, release, reconnect, and shutdown races. I kept the fixes as small semantic commits rather than hiding them in one large squash.

What each commit does

  1. 3d338b3 report ownership-aware connection status

    • Standbys now return null plus not_started, owned_elsewhere, or unknown, instead of falsely claiming Ableton is disconnected.
    • The same contract is used by the status tool, resource, dashboard, and features.m4l_bridge.
  2. 4a9b457 allow release during backend warmup

    • Browser and M4L warmups are cancellable background services rather than foreground operations that can block handoff for minutes.
    • Release signals and joins them, retaining ownership if a worker refuses to stop.
  3. 1f922ad suppress abandoned control calls

    • A call that times out or is cancelled before its synchronous body starts is prevented from running later.
    • Underlying work that has already started remains serialized until it genuinely finishes.
  4. 0cc7dfb serialize M4L status with release

    • A live M4L status ping holds an ownership operation lease so release cannot tear down or reconnect its sockets underneath it.
  5. 5fd6987 preserve passive socket liveness

    • Ableton status passively detects cleanly closed peers using non-consuming socket inspection.
    • If a command owns the response lock, status preserves the last verified result rather than interfering with protocol data.
  6. 39f7100 make owner handoff transactional

    • Responder, dashboard, and background-thread startup failures roll back cleanly.
    • Ownership is not published or released across a partially initialized lifecycle.
  7. 6ee6f58 complete ownership status guarantees

    • README and architecture documentation now describe tri-state status, passive liveness, failed activation, operation protection, and cancellable background cleanup.
  8. a5b9c98 close ownership operation races

    • Operations can only register while the manager is fully in the owner phase.
    • Failed startup and release retain ownership if a real operation is still active.
  9. 9b62249 serialize M4L connection generations

    • Status, reconnect, warmup publication, and teardown share one generation-safe state lock and immutable snapshot.
    • This prevents stale M4L objects, cross-generation cached health, and leaked UDP sockets during handoff.
  10. 1a5e90a signal reused Ableton connections

    • A reused healthy connection now raises the same readiness event as a new one, so browser warmup does not wait unnecessarily.
  11. b067f12 keep client metadata best effort

    • An unavailable FastMCP request context can no longer make a real control tool fail while the server is only trying to record an optional client name.
  12. 209ce67 bound abandoned control requests

    • A queued caller now shares the same response deadline instead of waiting indefinitely behind a genuinely stuck worker.
    • An already-running worker still keeps the semaphore until it exits; releasing it early would allow overlapping use of the shared request-response socket.
    • If a timeout or cancellation wins before the tool body starts, only the exact new ownership generation created by that invocation is rolled back. Pre-existing or newer ownership is preserved.
    • The README and architecture guide now describe this accurately as a caller response deadline rather than cancellable thread execution.

Scope boundaries

The update does not add a public claim tool, idle timeout, force-steal behaviour, HTTP transport, Codex configuration change, Remote Script change, or wire-protocol change. Failed Live activation still rolls back ownership and returns a structured tool error.

Validation

  • 277 tests pass in the complete suite.
  • 81 focused connection, ownership, status, and tool-wrapper tests pass.
  • Four focused regressions verify bounded queueing, automatic abandoned-claim rollback, generation-safe cleanup, and retained serialization.
  • Subprocess tests cover two simultaneous stdio clients and the complete 347-tool surface.
  • Concurrency tests cover release, shutdown, timeout, cancellation, dead peers, M4L connection generations, and incomplete cleanup.
  • Automated tests use mocks, local socket pairs, and temporary ports. They do not claim the live Ableton instance.
  • The complete PR diff passes git diff --check.

All 15 inline review threads are now resolved. CodeRabbit passed a fresh review on final head 209ce67. Qodo's final review raised two additional points: the proposed owner: null race is prevented by the responder and claim path sharing the same lock, while the bounded M4L warmup wait is intentional generation serialization that prevents response stealing, stale cache publication, socket replacement, and teardown races. Both were answered with mechanism-level evidence and required no further code change. The PR is ready for maintainer review.

@HelloThisIsFlo
HelloThisIsFlo marked this pull request as ready for review July 20, 2026 13:54
@qodo-code-review

qodo-code-review Bot commented Jul 20, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Owner probe misclassified ✗ Dismissed 🐞 Bug ☼ Reliability ⭐ New
Description
OwnershipManager._probe_remote_owner() rejects status payloads unless "owner" is a dict; during
ensure_control() startup, the responder can briefly serve "owner": null before _owner is assigned,
so standby processes can temporarily report control_availability="occupied_unknown" instead of
"owned" and show misleading ownership errors.
Code

MCP_Server/ownership.py[R403-409]

+        if (
+            not isinstance(payload, dict)
+            or payload.get("service") != "AbletonBridge"
+            or payload.get("protocol") != _STATUS_PROTOCOL_VERSION
+            or not isinstance(payload.get("owner"), dict)
+        ):
+            return self._standby_status("occupied_unknown")
Evidence
The responder can emit an AbletonBridge payload with owner=null, and the remote probe explicitly
rejects such payloads and reports occupied_unknown. ensure_control() starts the responder before
setting self._owner, creating the narrow race window where a standby probe sees owner=null and
misclassifies the occupant.

MCP_Server/ownership.py[147-170]
MCP_Server/ownership.py[359-368]
MCP_Server/ownership.py[403-416]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Standby instances rely on `_probe_remote_owner()` to distinguish another AbletonBridge owner from an unrelated port occupant. The current validation treats any payload with `owner: null` as `occupied_unknown`. But the responder can legally emit `owner: null` during the short window after the responder thread starts and before `_owner` is assigned, causing transient misclassification.

### Issue Context
- The responder sends `"owner": dict(self._owner) if self._owner else None`.
- `ensure_control()` starts the responder thread before assigning `self._owner`, so a standby probing during that interval will receive a valid AbletonBridge payload with `owner: null`.
- `_probe_remote_owner()` currently requires `owner` to be a dict and otherwise returns `occupied_unknown`.

### Fix Focus Areas
- MCP_Server/ownership.py[147-170]
- MCP_Server/ownership.py[359-416]

### Suggested fix
1) Make `_probe_remote_owner()` accept payloads where `service` and `protocol` match even if `owner` is `None` (return `control_availability: "owned"`, `owner: None`).
2) Additionally (or instead), eliminate the startup race by assigning `self._owner = owner` before starting the responder thread (while still ensuring the responder is running before exposing the bound listener as “published” ownership).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. M4L lock blocks tools ✗ Dismissed 🐞 Bug ➹ Performance ⭐ New
Description
_m4l_auto_connect() holds state.m4l_connection_lock while blocking on recvfrom() (timeout 2s), which
can stall any M4L tool calling get_m4l_connection() that also takes the same lock, increasing
latency and risking avoidable tool timeouts under repeated warmup attempts.
Code

MCP_Server/server.py[R94-107]

+            with state.m4l_connection_lock:
+                if stop_event.is_set() or state.m4l_connection is not conn:
+                    return
+                with conn._send_lock:
+                    # Drain stale data
+                    conn._drain_recv_socket()
+                    conn.recv_sock.settimeout(2.0)
+
+                    # Send ping
+                    conn.send_sock.sendto(ping_osc, (conn.send_host, conn.send_port))
+
+                    # Wait for response
+                    data, _addr = conn.recv_sock.recvfrom(65535)
+                    result = conn._parse_m4l_response(data)
Evidence
The PR adds a global M4L connection lock and then holds it across a 2-second blocking recvfrom in
the warmup loop. get_m4l_connection uses the same lock, and tools invoke get_m4l_connection, so
warmup can delay tool execution.

MCP_Server/server.py[90-107]
MCP_Server/connections/m4l.py[669-739]
MCP_Server/tools/m4l_tools.py[18-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The M4L warmup thread currently holds the global `state.m4l_connection_lock` while waiting for a UDP response (`recvfrom()` with a 2s timeout). Any tool that calls `get_m4l_connection()` must acquire the same lock, so tools can be blocked behind warmup network waits.

### Issue Context
- `_m4l_auto_connect()` wraps `recvfrom()` inside `with state.m4l_connection_lock:`.
- `get_m4l_connection()` also uses `with state.m4l_connection_lock:` for all M4L tool calls.

### Fix Focus Areas
- MCP_Server/server.py[90-107]
- MCP_Server/connections/m4l.py[669-739]

### Suggested fix
Refactor `_m4l_auto_connect()` so `state.m4l_connection_lock` is held only to read/verify/publish shared state (e.g., confirming `state.m4l_connection is conn` and updating `state.m4l_ping_cache` / `state.m4l_status_snapshot`). Do the blocking send/recv under `conn._send_lock` (or another per-connection lock) *without* holding `state.m4l_connection_lock`, then reacquire the state lock to publish results if `state.m4l_connection is still conn`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Timeout no longer unblocks tools ✓ Resolved 🐞 Bug ☼ Reliability
Description
On timeout or cancellation, _tool_handler() returns an error while intentionally allowing the
shielded background invoke() task to continue and deferring _ableton_semaphore release until
that task completes; if the worker thread hangs, control-required tools can be blocked indefinitely,
contradicting the stated intent that timeouts prevent stuck tools from blocking the semaphore
forever. Because invoke() can still call ownership.ensure_control() after the client has
abandoned the request, the process may also acquire/hold Ableton control unnecessarily, blocking
other MCP processes until an explicit release or shutdown.
Code

MCP_Server/tools/_base.py[R92-126]

+            semaphore = None
+            if requires_control:
+                # Acquiring remains caller-cancellable. Once acquired, the
+                # lease follows the real shielded work rather than the caller.
+                semaphore = _ableton_semaphore
+                await semaphore.acquire()
+
+            task = asyncio.create_task(invoke())
+            release_deferred = False
+            try:
+                result = await asyncio.wait_for(
+                    asyncio.shield(task),
+                    timeout=_TOOL_TIMEOUT_SECONDS,
+                )
                if isinstance(result, str):
                    stripped = result.strip()
                    if stripped.startswith(("{", "[")):
                        return result  # already structured JSON
                    return tool_success(result)
                return result
            except asyncio.TimeoutError:
+                # Worker threads cannot be cancelled. Keep the semaphore
+                # leased until the actual claim/tool work finishes.
+                invocation.abandon()
+                task.add_done_callback(_consume_background_result)
+                if semaphore is not None:
+                    task.add_done_callback(
+                        functools.partial(
+                            _release_ableton_semaphore,
+                            semaphore=semaphore,
+                        )
+                    )
+                    release_deferred = True
                logger.error("Tool timed out after %ds: %s", _TOOL_TIMEOUT_SECONDS, error_prefix)
                return tool_error(f"Tool timed out after {_TOOL_TIMEOUT_SECONDS}s: {error_prefix}")
Evidence
The cited code path creates a shielded invoke() task and, when TimeoutError/CancelledError
occurs, marks the invocation as abandoned but does not stop the task; instead it attaches a
done-callback and defers releasing _ableton_semaphore until the background work finishes. Since
the task continues running, if the underlying synchronous work never completes the semaphore never
gets released despite comments suggesting the timeout prevents indefinite blocking. Additionally,
invoke() performs ownership.ensure_control() before the abandonment gate is checked, so even
after the client times out the background task can still successfully claim ownership, causing this
process to retain control and potentially block other MCP processes.

MCP_Server/tools/_base.py[17-19]
MCP_Server/tools/_base.py[92-126]
MCP_Server/tools/_base.py[69-90]
MCP_Server/tools/_base.py[99-138]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`_tool_handler()` behaves like it has a hard timeout, but on timeout/cancellation it leaves the shielded `invoke()` task running and defers `_ableton_semaphore` release until that task completes. This means a hung worker can block all subsequent control-required tools in-process indefinitely, and a late-running `invoke()` can still call `ownership.ensure_control()` and acquire/hold Ableton control even though the client abandoned the request.

## Issue Context
- The current design may be intentional to avoid concurrent use of shared backend resources (e.g., shared sockets), but then the timeout is effectively only a *client response timeout*, not an *execution timeout*; the contract/comments should reflect that or a safe recovery mechanism should exist.
- `_InvocationGate` prevents the sync tool body from starting, but it does not prevent/rollback a late successful ownership claim.
- Unconditionally releasing ownership on abandonment is unsafe if the process was already the owner for other legitimate work; any fix must distinguish ownership newly acquired by this invocation vs preexisting ownership.

## Fix Focus Areas
- MCP_Server/tools/_base.py[12-19]
- MCP_Server/tools/_base.py[69-138]
- MCP_Server/ownership.py[31-47]
- MCP_Server/ownership.py[101-197]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Previous review results

Review updated until commit 209ce67

Results up to commit b067f12 ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Remediation recommended
1. Timeout no longer unblocks tools ✓ Resolved 🐞 Bug ☼ Reliability
Description
On timeout or cancellation, _tool_handler() returns an error while intentionally allowing the
shielded background invoke() task to continue and deferring _ableton_semaphore release until
that task completes; if the worker thread hangs, control-required tools can be blocked indefinitely,
contradicting the stated intent that timeouts prevent stuck tools from blocking the semaphore
forever. Because invoke() can still call ownership.ensure_control() after the client has
abandoned the request, the process may also acquire/hold Ableton control unnecessarily, blocking
other MCP processes until an explicit release or shutdown.
Code

MCP_Server/tools/_base.py[R92-126]

+            semaphore = None
+            if requires_control:
+                # Acquiring remains caller-cancellable. Once acquired, the
+                # lease follows the real shielded work rather than the caller.
+                semaphore = _ableton_semaphore
+                await semaphore.acquire()
+
+            task = asyncio.create_task(invoke())
+            release_deferred = False
+            try:
+                result = await asyncio.wait_for(
+                    asyncio.shield(task),
+                    timeout=_TOOL_TIMEOUT_SECONDS,
+                )
                if isinstance(result, str):
                    stripped = result.strip()
                    if stripped.startswith(("{", "[")):
                        return result  # already structured JSON
                    return tool_success(result)
                return result
            except asyncio.TimeoutError:
+                # Worker threads cannot be cancelled. Keep the semaphore
+                # leased until the actual claim/tool work finishes.
+                invocation.abandon()
+                task.add_done_callback(_consume_background_result)
+                if semaphore is not None:
+                    task.add_done_callback(
+                        functools.partial(
+                            _release_ableton_semaphore,
+                            semaphore=semaphore,
+                        )
+                    )
+                    release_deferred = True
                logger.error("Tool timed out after %ds: %s", _TOOL_TIMEOUT_SECONDS, error_prefix)
                return tool_error(f"Tool timed out after {_TOOL_TIMEOUT_SECONDS}s: {error_prefix}")
Evidence
The cited code path creates a shielded invoke() task and, when TimeoutError/CancelledError
occurs, marks the invocation as abandoned but does not stop the task; instead it attaches a
done-callback and defers releasing _ableton_semaphore until the background work finishes. Since
the task continues running, if the underlying synchronous work never completes the semaphore never
gets released despite comments suggesting the timeout prevents indefinite blocking. Additionally,
invoke() performs ownership.ensure_control() before the abandonment gate is checked, so even
after the client times out the background task can still successfully claim ownership, causing this
process to retain control and potentially block other MCP processes.

MCP_Server/tools/_base.py[17-19]
MCP_Server/tools/_base.py[92-126]
MCP_Server/tools/_base.py[69-90]
MCP_Server/tools/_base.py[99-138]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`_tool_handler()` behaves like it has a hard timeout, but on timeout/cancellation it leaves the shielded `invoke()` task running and defers `_ableton_semaphore` release until that task completes. This means a hung worker can block all subsequent control-required tools in-process indefinitely, and a late-running `invoke()` can still call `ownership.ensure_control()` and acquire/hold Ableton control even though the client abandoned the request.

## Issue Context
- The current design may be intentional to avoid concurrent use of shared backend resources (e.g., shared sockets), but then the timeout is effectively only a *client response timeout*, not an *execution timeout*; the contract/comments should reflect that or a safe recovery mechanism should exist.
- `_InvocationGate` prevents the sync tool body from starting, but it does not prevent/rollback a late successful ownership claim.
- Unconditionally releasing ownership on abandonment is unsafe if the process was already the owner for other legitimate work; any fix must distinguish ownership newly acquired by this invocation vs preexisting ownership.

## Fix Focus Areas
- MCP_Server/tools/_base.py[12-19]
- MCP_Server/tools/_base.py[69-138]
- MCP_Server/ownership.py[31-47]
- MCP_Server/ownership.py[101-197]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

Comment thread MCP_Server/tools/_base.py Outdated
@HelloThisIsFlo
HelloThisIsFlo marked this pull request as draft July 20, 2026 14:13
@HelloThisIsFlo
HelloThisIsFlo marked this pull request as ready for review July 20, 2026 14:30
@HelloThisIsFlo

HelloThisIsFlo commented Jul 20, 2026

Copy link
Copy Markdown
Author

/review

Comment thread MCP_Server/ownership.py
Comment thread MCP_Server/server.py
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 209ce67

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.

1 participant