Repository navigation
fix(memory): only unlink serve.port files that still hold our own port - #163
Open
KM-IA-Agency wants to merge 2 commits into
Open
KM-IA-Agency wants to merge 2 commits into
KM-IA-Agency wants to merge 2 commits into
Conversation
Several `cce serve` processes can run for the same project (one per agent session, or two agents on one repo). Each start overwrites serve.port and the rendezvous file with its own port, but on graceful exit every server unlinked both files unconditionally. When an older server exited, the newer one kept running with no rendezvous: hooks found no port and silently dropped captured turns until the next server start. The cleanup now removes a port file only when it still contains this server's bound port, and adds a regression test with two servers on one project. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KM-IA-Agency
requested review from
fazleelahhee and
rajkumarsakthivel
as code owners
October 4, 2026 20:49
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Concurrent publication between the ownership check and deletion can still cause cleanup to remove a newer server’s rendezvous.
Review effort: Balanced
Findings: 1
What changed in this PR
Protects memory-hook discovery when multiple cce serve processes share a project.
Changes:
- Records the bound port and checks file ownership before cleanup.
- Adds a two-server regression test for sequential shutdowns.
| File | Description |
|---|---|
tests/memory/test_hook_server_cleanup.py |
Tests preservation of a newer server’s port files. |
src/context_engine/memory/hook_server.py |
Adds ownership checks before deleting port files. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The ownership check and the unlink were not atomic across processes: a peer could publish its port after cleanup read this server's port and before it unlinked the file, losing the peer's rendezvous. Publication and compare-and-delete now run under the same cross-process lock on `<storage_base>/serve.port.lock` (fcntl on POSIX, msvcrt on Windows), bounded to 2 s. On timeout, cleanup leaves the files rather than risk deleting a peer's rendezvous. Adds a regression test that publishes from a peer between the read and the unlink; it fails without the lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
When several
cce serveprocesses run for the same project (one per agent session, or e.g. Claude Code and Codex on the same repo), each start overwritesserve.portand the rendezvous file~/.cce/projects/<name>/serve.portwith its own port. On graceful exit,_unlink_port_filesremoved both files unconditionally.So as soon as an older server exited, the newer one kept running with no rendezvous:
cce_hook.cmd/ the hook script found no port and silently dropped every captured turn until the next server start. In our setup (six concurrentcce serveon one repo), live memory capture was mostly dead for four days without any visible error.Fix
_unlink_port_filesnow removes a port file only when it still contains this server's bound port. The bound port is kept in a closure because the aiohttp app is frozen once the runner is set up.Unchanged: the single-server case (files are still removed on exit, the existing tests pass), tolerance of files already removed, and the storage == rendezvous layout.
Tests
test_cleanup_leaves_port_files_owned_by_another_server: two servers on one project; stopping the first must leave the second's port in both files, and stopping the second removes them.tests/memory/test_hook_server_cleanup.py: 4 passed with the fix; the new test fails without it (1 failed, 3 passed). Run locally on Windows / Python 3.13 with-n0; I have not run the full suite locally.Not covered
SIGKILL still bypasses
on_cleanup, so a killed server leaves a stale file. That is the existing #66 limit, handled by the socket-liveness probe in the hook script.Update: cross-process lock (review feedback)
The ownership check and the unlink were not atomic across processes: a peer could publish between them. Publication (
_publish_port) and the compare-and-delete now share one cross-process lock on<storage_base>/serve.port.lock(fcntl on POSIX, msvcrt on Windows), bounded to 2 s. On timeout, cleanup leaves the files rather than risk deleting a peer's rendezvous.New regression
test_peer_publishing_during_cleanup_keeps_its_rendezvous: a peer publishes between the read and the unlink. It passes with the lock and fails with the lock disabled.tests/memory/test_hook_server_cleanup.py+test_hooks.py: 33 passed locally (Windows / Python 3.13,-n0).🤖 Generated with Claude Code