Skip to content

fix(memory): only unlink serve.port files that still hold our own port - #163

Open
KM-IA-Agency wants to merge 2 commits into
elara-labs:mainfrom
KML-C:fix/serve-port-unlink-own-only
Open

KM-IA-Agency wants to merge 2 commits into
elara-labs:mainfrom
KML-C:fix/serve-port-unlink-own-only

Conversation

@KM-IA-Agency

@KM-IA-Agency KM-IA-Agency commented Oct 4, 2026 •

Copy link
Copy Markdown

Problem

When several cce serve processes run for the same project (one per agent session, or e.g. Claude Code and Codex on the same repo), each start overwrites serve.port and the rendezvous file ~/.cce/projects/<name>/serve.port with its own port. On graceful exit, _unlink_port_files removed 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 concurrent cce serve on one repo), live memory capture was mostly dead for four days without any visible error.

Fix

_unlink_port_files now 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

  • New 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

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>
Copilot AI balanced review requested due to automatic review settings October 4, 2026 20:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 Medium severity

Open (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.

Comment thread src/context_engine/memory/hook_server.py Outdated
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

No deployments
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.

2 participants