#12939: fix(memory): strip null bytes from workspace paths causing ENOTDIR
agents
size: XS
## Summary
Fixes QMD boot failure where `MEMORY.md` path contains a trailing null byte (`\0`), causing `ENOTDIR` when the indexer tries to open the file.
## Problem
Some config sources or file system operations introduce trailing null bytes in workspace directory paths. When QMD passes these paths to `fs.open()`, Node rejects with `ENOTDIR`:
```
ENOTDIR: not a directory, open '/home/cosmo/clawd/MEMORY.md\^@'
```
## Changes
**`src/agents/agent-scope.ts`**
- `resolveAgentWorkspaceDir()` now strips null bytes from resolved paths (root fix)
**`src/memory/qmd-manager.ts`**
- QMD manager constructor strips null bytes from workspace dir (defense-in-depth)
**`src/memory/backend-config.ts`**
- `resolveDefaultCollections()` strips null bytes before building collection paths (defense-in-depth)
**Tests:**
- `src/agents/agent-scope.test.ts` — new test verifying null byte stripping
- `src/memory/backend-config.test.ts` — new test verifying collection paths are clean
## Testing
- All 17 agent-scope + backend-config tests pass ✅
- 🤖 AI-assisted (Claude) — fully tested, all changes understood
Closes #12919
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR addresses a boot-time QMD failure where workspace paths can contain trailing NUL ("\0") bytes, causing Node `fs.open()`/path operations to throw `ENOTDIR`.
Changes include:
- Sanitizing agent workspace directory resolution by stripping `\0` bytes in `resolveAgentWorkspaceDir()`.
- Additional defense-in-depth sanitization in the QMD manager constructor and when building default QMD collection roots.
- New tests ensuring configured workspace paths and default collection paths do not contain `\0`.
Overall, the approach fits the codebase by centralizing the main fix in agent workspace resolution and adding targeted safeguards around QMD’s path usage, but there are still a couple call paths where `workspaceDir` can remain unsanitized (default workspace branch and custom QMD paths).
<h3>Confidence Score: 3/5</h3>
- Reasonably safe but still leaves known null-byte path cases unsanitized in some branches.
- Core sanitization is implemented and tests cover configured workspace + default collections, but the default workspace path branch (via OPENCLAW_HOME) is not sanitized and relative custom QMD paths/session export dirs can still inherit a NUL-containing workspace base. Those gaps mean the reported ENOTDIR class of failure can still occur for some configurations.
- src/agents/agent-scope.ts, src/memory/backend-config.ts
<!-- greptile_other_comments_section -->
<sub>(5/5) You can turn off certain types of comments like style [here](https://app.greptile.com/review/github)!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#2884: fix: Create memory directory and symlink identity files during work...
by webdevtodayjason · 2026-01-27
81.2%
#22937: fix: remove legacy unsuffixed QMD collections on upgrade
by sud0n1m-ziggy · 2026-02-21
78.5%
#23085: fix(workspace): respect OPENCLAW_STATE_DIR for workspace paths, fix...
by charojo · 2026-02-22
78.1%
#9624: fix(memory): resolve QMD search returning empty results [AI-assisted]
by kowshik24 · 2026-02-05
77.9%
#11364: fix(memory/qmd): prevent cascading failure when query fails or retu...
by blazerui · 2026-02-07
77.3%
#21240: fix: GH#20607 prevent doctor from dropping custom config sections
by theognis1002 · 2026-02-19
77.1%
#9381: Fix: Allow QMD CLI memory search when scope is restrictive
by vishaltandale00 · 2026-02-05
76.7%
#23480: fix(test): use path.join for cross-platform XDG path assertions in ...
by hydro13 · 2026-02-22
76.6%
#12956: fix: guard .trim() calls on potentially undefined workspaceDir
by omair445 · 2026-02-10
76.4%
#21471: fix: check QMD backend before memory search config
by lbo728 · 2026-02-20
76.4%