- Add shebang banner to NPX CLI esbuild config so npx claude-mem works
- Remove manual backslash pre-escaping in WindsurfHooksInstaller (JSON.stringify handles it)
- Scope cache deletion to claude-mem only, not entire vendor namespace
- Use getWorkerPort() in OpenCodeInstaller instead of hard-coded 37777
- Throw on corrupt JSON in readJsonSafe/readGeminiSettings/Windsurf to prevent data loss
- Fix Cursor install stub to warn instead of silently succeeding
- Fix Gemini uninstall to remove individual hooks within groups, not whole groups
- Update tests for new corrupt-file-throws behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SessionEnd has a 1.5s hardcoded cap from Claude Code (CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS),
making it unsuitable for waiting on async work. Previously, the Stop hook would fire-and-forget
the summarize request, then SessionEnd would immediately call deleteSession — aborting the SDK
agent mid-summary.
Now the Stop hook (120s timeout, no cap) owns the full lifecycle:
1. Queue summarize request
2. Poll new GET /api/sessions/status endpoint until queue drains
3. Call /api/sessions/complete after summary finishes
SessionEnd is now a true fire-and-forget fallback (process.exit(0) immediately).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve merge conflicts in adapter index, gemini-cli adapter, and rebuilt CJS artifacts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes CursorHooksInstaller ESM compatibility, updates install command
with improved path resolution, and refreshes built plugin artifacts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an observation response accidentally contains a <summary> tag with
plain text (no <request>/<investigated>/etc. sub-tags), parseSummary was
creating empty SESSION SUMMARY records with all fields as empty strings.
Add an all-null guard AFTER field extraction: if none of the 5 sub-tags
matched, the <summary> match is a false positive and we return null.
This is distinct from the commented-out validation above (which rejected
summaries with SOME missing fields). We only reject when ALL are absent —
real partial summaries are still saved per the maintainer's explicit note.
Closes#1360
Co-Authored-By: Claude <noreply@anthropic.com>
JSON.parse('/path/to/file') throws SyntaxError, crashing the viewer and
any code reading observations with legacy bare-path data in those columns.
- Add parseFileList() helper in observations/files.ts — tries JSON.parse,
falls back to wrapping bare strings in an array
- Replace unsafe JSON.parse calls in files.ts, SessionStore.ts, ChromaSync.ts
- Add 9 unit tests covering null, empty, valid JSON, bare paths, invalid JSON
Closes#1359
Co-Authored-By: Claude <noreply@anthropic.com>
The gh issue comment command was interpolating the LLM response via
${{ steps.inference.outputs.response }} directly in the shell, allowing
single-quote escaping if the response contained untrusted content.
RESPONSE was already declared as an env var but unused — now using it.
Closes#1285
Co-Authored-By: Claude <noreply@anthropic.com>
CLAUDE_MEM_MODEL defaulted to the deprecated claude-sonnet-4-5 across source,
installer, tests, and documentation. Updated all references to claude-sonnet-4-6.
Closes#1390
Co-Authored-By: Claude <noreply@anthropic.com>
When CLAUDE_MEM_FOLDER_USE_LOCAL_MD is set to 'true' in settings,
claude-mem writes auto-generated context to CLAUDE.local.md instead
of CLAUDE.md. This separates personal machine-generated context from
shared project instructions, aligning with Claude Code's native
CLAUDE.local.md convention where:
- CLAUDE.md = team-shared project instructions (checked into git)
- CLAUDE.local.md = personal/local context (gitignored)
Changes:
- Add CLAUDE_MEM_FOLDER_USE_LOCAL_MD setting (default: false)
- Add getTargetFilename() helper to resolve target based on settings
- Update writeClaudeMdToFolder() to accept optional target filename
- Update active-file detection to skip folders with either CLAUDE.md
or CLAUDE.local.md being actively read/modified (issue #859 compat)
- Add 8 new tests covering filename selection, write behavior,
content preservation, atomic writes, and active-file detection
Closes#632
The MCP server (#!/usr/bin/env node) and context generator run under
Node.js, where import.meta.url throws SyntaxError in CJS mode. Only
the worker-service needs the banner since it runs under Bun.
CJS files under Node.js already have __dirname/__filename natively.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add sessionId to summarize.ts warning log for easier triage
- Add APPROVED OVERRIDE annotation to Windows spawn catch block
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Fix esbuild inlining build-machine __dirname as string literal — use
CJS-compatible runtime banner with require("node:url").fileURLToPath
across worker-service, mcp-server, and context-generator builds.
2. Fix isMainModule check missing .cjs extension and Windows backslash
path normalization.
3. Wrap extractLastMessage in try-catch to prevent infinite Stop hook
feedback loop on malformed transcripts (exit 0 instead of exit 2).
4. Replace heavy SessionEnd hook (Node→Bun→1.7MB CJS→HTTP) with
lightweight inline node -e one-liner (~200ms vs >1s).
5. Add 7 Gemini/OpenRouter error patterns to unrecoverablePatterns
circuit breaker to prevent 77K+ retry loops on expired API keys.
6. Preserve CLAUDE_CODE_OAUTH_TOKEN and CLAUDE_CODE_GIT_BASH_PATH in
sanitizeEnv instead of stripping them with the CLAUDE_CODE_ prefix.
7. Use PowerShell -EncodedCommand for spawnDaemon to fix path quoting
when Windows usernames contain spaces.
Closes#1515, #1495, #1475, #1465, #1500, #1513, #1512, #1450, #1460,
#1486, #1449, #1481, #1451, #1480, #1453, #1445
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The USER_MESSAGE_ONLY (exit code 3) constant was used by the old
user-message-hook.js (removed in the hooks refactor). Claude Code only
recognizes exit codes 0 (success) and 2 (blocking error) — any other
non-zero exit code is treated as a hook failure, causing the
"SessionStart:startup hook error" message on every session start for
users still running v8.x.
This removes the dead constant and improves the exit code documentation
to prevent reintroduction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fields concatenated without separators allowed different tuples to produce
identical hashes (e.g. session="ab", title="cd" vs session="abc", title="d").
This could cause legitimate observations to be silently deduplicated.
Join with \x00 so field boundaries are unambiguous.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the OpenClaw gateway runs in Docker and the claude-mem worker runs
on the host, localhost:37777 is unreachable from inside the container.
Add a workerHost config option (default: 127.0.0.1) so users can set it
to host.docker.internal for Docker-based deployments.
Changes:
- Add workerHost to ClaudeMemPluginConfig interface
- Read workerHost from plugin config in plugin entry point
- Update workerBaseUrl to use configurable host
- Add workerHost to openclaw.plugin.json config schema
- Update startup log to show configured host
Removes installer/ directory (16 files) — fully replaced by src/npx-cli/.
Updates install.sh and installer.js to redirect to npx claude-mem.
Adds npx claude-mem as primary install method in docs and README.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds esbuild steps for npx-cli (57KB, Node.js ESM) and openclaw plugin
(12KB). Creates .npmignore to exclude node_modules and Bun binary from
npm package, reducing pack size from 146MB to 2MB.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the old git-clone installer with a direct npm package copy workflow.
Supports 13 IDE auto-detection targets, runtime delegation to Bun worker,
and pure Node.js install path (no Bun required for installation).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: stop spinner from spinning forever due to orphaned DB messages
The activity spinner never stopped because isAnySessionProcessing() queried
ALL pending/processing messages in the database, including orphaned messages
from dead sessions that no generator would ever process.
Root cause: isAnySessionProcessing() used hasAnyPendingWork() which is a
global DB scan. Changed it to use getTotalQueueDepth() which only checks
sessions in the active in-memory Map.
Additional fixes:
- Add terminateSession() to enforce restart-or-terminate invariant
- Fix 3 zombie paths in .finally() handler that left sessions alive
- Clean up idle sessions from memory on successful completion
- Remove redundant bare isProcessing:true broadcast
- Replace inline require() with proper accessor
- Add 8 regression tests for session termination invariant
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review findings — idle-timeout race, double broadcast, query amplification
- Move pendingCount check before idle-timeout termination to prevent
abandoning fresh messages that arrive between idle abort and .finally()
- Move broadcastProcessingStatus() inside restart branch only — the else
branch already broadcasts via removeSessionImmediate callback
- Compute queueDepth once in broadcastProcessingStatus() and derive
isProcessing from it, eliminating redundant double iteration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ColorFormatter and MarkdownFormatter names obscured their actual purpose.
The formatters serve two distinct audiences: the AI agent (compressed,
token-efficient context) and the human (rich ANSI-colored terminal output).
- MarkdownFormatter → AgentFormatter (renderMarkdown* → renderAgent*)
- ColorFormatter → HumanFormatter (renderColor* → renderHuman*)
- useColors parameter → forHuman across the pipeline
- Import aliases Color/Markdown → Human/Agent
- API query param `colors=true` unchanged (backward compatible)
Pure rename refactor — no logic or behavior changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: strip <system_instruction> tags before database storage
Extends the existing tag-stripping mechanism (used for <private> and
<claude-mem-context>) to also filter Conductor-injected system instructions,
preventing them from being persisted in the observation database.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: also strip <system-instruction> (hyphen variant) before DB storage
Conductor uses both <system_instruction> and <system-instruction> tag
formats. This adds the hyphen variant to the same stripping mechanism.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(openclaw): inject context via system prompt instead of overwriting MEMORY.md
The OpenClaw plugin was overwriting each agent's MEMORY.md with a large
auto-generated observation dump (~12-15KB) on every before_agent_start
and tool_result_persist event. This conflicts with OpenClaw's design
where MEMORY.md is agent-curated long-term memory.
Migrate context injection from file-based (writeFile MEMORY.md) to
OpenClaw's native before_prompt_build hook, which returns context via
appendSystemContext. This keeps MEMORY.md under agent control while
still providing cross-session observation context to the LLM.
Changes:
- Add before_prompt_build hook that returns { appendSystemContext }
- Remove writeFile/MEMORY.md sync from before_agent_start
- Remove MEMORY.md sync from tool_result_persist (observations still recorded)
- Add 60s TTL cache to avoid re-fetching context on every LLM turn
- Add syncMemoryFileExclude config for per-agent opt-out
- Remove dead workspaceDirsBySessionKey tracking map
- Rewrite test suite to verify prompt injection instead of file writes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ui): align settings defaults with backend and use nullish coalescing
The web UI had two issues causing settings inflation:
1. DEFAULT_SETTINGS in the UI used FULL_COUNT='5' and all token columns
'true', while SettingsDefaultsManager (backend) uses FULL_COUNT='0'
and token columns 'false'. Opening the settings modal and saving
without changes would silently inflate the context.
2. useSettings used || for fallback, which treats '0' and 'false' as
falsy — even when the backend correctly returns these values, the UI
would replace them with inflated defaults. Changed to ?? (nullish
coalescing) so only null/undefined trigger the fallback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(openclaw): update integration docs for system prompt injection
Reflect the migration from MEMORY.md file writes to before_prompt_build
hook-based context injection:
- Update architecture diagram and overview to show new hook flow
- Replace "MEMORY.md Live Sync" section with "System Prompt Context Injection"
- Update event lifecycle steps (before_agent_start, tool_result_persist)
- Add before_prompt_build step with TTL cache description
- Document new syncMemoryFileExclude config parameter
- Update session tracking to reflect removed workspaceDirsBySessionKey
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: fix terminology and update SKILL.md for system prompt injection
Replace "prompt injection" with "context injection" in docs to avoid
confusion with the OWASP security term. Update openclaw/SKILL.md to
reflect the new before_prompt_build hook and remove stale MEMORY.md
references.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Alex Newman <thedotmack@gmail.com>