Skip to content

[Bug] Windows: hook command generated by desktop app uses POSIX single quotes and fails to execute #149

Description

@cococolanosugar

Environment

  • OS: Windows 11 (PowerShell / cmd.exe)
  • Version: memmy-agent 1.0.4
  • Install path: GUI -> Memory -> System -> Cross-Agent access -> Install plugin
  • Affected agents: Claude Code / Codex

Problem

On Windows, installing the integration for Claude Code / Codex / Cursor via GUI -> Memory -> System -> Cross-Agent access -> Install plugin generates a hook command field wrapped in POSIX single quotes. Windows cannot execute this format (cmd.exe treats single quotes as literal characters; PowerShell treats 'node' as a string expression rather than a command call), so the hook fails and automatic memory recall/save stops working.

// Actual (broken)
"command": "'node' 'C:\\Users\\GW00384649\\.codex\\hooks\\memmy-resume-hook.mjs'"

// Expected (correct)
"command": "node \"C:\\Users\\GW00384649\\.codex\\hooks\\memmy-resume-hook.mjs\""

Reproduction

  1. Launch the desktop app on Windows
  2. Go to GUI -> Memory -> System -> Cross-Agent access and click Install plugin for Claude Code / Codex
  3. Open C:\Users\<user>\.codex\hooks.json (or .claude\settings.json)
  4. Inspect the command field - the path is wrapped in single quotes

Root cause

shellQuote() in App/backend/src/adapters/outbound/skill-writer/hook-command.ts:79-81 hardcodes POSIX single-quote semantics with no Windows branch:

function shellQuote(value: string): string {
  return `'${value.replace(/'/g, "'\\''")}'`;
}
  • Windows cmd.exe treats single quotes as literal characters; PowerShell treats 'node' as a string expression, not a command call
  • createNodeHookCommand() does not pass platform info to shellQuote()
  • Called by three targets: claude-code/target.ts:226,239, codex/target.ts:187,200, cursor/target.ts:113,120,127
  • Tests only cover platform: "darwin", so CI cannot catch this

Proposed fix

Branch shellQuote() by platform - use double quotes on Windows, keep POSIX behavior unchanged:

export function createNodeHookCommand(
  hookScriptPath: string,
  runtime: NodeExecutableRuntime = defaultNodeExecutableRuntime()
): string {
  const nodePath = resolveNodeExecutable(runtime);
  return `${shellQuote(nodePath, runtime.platform)} ${shellQuote(hookScriptPath, runtime.platform)}`;
}

function shellQuote(value: string, platform: NodeJS.Platform): string {
  if (platform === "win32") {
    if (!/[\s"\\/]/.test(value)) return value;            // simple command name (e.g. node) needs no quoting
    return `"${value.replace(/"/g, '\\"')}"`;              // wrap paths in double quotes
  }
  return `'${value.replace(/'/g, "'\\''")}'`;              // POSIX behavior unchanged
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions