Skip to content

fix(cli): silent killers — viewer port auto-bump, engine version-match warning, adopt-on-attach, stop --force, npx PATH hint#405

Merged
rohitg00 merged 5 commits into
mainfrom
feat/cli-silent-killers
May 15, 2026
Merged

fix(cli): silent killers — viewer port auto-bump, engine version-match warning, adopt-on-attach, stop --force, npx PATH hint#405
rohitg00 merged 5 commits into
mainfrom
feat/cli-silent-killers

Conversation

@rohitg00
Copy link
Copy Markdown
Owner

@rohitg00 rohitg00 commented May 15, 2026

Five small DevEx fixes surfaced during v0.9.14 local testing. Each one closes a path where the CLI silently fails or refuses to act with no escape hatch.

Fixes

  1. Viewer port auto-bumpsrc/viewer/server.ts:61-148
    Used to log Viewer port 3113 already in use, skipping viewer. and silently exit. Now retries up to 10 increments (3113 -> 3122) before giving up. On fallback, logs Viewer started on http://localhost:3114 (fallback from 3113). Only fail-loud after all retries.

  2. Engine version-match warningsrc/cli.ts:245-258 (helper) + wired at src/cli.ts:503 (startIiiBin) and src/cli.ts:699-702 (main() attach path).
    New helper warnIfEngineVersionMismatch(iiiBinPath) calls iiiBinVersion() and prints a p.log.warn line when the version differs from IIPINNED_VERSION (currently 0.11.2). The message tells the operator how to silence it (AGENTMEMORY_III_VERSION) and how to downgrade (curl | tar). Fires once per process.

  3. stop --forcesrc/cli.ts:1544 (flag parse) + src/cli.ts:1583-1592 (guard bypass) + src/cli.ts:90-93 (--help).
    runStop() parses --force. When true AND the Docker-heuristic guard would have fired (state missing + compose discoverable), bypasses the guard and proceeds with native pidfile + lsof. Documented in --help. The non-force path remains the default and still refuses.

  4. Adopt-on-attachsrc/cli.ts:365-389 (helper) + src/cli.ts:702 (wire-in).
    When main() finds isEngineRunning() already true, new adoptRunningEngine() writes the engine PID (from findEnginePidsByPort) to ~/.agentmemory/iii.pid and writes {kind:'native', configPath, attached:true} to ~/.agentmemory/engine-state.json. Idempotent — never clobbers existing files. Logs Attached to existing iii-engine (pid 12345). This lets a subsequent agentmemory stop actually find the process without --force.

  5. npx PATH hintsrc/cli.ts:336-364 (helpers) + src/cli.ts:703,772 (wire-in).
    After engine is ready, detects npx via npm_lifecycle_event, _npx in process.argv[1], or npm_config_user_agent starting with npm/. Emits one line:
    ```
    Tip: install globally for the bare `agentmemory` command:
    npm install -g @agentmemory/agentmemory
    ```
    Suppressed when ~/.agentmemory/preferences.json has skipNpxHint: true.

Verification

  • npm run build clean
  • npm test: 11 failed | 892 passed (baseline unchanged — all 11 pre-existing failures in test/mcp-standalone.test.ts)
  • No new dependencies
  • Only src/cli.ts and src/viewer/server.ts touched
  • No CHANGELOG.md / version bump

Test plan

  • agentmemory stop on an engine started by a previous shell (without state file) -> still refuses
  • agentmemory stop --force on the same -> proceeds with native pidfile + lsof
  • Start agentmemory while another process holds :3113 -> viewer comes up on :3114 with fallback message
  • Install iii v0.11.6 on PATH, run agentmemory -> see version mismatch warning
  • Run npx @agentmemory/agentmemory -> see one-line install-globally tip after engine ready
  • Start engine in shell A, run agentmemory in shell B -> pidfile + state file written, attach message logged

Summary by CodeRabbit

  • New Features
    • Added stop --force mode to force engine stop while bypassing engine state protection checks
    • Engine version mismatch detection automatically warns users with helpful download guidance
    • Npx invocation detection offers users an optional prompt to install the tool globally
    • Viewer server automatically retries alternate ports when the primary port is unavailable or in use

Review Change Stack

rohitg00 added 5 commits May 15, 2026 15:51
The viewer used to log 'Viewer port 3113 already in use, skipping viewer.'
and silently fail. On every fresh terminal you'd lose the viewer for the
rest of the session unless you noticed the warning buried in stdout.

Retry the next 10 ports (3113 -> 3122) before giving up. On fallback,
log: 'Viewer started on http://localhost:3114 (fallback from 3113)'.
agentmemory pins iii-engine to v0.11.2 (see IIPINNED_VERSION in
src/cli.ts) but anyone with a different iii on PATH gets silent
behavioral drift -- EPIPE reconnect loops, empty search after save --
because we never tell them their engine doesn't match.

Add warnIfEngineVersionMismatch() and fire it from both:
  - startIiiBin() before we spawn (covers fresh starts)
  - main() early-return when isEngineRunning() returns true (covers
    attach-to-existing)

The warn tells the operator how to silence (AGENTMEMORY_III_VERSION)
and how to downgrade (curl | tar).
runStop() correctly refuses to signal host PIDs when state file is
missing and a docker-compose.yml is discoverable -- the engine might
have been started by another shell via Docker compose, and SIGTERMing
the lsof match would kill an unrelated host process.

But when the engine actually WAS started natively (e.g. by a stale
agentmemory v0.9.13 with no state-writing) and the state file is gone,
the guard becomes a foot-gun: stop refuses, the engine stays up, and
the operator has no escape hatch short of `lsof | kill`.

Add --force. Documented in --help. The non-force path remains the
default and still refuses.
When main() finds isEngineRunning() already true (e.g. a previous
shell started it), we early-return without writing iii.pid or
engine-state.json. Later `agentmemory stop` then hits the
Docker-heuristic guard (state missing + compose discoverable) and
refuses to act -- correct in principle, foot-gun in practice.

Adopt on attach: write iii.pid from lsof-on-port and engine-state.json
as kind=native attached=true, but only if neither already exists.
Idempotent -- safe to re-run.

Log: 'Attached to existing iii-engine (pid 12345)'.
After the engine is ready, if invoked via npx (detected via
npm_lifecycle_event / process.argv[1] containing _npx /
npm_config_user_agent starting with npm/), emit one line:

  Tip: install globally for the bare `agentmemory` command:
    npm install -g @agentmemory/agentmemory

Suppressed by ~/.agentmemory/preferences.json with skipNpxHint:true
(a sibling PR will write that file when the user opts out).
@vercel
Copy link
Copy Markdown

vercel Bot commented May 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agentmemory Ready Ready Preview, Comment May 15, 2026 3:05pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

📝 Walkthrough

Walkthrough

The PR enhances CLI engine lifecycle management with version mismatch detection against a pinned binary version, automatic adoption of already-running engines, and an optional npx installation tip controlled by user preference. The stop command gains a --force flag to bypass Docker-state guards. The viewer server gains port-retry fallback when the requested port is already in use.

Changes

Engine Lifecycle Diagnostics and Viewer Startup

Layer / File(s) Summary
Engine state contract extension
src/cli.ts
The native engine state union extends to optionally track an attached field indicating adoption from an already-running process.
Engine version mismatch detection
src/cli.ts
A helper checks the detected iii binary version against IIPINNED_VERSION and logs a warning with download hints; invoked when starting the native engine.
Npx detection and engine adoption
src/cli.ts
Helpers detect npx invocation, read user preference to suppress the tip (~/.agentmemory/preferences.json), and adopt running engines by updating local pidfile and state from lsof results.
Npx hint emission on engine readiness
src/cli.ts
After the engine becomes ready, the CLI optionally emits an npx installation tip based on invocation detection and user preference.
Stop command force flag
src/cli.ts
The stop command gains a --force flag (documented in help) that parses and conditionally bypasses the Docker-state guard, falling back to native pidfile + lsof behavior when set.
Viewer server port-retry fallback
src/viewer/server.ts
The viewer HTTP server startup now tracks retry attempts and the original requested port, increments the port on EADDRINUSE, and retries up to MAX_VIEWER_PORT_RETRIES with final-port logging and exhaustion warning.

Possibly related PRs

  • rohitg00/agentmemory#260: Pins iii-engine v0.11.2 across install paths; main PR adds version-mismatch detection against the pinned version during startup/attachment.
  • rohitg00/agentmemory#396: Modifies src/cli.ts stop flow for engine state and pidfile awareness; main PR adds stop --force to bypass the Docker-state guard in the same area.
  • rohitg00/agentmemory#95: Adjusts engine liveness probing and adds EADDRINUSE handling for the viewer server; main PR expands the same startup and bind-conflict logic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A rabbit hops through engine states,
Checking versions at the gates,
Npx tips and ports that retry—
When sockets clash, we multiply!
With force-stop flags and adoption's grace,
The startup dance finds its place. 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title comprehensively summarizes all five main changes in the PR (viewer port auto-bump, engine version-match warning, adopt-on-attach, stop --force, npx PATH hint), using clear, specific terminology that aligns with the actual modifications.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-silent-killers

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/cli.ts`:
- Around line 365-389: The adoptRunningEngine function currently marks any
reachable engine as a local/native instance; change it so it only writes native
state and a pidfile (via writeEngineState and writeEnginePidfile) and runs any
native-version checks when you can prove the service is local and native:
require that findEnginePidsByPort(getRestPort()) actually returns a PID and that
the base URL is loopback/localhost (check getBaseUrl() or the rest port binding)
before persisting { kind: "native", attached: true } or logging "Attached to
existing iii-engine"; if no local PID or not loopback, do not write native state
or pidfile and skip the native-version check/logging. Apply the same guard to
the other place that writes engine state/pid (the later
writeEngineState/writeEnginePidfile usage referenced in the review) so
remote/Docker/Windows cases are not misclassified.
🪄 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: 314c678a-9220-4874-864c-f6bc9c27495d

📥 Commits

Reviewing files that changed from the base of the PR and between 372c6a6 and 5bb65fe.

📒 Files selected for processing (2)
  • src/cli.ts
  • src/viewer/server.ts

Comment thread src/cli.ts
Comment on lines +365 to +389
function adoptRunningEngine(): void {
try {
const existingState = readEngineState();
const existingPid = readEnginePidfile();
if (existingState && existingPid) return;

const pids = findEnginePidsByPort(getRestPort());
const enginePid = pids[0];
if (enginePid && !existingPid) {
writeEnginePidfile(enginePid);
}
if (!existingState) {
writeEngineState({
kind: "native",
configPath: findIiiConfig() || "",
attached: true,
});
}
if (enginePid && !existingPid) {
p.log.info(`Attached to existing iii-engine (pid ${enginePid})`);
}
} catch (err) {
vlog(`adoptRunningEngine: ${err instanceof Error ? err.message : String(err)}`);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Only adopt engines you can prove are local/native.

This path currently treats any reachable getBaseUrl() as a local native iii instance. With a remote AGENTMEMORY_URL, a Docker-published engine, or Windows (where findEnginePidsByPort() always returns []), it still persists { kind: "native", attached: true } and may emit a local-binary version warning for the wrong process. That changes later stop behavior from the existing Docker/ambiguity guard into native PID signaling or a dead-end “could not locate engine process”.

At minimum, don't persist native state or run the version check unless you've established that the target is loopback and you actually found a native engine PID to adopt.

Minimal guard to avoid misclassifying foreign engines
-function adoptRunningEngine(): void {
+function adoptRunningEngine(): boolean {
   try {
     const existingState = readEngineState();
     const existingPid = readEnginePidfile();
-    if (existingState && existingPid) return;
+    if (existingState && existingPid) return true;
+
+    const base = new URL(getBaseUrl());
+    const isLoopback = new Set(["localhost", "127.0.0.1", "::1"]).has(base.hostname);
+    if (!isLoopback) return false;

     const pids = findEnginePidsByPort(getRestPort());
     const enginePid = pids[0];
+    if (!enginePid) return false;
+
     if (enginePid && !existingPid) {
       writeEnginePidfile(enginePid);
     }
     if (!existingState) {
       writeEngineState({
         kind: "native",
         configPath: findIiiConfig() || "",
         attached: true,
       });
     }
     if (enginePid && !existingPid) {
       p.log.info(`Attached to existing iii-engine (pid ${enginePid})`);
     }
+    return true;
   } catch (err) {
     vlog(`adoptRunningEngine: ${err instanceof Error ? err.message : String(err)}`);
+    return false;
   }
 }

-    warnIfEngineVersionMismatch(attachedBin);
-    adoptRunningEngine();
+    const adopted = adoptRunningEngine();
+    if (adopted) warnIfEngineVersionMismatch(attachedBin);

Also applies to: 699-703

🤖 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 `@src/cli.ts` around lines 365 - 389, The adoptRunningEngine function currently
marks any reachable engine as a local/native instance; change it so it only
writes native state and a pidfile (via writeEngineState and writeEnginePidfile)
and runs any native-version checks when you can prove the service is local and
native: require that findEnginePidsByPort(getRestPort()) actually returns a PID
and that the base URL is loopback/localhost (check getBaseUrl() or the rest port
binding) before persisting { kind: "native", attached: true } or logging
"Attached to existing iii-engine"; if no local PID or not loopback, do not write
native state or pidfile and skip the native-version check/logging. Apply the
same guard to the other place that writes engine state/pid (the later
writeEngineState/writeEnginePidfile usage referenced in the review) so
remote/Docker/Windows cases are not misclassified.

@rohitg00 rohitg00 merged commit 2250d86 into main May 15, 2026
5 checks passed
rohitg00 added a commit that referenced this pull request May 15, 2026
… remove, silent killers) (#407)

Patch bump per the established rule: all changes additive, no breaks
to MemoryProvider trait or exported types or default behaviour. New
top-level subcommands (connect, remove, --reset, --force) are opt-in.

PRs included since v0.9.14:
  #405 — silent killers: viewer port auto-bump, engine version-match
         warning, stop --force, adopt-on-attach, npx PATH hint
  #402 — agentmemory connect — automate native-plugin install for 8
         agents (claude-code, codex, cursor, gemini-cli, openclaw
         end-to-end; hermes/pi/openhuman stubbed)
  #406 — interactive doctor v2 + agentmemory remove (destruction plan
         + two-confirmation flow)
  #403 — splash banner + agent grid + provider picker + smart-defaults
         preferences + bootLog shim (30+ lines of log spam → 10)

Files bumped (9):
  package.json, packages/mcp/package.json, plugin/.claude-plugin/plugin.json,
  plugin/.codex-plugin/plugin.json, src/version.ts, src/types.ts,
  src/functions/export-import.ts, test/export-import.test.ts,
  CHANGELOG.md
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