Skip to content

fix(cli): tie iii engine lifecycle and skip prompts in non-TTY context#528

Open
jonathanzhan1975 wants to merge 4 commits into
rohitg00:mainfrom
jonathanzhan1975:fix/cli-iii-lifecycle
Open

fix(cli): tie iii engine lifecycle and skip prompts in non-TTY context#528
jonathanzhan1975 wants to merge 4 commits into
rohitg00:mainfrom
jonathanzhan1975:fix/cli-iii-lifecycle

Conversation

@jonathanzhan1975
Copy link
Copy Markdown
Contributor

@jonathanzhan1975 jonathanzhan1975 commented May 19, 2026

Summary

Root cause of #303 is the TTY heuristic causing iii.exe to be orphaned in non-TTY context; also interactive prompts block headless launches.

This fix:

  • removes the TTY heuristic from backgroundMode
  • adds cleanup handlers so foreground CLI workers terminate the spawned iii engine
  • gates all p.confirm() calls in the startup install prompts with a !process.stdin.isTTY check

Fixes #303.

Summary by CodeRabbit

  • Bug Fixes

    • CLI prompts now correctly skip installation/first-run prompts in non-interactive terminals and when preferences disable them.
  • New Features

    • Background mode for engine processes with improved lifecycle handling.
    • Memory tools accept optional format and token budget parameters.
    • New config flag to control dropping stale index entries.
  • Chores

    • Plugin hook command strings updated to use quoted paths.
  • Tests

    • Added/updated tests for hooks, env flag, and memory proxy behavior.

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 19, 2026

@jonathanzhan1975 is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

📝 Walkthrough

Walkthrough

Adds TTY- and preference-aware CLI prompt gating; implements a --background spawn mode that detaches/unrefs the engine and alters stdio/handler registration; introduces isDropStaleIndexEnabled() wired into index logic; extends memory_recall validation and proxying to /agentmemory/search with format/token_budget; quotes plugin hook node commands and updates tests.

Changes

CLI Prompting and Background Process Mode

Layer / File(s) Summary
CLI Prompt Skip Conditions
src/cli.ts
Global-install prompt now skips when stdin is not a TTY or prefs.skipNpxHint is set; iii-console install prompt now skips when prefs.skipConsoleInstall is set (still requires TTY).
Background Engine Process Spawning and Lifecycle
src/cli.ts
spawnEngineBackground derives backgroundMode from --background; toggles detached/stdio behavior, disables parent stdout forwarding and lifecycle/signal handlers when backgrounded, and calls unref() on the child in background mode.

MCP standalone: memory_recall and memory_smart_search

Layer / File(s) Summary
Validated args: format & token_budget
src/mcp/standalone.ts
Adds optional format and tokenBudget to validated tool args; parses/normalizes format and positive integer token_budget from numeric or string inputs.
Proxy dispatcher: memory_recall -> /agentmemory/search
src/mcp/standalone.ts, test/mcp-standalone-proxy.test.ts
Splits memory_recall into its own proxy case posting to /agentmemory/search with { query, limit, format (default "full"), token_budget? }; memory_smart_search forwards same optional params to /agentmemory/smart-search. Tests assert endpoint selection and defaulting behavior.

Config flag and index wiring

Layer / File(s) Summary
isDropStaleIndexEnabled accessor
src/config.ts, src/index.ts
Adds exported isDropStaleIndexEnabled() that returns true only when merged env value equals "true", and replaces raw env check in index code with this accessor.
Env loader tests
test/env-loader.test.ts
Clears AGENTMEMORY_DROP_STALE_INDEX between tests and adds a case verifying the accessor is enabled by env-file AGENTMEMORY_DROP_STALE_INDEX=true.

Plugin hook manifests and tests

Layer / File(s) Summary
Quote CLAUDE_PLUGIN_ROOT in codex hooks
plugin/hooks/hooks.codex.json
Wraps ${CLAUDE_PLUGIN_ROOT}/scripts/... paths in double quotes inside node command strings for multiple codex hook entries.
Tests for hook command quoting and script references
test/codex-plugin.test.ts
Adds helper to collect hook commands and a test asserting node commands use quoted ${CLAUDE_PLUGIN_ROOT} paths; tightens regex extracting script paths for filesystem existence checks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #440: Engine writes data/ to caller's cwd — the MCP/engine spawn changes touch engine startup and backgrounding behavior relevant to how engine is launched from plugin contexts.

Possibly related PRs

Poem

🐰 I hopped through prompts and quieted the pleas,
Quoted the hooks so scripts run with ease.
I split recall, fed it a budget and form,
Let engines run free in the background storm.
A tiny rabbit nods — the repo sleeps warm.

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses issue #303 by improving process lifecycle management and skipping prompts in non-TTY contexts, but does not implement the core solution (relocate data directory to user home directory or use --data-dir flag). Implement the proposed fix from issue #303: add CLI flag/env var for data directory location (--data-dir / AGENTMEMORY_DATA_DIR) defaulting to ~/.agentmemory/data or auto-detect git repos and relocate state files accordingly.
Out of Scope Changes check ⚠️ Warning Most changes (CLI TTY handling, memory recall parameters, hook quoting) are narrowly scoped, but hook command quoting changes and new memory recall features extend beyond the stated #303 issue objectives. Clarify scope: hook quoting and memory_recall enhancements appear unrelated to #303 (engine directory pollution). Consider moving these to separate PRs or updating PR description to document their relationship.
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title correctly describes the main changes: CLI-level fixes for TTY handling and prompts with process lifecycle management for the iii engine.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

🧹 Nitpick comments (2)
src/cli.ts (2)

429-432: 💤 Low value

Redundant TTY check — !process.stdin.isTTY on line 432 is unreachable.

Line 429 already returns early when !process.stdin.isTTY, so the same condition on line 432 can never be true. The prefs.skipNpxHint addition is the meaningful change; consider removing the duplicate TTY check for clarity.

♻️ Suggested simplification
-  if (prefs.skipGlobalInstall || prefs.skipNpxHint || !process.stdin.isTTY) return;
+  if (prefs.skipGlobalInstall || prefs.skipNpxHint) return;
🤖 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 429 - 432, The second redundant TTY check is
unreachable because the file already returns when !process.stdin.isTTY; edit the
conditional that currently reads "if (prefs.skipGlobalInstall ||
prefs.skipNpxHint || !process.stdin.isTTY) return;" to remove the duplicate
"!process.stdin.isTTY" check so it simply returns when prefs.skipGlobalInstall
or prefs.skipNpxHint are true, keeping the earlier "if (!process.stdin.isTTY)
return;" and using the existing prefs variable (prefs.skipGlobalInstall,
prefs.skipNpxHint) to determine the later early exit.

517-519: 💤 Low value

Same redundancy: !process.stdin.isTTY on line 519 is already handled by line 517.

♻️ Suggested simplification
-  if (prefs.skipConsoleInstall || !process.stdin.isTTY) return state;
+  if (prefs.skipConsoleInstall) return state;
🤖 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 517 - 519, The second TTY check is redundant; after
the earlier guard "if (!process.stdin.isTTY || process.env['CI']) return state"
we already know stdin is a TTY. Update the conditional "if
(prefs.skipConsoleInstall || !process.stdin.isTTY) return state" to remove the
redundant check so it reads "if (prefs.skipConsoleInstall) return state" (leave
the readPrefs() call and surrounding logic intact).
🤖 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.

Nitpick comments:
In `@src/cli.ts`:
- Around line 429-432: The second redundant TTY check is unreachable because the
file already returns when !process.stdin.isTTY; edit the conditional that
currently reads "if (prefs.skipGlobalInstall || prefs.skipNpxHint ||
!process.stdin.isTTY) return;" to remove the duplicate "!process.stdin.isTTY"
check so it simply returns when prefs.skipGlobalInstall or prefs.skipNpxHint are
true, keeping the earlier "if (!process.stdin.isTTY) return;" and using the
existing prefs variable (prefs.skipGlobalInstall, prefs.skipNpxHint) to
determine the later early exit.
- Around line 517-519: The second TTY check is redundant; after the earlier
guard "if (!process.stdin.isTTY || process.env['CI']) return state" we already
know stdin is a TTY. Update the conditional "if (prefs.skipConsoleInstall ||
!process.stdin.isTTY) return state" to remove the redundant check so it reads
"if (prefs.skipConsoleInstall) return state" (leave the readPrefs() call and
surrounding logic intact).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 61b02a14-c8c3-4aa3-b2e0-e451515bfd05

📥 Commits

Reviewing files that changed from the base of the PR and between 68fddd4 and 3317d30.

📒 Files selected for processing (1)
  • src/cli.ts

honor2030 and others added 4 commits May 19, 2026 22:02
Co-authored-by: 이민재 <19909783+honor2030@users.noreply.github.com>
Co-authored-by: honor2030 <19909783+honor2030@users.noreply.github.com>
…oken_budget (rohitg00#507) (rohitg00#516)

* fix(mcp): route memory_recall to /agentmemory/search and forward format/token_budget

memory_recall and memory_smart_search were sharing the smart-search
endpoint, which always returns compact mode and silently drops the
format and token_budget parameters that the tool schema advertises.
Split the cases so memory_recall hits /agentmemory/search (which
honors format) while memory_smart_search keeps its own endpoint.
Default format to "full" for memory_recall so the documented behavior
matches the wire call.

Signed-off-by: serhiizghama <zmrser@gmail.com>

* test(mcp): cover memory_recall endpoint, format forwarding, and defaults

Two new proxy tests for issue rohitg00#507: one asserts memory_recall calls
POST /agentmemory/search with the format and token_budget fields,
and never falls through to smart-search; the other pins the default
format to "full" when the caller omits it.

Signed-off-by: serhiizghama <zmrser@gmail.com>

---------

Signed-off-by: serhiizghama <zmrser@gmail.com>
@jonathanzhan1975 jonathanzhan1975 force-pushed the fix/cli-iii-lifecycle branch from 3317d30 to fc91308 Compare May 19, 2026 14:21
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: 2

🤖 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 663-667: The signal handlers in cli.ts currently call
process.exit() after cleanupChild(), which can prematurely terminate the process
before other signal handlers in src/index.ts run (e.g., indexPersistence.save()
and sdk.shutdown()). Remove the forced process.exit() call inside the loop that
registers process.on for "SIGINT" and "SIGTERM"; instead simply invoke
cleanupChild() (or await it if it becomes async) and return so the normal signal
propagation and the index.ts handlers (indexPersistence.save, sdk.shutdown) can
run, or if you need to ensure ordering, invoke those shutdown helpers explicitly
from this handler rather than calling process.exit().

In `@src/mcp/standalone.ts`:
- Around line 123-126: The code currently lowercases args["format"] and assigns
it to v.format but accepts any non-empty string; instead enforce a whitelist of
allowed formats (e.g., const allowedFormats = ['json','yaml', ...]) and after
computing fmt = args["format"]?.trim().toLowerCase() validate that fmt is one of
allowedFormats; if not, return or throw a validation error (fail fast at the MCP
boundary) rather than assigning v.format. Update the block that reads fmt/sets
v.format (referencing args["format"], fmt, and v.format) to perform this
whitelist check and produce a clear validation error when the value is invalid.
🪄 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: 12d3e765-c203-4b9f-8038-026f64a8fbe2

📥 Commits

Reviewing files that changed from the base of the PR and between 3317d30 and fc91308.

📒 Files selected for processing (9)
  • plugin/hooks/hooks.codex.json
  • plugin/hooks/hooks.json
  • src/cli.ts
  • src/config.ts
  • src/index.ts
  • src/mcp/standalone.ts
  • test/codex-plugin.test.ts
  • test/env-loader.test.ts
  • test/mcp-standalone-proxy.test.ts
✅ Files skipped from review due to trivial changes (2)
  • plugin/hooks/hooks.json
  • plugin/hooks/hooks.codex.json

Comment thread src/cli.ts
Comment on lines +663 to +667
for (const sig of ["SIGINT", "SIGTERM"] as const) {
process.on(sig, () => {
cleanupChild();
process.exit();
});
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 | ⚡ Quick win

Avoid forcing process.exit() in the engine cleanup signal hook.

Line 666 can terminate the process before src/index.ts signal handlers run, which skips graceful worker shutdown (indexPersistence.save(), sdk.shutdown()).

🔧 Proposed fix
     for (const sig of ["SIGINT", "SIGTERM"] as const) {
       process.on(sig, () => {
         cleanupChild();
-        process.exit();
+        // Let downstream signal handlers (worker shutdown) run.
+        // Only force exit if this is the sole listener.
+        if (process.listenerCount(sig) === 1) process.exit(0);
       });
     }
🤖 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 663 - 667, The signal handlers in cli.ts currently
call process.exit() after cleanupChild(), which can prematurely terminate the
process before other signal handlers in src/index.ts run (e.g.,
indexPersistence.save() and sdk.shutdown()). Remove the forced process.exit()
call inside the loop that registers process.on for "SIGINT" and "SIGTERM";
instead simply invoke cleanupChild() (or await it if it becomes async) and
return so the normal signal propagation and the index.ts handlers
(indexPersistence.save, sdk.shutdown) can run, or if you need to ensure
ordering, invoke those shutdown helpers explicitly from this handler rather than
calling process.exit().

Comment thread src/mcp/standalone.ts
Comment on lines +123 to +126
const fmt = args["format"];
if (typeof fmt === "string" && fmt.trim()) {
v.format = fmt.trim().toLowerCase();
}
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 | 🟡 Minor | ⚡ Quick win

Whitelist accepted format values at validation boundary.

Line 123-126 lowercases format but accepts any non-empty string; invalid values are forwarded downstream instead of failing fast in the MCP boundary validator.

🔧 Proposed fix
       const fmt = args["format"];
       if (typeof fmt === "string" && fmt.trim()) {
-        v.format = fmt.trim().toLowerCase();
+        const normalized = fmt.trim().toLowerCase();
+        const allowedFormats = new Set(["full", "compact"]);
+        if (!allowedFormats.has(normalized)) {
+          throw new Error("format must be one of: full, compact");
+        }
+        v.format = normalized;
       }
As per coding guidelines, "Perform input validation at system boundaries (MCP handlers and REST endpoints)".
🤖 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/mcp/standalone.ts` around lines 123 - 126, The code currently lowercases
args["format"] and assigns it to v.format but accepts any non-empty string;
instead enforce a whitelist of allowed formats (e.g., const allowedFormats =
['json','yaml', ...]) and after computing fmt =
args["format"]?.trim().toLowerCase() validate that fmt is one of allowedFormats;
if not, return or throw a validation error (fail fast at the MCP boundary)
rather than assigning v.format. Update the block that reads fmt/sets v.format
(referencing args["format"], fmt, and v.format) to perform this whitelist check
and produce a clear validation error when the value is invalid.

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.

Engine writes data/ to caller's cwd — pollutes user repos when started from project root

3 participants