chore: merge upstream v12.1.6 + keep local fixes

- Upstream v12.1.6: fix drop orphan flag when filtering empty-string spawn args (#2049)
  → Adopted upstream's cleaner look-behind approach for ProcessRegistry.ts
- Keep: reset completed session on mac sleep/resume (create.ts, not in upstream)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
JOUNGWOOK KWON
2026-04-17 09:04:40 +09:00
10 changed files with 284 additions and 262 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"plugins": [
{
"name": "claude-mem",
"version": "12.1.5",
"version": "12.1.6",
"source": "./plugin",
"description": "Persistent memory system for Claude Code - context compression across sessions"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "12.1.5",
"version": "12.1.6",
"description": "Memory compression system for Claude Code - persist context across sessions",
"author": {
"name": "Alex Newman"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "12.1.5",
"version": "12.1.6",
"description": "Memory compression system for Claude Code - persist context across sessions",
"author": {
"name": "Alex Newman",
+35 -6
View File
@@ -4,21 +4,50 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [12.1.6] - 2026-04-16
## Fix
**Critical regression fix (#2049): observations no longer save on Claude Code 2.1.109+**
Resolves 100% observation/summary failure on Claude Code 2.1.109+ caused by a latent bug in how the bundled Agent SDK emits the `--setting-sources` flag.
### Root cause
The Agent SDK emits `["--setting-sources", ""]` whenever `settingSources` defaults to `[]`. Our existing Bun-compat filter stripped the empty string but left an orphan `--setting-sources` flag, which then consumed the following `--permission-mode` as its value. Claude Code 2.1.109+ rejects this with:
```
Error processing --setting-sources:
Invalid setting source: --permission-mode.
```
Every observation SDK spawn crashed with exit code 1 before any data could be written.
### Fix
`ProcessRegistry.createPidCapturingSpawn` now uses a pair-aware filter: when an empty-string arg follows a `--flag`, both are dropped together. The SDK default (no setting sources) is preserved by omission.
### Credits
Thanks to @GigiTiti-Kai for the detailed root-cause report in #2049.
## [12.1.5] - 2026-04-15
## Forced update to ship --setting-sources fix
Users on v12.1.3 experience 100% observation failure due to empty-string arg filtering corrupting `--setting-sources` on Claude Code 2.1.109+. The fix landed in v12.1.4 (commit 3d92684 — `fix: filter empty string args before Bun spawn()`). This release forces the update to propagate across npm and the marketplace.
Users on v12.1.3 experience 100% observation failure due to empty-string arg filtering corrupting `--setting-sources` on Claude Code 2.1.109+. The fix already landed in v12.1.4 (commit 3d92684 — `fix: filter empty string args before Bun spawn()`). This release forces the update to propagate across npm and the marketplace so every user gets the fix.
## Backlog cleanup
Also shipped earlier today: the April 2026 backlog consolidation merged 93 PRs and 147 issues into 138 clean tracking issues (95 bugs, 43 feature requests).
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v12.1.4...v12.1.5
## Upgrade
```bash
npm install -g claude-mem@12.1.5
```
## [12.1.4] - 2026-04-15
A Claude instance inserted `$CMEM` token branding into the context injection header during a compression refactor. Reverted back to the original descriptive format: `# [project] recent context, datetime`
## Bug Fixes
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v12.1.3...v12.1.4
- **Revert unauthorized $CMEM branding**: A prior Claude instance inserted `$CMEM` token branding into the context injection header during a compression refactor. Reverted back to the original descriptive format: `# [project] recent context, datetime`
## [12.1.3] - 2026-04-15
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "12.1.5",
"version": "12.1.6",
"description": "Memory compression system for Claude Code - persist context across sessions",
"keywords": [
"claude",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "12.1.5",
"version": "12.1.6",
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
"author": {
"name": "Alex Newman"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem-plugin",
"version": "12.1.5",
"version": "12.1.6",
"private": true,
"description": "Runtime dependencies for claude-mem bundled hooks",
"type": "module",
+1 -1
View File
@@ -194,7 +194,7 @@ ${f}`}let s=i.lineStart;for(let u=i.lineStart-1;u>=0;u--){let l=a[u].trim();if(l
${c}`}var vb=new Set([".js",".jsx",".ts",".tsx",".mjs",".cjs",".py",".pyw",".go",".rs",".rb",".java",".cs",".cpp",".cc",".cxx",".c",".h",".hpp",".hh",".swift",".kt",".kts",".php",".vue",".svelte",".ex",".exs",".lua",".scala",".sc",".sh",".bash",".zsh",".hs",".zig",".css",".scss",".toml",".yml",".yaml",".sql",".md",".mdx"]),oj=new Set(["node_modules",".git","dist","build",".next","__pycache__",".venv","venv","env",".env","target","vendor",".cache",".turbo","coverage",".nyc_output",".claude",".smart-file-read"]),ij=512*1024;async function*_b(t,e,r=20,n){if(r<=0)return;let o;try{o=await(0,Zn.readdir)(t,{withFileTypes:!0})}catch{return}for(let i of o){if(i.name.startsWith(".")&&i.name!=="."||oj.has(i.name))continue;let a=(0,Ai.join)(t,i.name);if(i.isDirectory())yield*_b(a,e,r-1,n);else if(i.isFile()){let s=i.name.slice(i.name.lastIndexOf("."));(vb.has(s)||n&&n.has(s))&&(yield a)}}}async function aj(t){try{let e=await(0,Zn.stat)(t);if(e.size>ij||e.size===0)return null;let r=await(0,Zn.readFile)(t,"utf-8");return r.slice(0,1e3).includes("\0")?null:r}catch{return null}}async function yb(t,e,r={}){let n=r.maxResults||20,o=e.toLowerCase(),i=o.split(/[\s_\-./]+/).filter(x=>x.length>0),a=r.projectRoot||t,s=Mi(a),c=new Set;for(let x of Object.values(s.grammars))for(let b of x.extensions)vb.has(b)||c.add(b);let u=[];for await(let x of _b(t,t,20,c.size>0?c:void 0)){if(r.filePattern&&!(0,Ai.relative)(t,x).toLowerCase().includes(r.filePattern.toLowerCase()))continue;let b=await aj(x);b&&u.push({absolutePath:x,relativePath:(0,Ai.relative)(t,x),content:b})}let l=mb(u,a),d=[],p=[],f=0;for(let[x,b]of l){f+=sj(b);let T=rc(x.toLowerCase(),i)>0,qe=[],Ve=(Fn,qr)=>{for(let _e of Fn){let tr=0,mt="",qn=rc(_e.name.toLowerCase(),i);qn>0&&(tr+=qn*3,mt="name match"),_e.signature.toLowerCase().includes(o)&&(tr+=2,mt=mt?`${mt} + signature`:"signature match"),_e.jsdoc&&_e.jsdoc.toLowerCase().includes(o)&&(tr+=1,mt=mt?`${mt} + jsdoc`:"jsdoc match"),tr>0&&(T=!0,qe.push({filePath:x,symbolName:qr?`${qr}.${_e.name}`:_e.name,kind:_e.kind,signature:_e.signature,jsdoc:_e.jsdoc,lineStart:_e.lineStart,lineEnd:_e.lineEnd,matchReason:mt})),_e.children&&Ve(_e.children,_e.name)}};Ve(b.symbols),T&&(d.push(b),p.push(...qe))}p.sort((x,b)=>{let N=rc(x.symbolName.toLowerCase(),i);return rc(b.symbolName.toLowerCase(),i)-N});let h=p.slice(0,n),g=new Set(h.map(x=>x.filePath)),y=d.filter(x=>g.has(x.filePath)).slice(0,n),S=y.reduce((x,b)=>x+b.foldedTokenEstimate,0);return{foldedFiles:y,matchingSymbols:h,totalFilesScanned:u.length,totalSymbolsFound:f,tokenEstimate:S}}function rc(t,e){let r=0;for(let n of e)if(t===n)r+=10;else if(t.includes(n))r+=5;else{let o=0,i=0;for(let a of n){let s=t.indexOf(a,o);s!==-1&&(i++,o=s+1)}i===n.length&&(r+=1)}return r}function sj(t){let e=t.symbols.length;for(let r of t.symbols)r.children&&(e+=r.children.length);return e}function $b(t,e){let r=[];if(r.push(`\u{1F50D} Smart Search: "${e}"`),r.push(` Scanned ${t.totalFilesScanned} files, found ${t.totalSymbolsFound} symbols`),r.push(` ${t.matchingSymbols.length} matches across ${t.foldedFiles.length} files (~${t.tokenEstimate} tokens for folded view)`),r.push(""),t.matchingSymbols.length===0)return r.push(" No matching symbols found."),r.join(`
`);r.push("\u2500\u2500 Matching Symbols \u2500\u2500"),r.push("");for(let n of t.matchingSymbols){if(r.push(` ${n.kind} ${n.symbolName} (${n.filePath}:${n.lineStart+1})`),r.push(` ${n.signature}`),n.jsdoc){let o=n.jsdoc.split(`
`).find(i=>i.replace(/^[\s*/]+/,"").trim().length>0);o&&r.push(` \u{1F4AC} ${o.replace(/^[\s*/]+/,"").trim()}`)}r.push("")}r.push("\u2500\u2500 Folded File Views \u2500\u2500"),r.push("");for(let n of t.foldedFiles)r.push(Un(n)),r.push("");return r.push("\u2500\u2500 Actions \u2500\u2500"),r.push(" To see full implementation: use smart_unfold with file path and symbol name"),r.join(`
`)}var gm=require("node:fs/promises"),Sb=require("node:fs"),Fr=require("node:path"),kb=require("node:url"),vj={},cj="12.1.3";console.log=(...t)=>{E.error("CONSOLE","Intercepted console output (MCP protocol protection)",void 0,{args:t})};var wb=!1,Eb=(()=>{if(typeof __dirname<"u")return __dirname;try{return(0,Fr.dirname)((0,kb.fileURLToPath)(vj.url))}catch{return wb=!0,process.cwd()}})(),vm=(0,Fr.resolve)(Eb,"worker-service.cjs");function uj(){wb&&((0,Sb.existsSync)(vm)||E.error("SYSTEM","mcp-server: dirname resolution failed (both __dirname and import.meta.url are unavailable). Fell back to process.cwd() and the resolved WORKER_SCRIPT_PATH does not exist. This is the actual problem \u2014 the worker bundle is fine, but mcp-server cannot locate it. Worker auto-start will fail until the dirname-resolution path is fixed.",{workerScriptPath:vm,mcpServerDir:Eb}))}var bb={search:"/api/search",timeline:"/api/timeline"};async function hm(t,e){E.debug("SYSTEM","\u2192 Worker API",void 0,{endpoint:t,params:e});try{let r=new URLSearchParams;for(let[a,s]of Object.entries(e))s!=null&&r.append(a,String(s));let n=`${t}?${r}`,o=await Xs(n);if(!o.ok){let a=await o.text();throw new Error(`Worker API error (${o.status}): ${a}`)}let i=await o.json();return E.debug("SYSTEM","\u2190 Worker API success",void 0,{endpoint:t}),i}catch(r){return E.error("SYSTEM","\u2190 Worker API error",{endpoint:t},r),{content:[{type:"text",text:`Error calling Worker API: ${r instanceof Error?r.message:String(r)}`}],isError:!0}}}async function Ln(t,e){E.debug("HTTP","Worker API request (POST)",void 0,{endpoint:t});try{let r=await Xs(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!r.ok){let o=await r.text();throw new Error(`Worker API error (${r.status}): ${o}`)}let n=await r.json();return E.debug("HTTP","Worker API success (POST)",void 0,{endpoint:t}),{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(r){return E.error("HTTP","Worker API error (POST)",{endpoint:t},r),{content:[{type:"text",text:`Error calling Worker API: ${r instanceof Error?r.message:String(r)}`}],isError:!0}}}async function lj(){try{return(await Xs("/api/health")).ok}catch(t){return E.debug("SYSTEM","Worker health check failed",{},t),!1}}async function dj(){if(await lj())return!0;E.warn("SYSTEM","Worker not available, attempting auto-start for MCP client"),uj();try{let t=Yf(),e=await rb(t,vm);return e||E.error("SYSTEM","Worker auto-start returned false \u2014 MCP tools that require the worker (search, timeline, get_observations) will fail until the worker is running. Check earlier log lines for the specific failure reason (Bun not found, missing worker bundle, port conflict, etc.)."),e}catch(t){return E.error("SYSTEM","Worker auto-start threw \u2014 MCP tools that require the worker (search, timeline, get_observations) will fail until the worker is running.",void 0,t),!1}}var Ib=[{name:"__IMPORTANT",description:`3-LAYER WORKFLOW (ALWAYS FOLLOW):
`)}var gm=require("node:fs/promises"),Sb=require("node:fs"),Fr=require("node:path"),kb=require("node:url"),vj={},cj="12.1.6";console.log=(...t)=>{E.error("CONSOLE","Intercepted console output (MCP protocol protection)",void 0,{args:t})};var wb=!1,Eb=(()=>{if(typeof __dirname<"u")return __dirname;try{return(0,Fr.dirname)((0,kb.fileURLToPath)(vj.url))}catch{return wb=!0,process.cwd()}})(),vm=(0,Fr.resolve)(Eb,"worker-service.cjs");function uj(){wb&&((0,Sb.existsSync)(vm)||E.error("SYSTEM","mcp-server: dirname resolution failed (both __dirname and import.meta.url are unavailable). Fell back to process.cwd() and the resolved WORKER_SCRIPT_PATH does not exist. This is the actual problem \u2014 the worker bundle is fine, but mcp-server cannot locate it. Worker auto-start will fail until the dirname-resolution path is fixed.",{workerScriptPath:vm,mcpServerDir:Eb}))}var bb={search:"/api/search",timeline:"/api/timeline"};async function hm(t,e){E.debug("SYSTEM","\u2192 Worker API",void 0,{endpoint:t,params:e});try{let r=new URLSearchParams;for(let[a,s]of Object.entries(e))s!=null&&r.append(a,String(s));let n=`${t}?${r}`,o=await Xs(n);if(!o.ok){let a=await o.text();throw new Error(`Worker API error (${o.status}): ${a}`)}let i=await o.json();return E.debug("SYSTEM","\u2190 Worker API success",void 0,{endpoint:t}),i}catch(r){return E.error("SYSTEM","\u2190 Worker API error",{endpoint:t},r),{content:[{type:"text",text:`Error calling Worker API: ${r instanceof Error?r.message:String(r)}`}],isError:!0}}}async function Ln(t,e){E.debug("HTTP","Worker API request (POST)",void 0,{endpoint:t});try{let r=await Xs(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!r.ok){let o=await r.text();throw new Error(`Worker API error (${r.status}): ${o}`)}let n=await r.json();return E.debug("HTTP","Worker API success (POST)",void 0,{endpoint:t}),{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(r){return E.error("HTTP","Worker API error (POST)",{endpoint:t},r),{content:[{type:"text",text:`Error calling Worker API: ${r instanceof Error?r.message:String(r)}`}],isError:!0}}}async function lj(){try{return(await Xs("/api/health")).ok}catch(t){return E.debug("SYSTEM","Worker health check failed",{},t),!1}}async function dj(){if(await lj())return!0;E.warn("SYSTEM","Worker not available, attempting auto-start for MCP client"),uj();try{let t=Yf(),e=await rb(t,vm);return e||E.error("SYSTEM","Worker auto-start returned false \u2014 MCP tools that require the worker (search, timeline, get_observations) will fail until the worker is running. Check earlier log lines for the specific failure reason (Bun not found, missing worker bundle, port conflict, etc.)."),e}catch(t){return E.error("SYSTEM","Worker auto-start threw \u2014 MCP tools that require the worker (search, timeline, get_observations) will fail until the worker is running.",void 0,t),!1}}var Ib=[{name:"__IMPORTANT",description:`3-LAYER WORKFLOW (ALWAYS FOLLOW):
1. search(query) \u2192 Get index with IDs (~50-100 tokens/result)
2. timeline(anchor=ID) \u2192 Get context around interesting results
3. get_observations([IDs]) \u2192 Fetch full details ONLY for filtered IDs
File diff suppressed because one or more lines are too long
+11 -16
View File
@@ -412,28 +412,23 @@ export function createPidCapturingSpawn(sessionDbId: number) {
const useCmdWrapper = process.platform === 'win32' && spawnOptions.command.endsWith('.cmd');
const env = sanitizeEnv(spawnOptions.env ?? process.env);
// Filter empty string args: Bun's spawn() silently drops empty strings from argv,
// causing subsequent flags to be consumed as values for the preceding flag.
// The Agent SDK may produce empty-string args (e.g., settingSources defaults to []
// which joins to ""). Node preserves these, but Bun drops them, breaking CLI parsing.
//
// FIX: Simply filtering empty strings has the same effect as Bun dropping them —
// it leaves the preceding flag (e.g. --setting-sources) without a value, causing
// the next flag (e.g. --permission-mode) to be consumed as that value.
// Instead, we must remove the entire flag+empty-value pair.
// Filter empty string args AND their preceding flag (Issue #2049).
// The Agent SDK emits ["--setting-sources", ""] when settingSources defaults to [].
// Simply dropping "" leaves an orphan --setting-sources that consumes the next
// flag (e.g. --permission-mode) as its value, crashing Claude Code 2.1.109+ with
// "Invalid setting source: --permission-mode". Drop the flag too so the SDK
// default (no setting sources) is preserved by omission.
const args: string[] = [];
for (let i = 0; i < spawnOptions.args.length; i++) {
const arg = spawnOptions.args[i];
if (arg === '') continue; // skip standalone empty strings
// If this is a --flag followed by an empty string, skip both (remove the pair entirely)
if (arg.startsWith('--') && i + 1 < spawnOptions.args.length && spawnOptions.args[i + 1] === '') {
i++; // skip the empty value too
for (const arg of spawnOptions.args) {
if (arg === '') {
if (args.length > 0 && args[args.length - 1].startsWith('--')) {
args.pop();
}
continue;
}
args.push(arg);
}
const child = useCmdWrapper
? spawn('cmd.exe', ['/d', '/c', spawnOptions.command, ...args], {
cwd: spawnOptions.cwd,