thedotmack/claude-mem · error · AdapterRejectedInput
adapter rejected input: invalid_cwd
Error message
adapter rejected input: invalid_cwd
What it means
cursorAdapter.normalizeInput throws AdapterRejectedInput('invalid_cwd') when the cwd chosen from r.workspace_roots[0], r.cwd, or process.cwd() is not a non-empty string. Cursor typically supplies workspace_roots, so failure means those were empty/invalid and no usable fallback existed.
Solutions
- Ensure Cursor sends a valid workspace_roots array or cwd in the hook payload
- Update the adapter if Cursor changed its hook schema (check version compatibility)
- Remove stale empty-string entries from workspace_roots before invoking
- Catch AdapterRejectedInput with .reason === 'invalid_cwd' and retry with an explicit cwd
Example fix
// before
cursorAdapter.normalizeInput({ workspace_roots: [null], cwd: null });
// after
cursorAdapter.normalizeInput({ cwd: '/absolute/path/to/workspace' }); Defensive patterns
Strategy: validation
Validate before calling
const cwd = raw?.workspace_roots?.[0] || raw?.cwd || process.cwd();
if (typeof cwd !== 'string' || cwd.length === 0) throw new Error('no usable cwd for cursor event'); Type guard
function hasValidCwd(v: unknown): v is string { return typeof v === 'string' && v.length > 0; } Try / catch
try { adapter.normalizeInput(raw); } catch (e) { if (e instanceof AdapterRejectedInput && e.reason === 'invalid_cwd') { /* retry with explicit cwd */ } else throw e; } Prevention
- Ensure Cursor payloads contain workspace_roots or cwd
- Track Cursor extension version changes to the hook schema
- Strip null/empty entries from workspace_roots before invoking
When it happens
Trigger: Cursor hook payload with workspace_roots: [] and cwd missing (process.cwd() fallback should save this, so concrete failure is workspace_roots[0] or r.cwd being an empty string / non-string that the || chain still selects, e.g. workspace_roots[0] === '' short-circuits only on falsy — actually falsy values skip, so failure implies process.cwd() unavailable or a non-string falsy-skipped chain leaving an invalid value).
Common situations: Cursor extension updates changing the payload shape (workspace_roots renamed); global hooks run where the working directory was deleted; payload JSON with "workspace_roots": [null].
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- adapter rejected input: invalid_cwd
- adapter rejected input: invalid_cwd
- adapter rejected input: invalid_cwd
- adapter rejected input: invalid_cwd
- adapter rejected input: invalid_cwd
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/b84bccc9f704118f.
Report an issue: GitHub.
Appendix: source
Thrown at src/cli/adapters/cursor.ts:36
// set so a malicious sessionId from stdin cannot escape ~/.cursor/projects via
// path separators, '..' segments, or null bytes (security review on PR #2282).
const SAFE_SESSION_ID_RE = /^[A-Za-z0-9_-]+$/;
export function deriveCursorTranscriptPath(cwd: string | undefined, sessionId: string | undefined): string | undefined {
if (!cwd || !sessionId) return undefined;
if (!SAFE_SESSION_ID_RE.test(sessionId)) return undefined;
const slug = cwd.replace(/^\//, '').replace(/[/.]/g, '-');
const candidate = join(homedir(), '.cursor', 'projects', slug, 'agent-transcripts', sessionId, `${sessionId}.jsonl`);
return existsSync(candidate) ? candidate : undefined;
}
export const cursorAdapter: PlatformAdapter = {
normalizeInput(raw) {
const r = (raw ?? {}) as any;
const isShellCommand = !!r.command && !r.tool_name;
const cwd = r.workspace_roots?.[0] || r.cwd || process.cwd();
if (!isValidCwd(cwd)) {
throw new AdapterRejectedInput('invalid_cwd');
}
const sessionId = r.conversation_id || r.generation_id || r.id;
return {
sessionId,
cwd,
prompt: r.prompt ?? r.query ?? r.input ?? r.message,
toolName: isShellCommand ? 'Bash' : r.tool_name,
toolInput: isShellCommand ? { command: r.command } : r.tool_input,
toolResponse: isShellCommand ? { output: r.output } : r.result_json, // result_json not tool_response
toolUseId: typeof r.tool_use_id === 'string' ? r.tool_use_id : (typeof r.tool_call_id === 'string' ? r.tool_call_id : undefined),
// Cursor's stop hook does not pass a transcript path on stdin, but it
// does write a JSONL transcript to disk under ~/.cursor/projects/...,
// so we derive the path from cwd + conversation id.
transcriptPath: deriveCursorTranscriptPath(cwd, sessionId),
filePath: r.file_path,
edits: r.edits,
};
},View on GitHub (pinned to d8bc9755e7)