Skip to content

fix(memory): keep qmd session paths roundtrip-safe#57560

Merged
vincentkoc merged 2 commits into
mainfrom
fix/qmd-binary-warning
Mar 30, 2026
Merged

fix(memory): keep qmd session paths roundtrip-safe#57560
vincentkoc merged 2 commits into
mainfrom
fix/qmd-binary-warning

Conversation

@vincentkoc

Copy link
Copy Markdown
Member

Summary

  • Problem: when a QMD-backed file lives under the workspace qmd/ directory, memory_search returned that workspace-relative path, but memory_get treats qmd/<collection>/... as a reserved virtual namespace and therefore rejected the generic qmd/sessions/... alias.
  • Why it matters: QMD session hits could not roundtrip from memory_search into memory_get, which broke normal session-backed recall flows.
  • What changed: search result serialization now preserves workspace-relative paths except when they collide with the reserved qmd/ virtual namespace; in that collision case it returns the collection-scoped virtual path instead.
  • What did NOT change (scope boundary): this does not change normal workspace path handling or QMD read semantics outside the reserved qmd/ prefix collision case.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause / Regression History (if applicable)

  • Root cause: extensions/memory-core/src/memory/qmd-manager.ts preferred workspace-relative paths for hits inside the workspace, but readFile() reserves the qmd/<collection>/... prefix for virtual QMD collection reads. Session exports under ${workspace}/qmd/... therefore serialized to an ambiguous path that memory_get interpreted as the wrong collection.
  • Missing detection / guardrail: there was no regression test for the case where session exports live under the workspace qmd/ directory and the returned path must still be roundtrip-safe.
  • Prior context (git blame, prior PR, issue, or refactor if known): fix: keep QMD session search paths roundtrip-safe #43523 identified the same failure mode on older code.
  • Why this regressed now: current path formatting still allowed real workspace files to collide with the reserved virtual QMD namespace.
  • If unknown, what was ruled out: raw qmd://collection/... file resolution already worked; the breakage is specifically in OpenClaw's search-hit path formatting layer.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
  • extensions/memory-core/src/memory/qmd-manager.test.ts
  • Scenario the test should lock in: when session exports live under the workspace qmd/ directory, memory_search returns qmd/<real-collection>/... and readFile() succeeds with that exact path.
  • Why this is the smallest reliable guardrail: the bug is a pure path-translation mismatch inside QmdMemoryManager, and the existing manager test harness already covers both search-hit formatting and read-path resolution.
  • Existing test that already covers this (if any): adjacent tests already cover qmd:// file URIs and QMD path read safety, but not the reserved-prefix collision case.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

  • memory_get can now read the exact QMD session-hit path returned by memory_search even when exported session markdown lives under the workspace qmd/ directory.

Diagram (if applicable)

Before:
search hit inside workspace qmd/sessions/... -> returns qmd/sessions/... -> memory_get interprets "sessions" as collection name -> fails

After:
search hit inside workspace qmd/sessions/... -> returns qmd/sessions-main/... -> memory_get resolves the real collection -> succeeds

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node 22 / pnpm workspace
  • Model/provider: N/A
  • Integration/channel (if any): memory_search / memory_get
  • Relevant config (redacted): memory.backend=qmd with session indexing enabled and a workspace under the agent state dir

Steps

  1. Place session-export markdown under a workspace qmd/ directory.
  2. Return that hit from QMD search as qmd://sessions-main/....
  3. Feed the returned path back into memory_get.

Expected

  • The returned path is collection-scoped and directly readable.

Actual

  • The old path formatting returned the generic qmd/sessions/... alias, which memory_get rejected as an unknown collection.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: focused QMD manager regression for the workspace-qmd/ collision case plus adjacent qmd:// URI and read-path safety tests.
  • Edge cases checked: normal qmd://collection/... URI resolution still works and non-markdown / symlink reads remain blocked.
  • What you did not verify: live manual run against a real QMD install outside the test harness.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Risks and Mitigations

  • Risk: workspace files under a real qmd/ directory now serialize differently than other workspace-relative files.
    • Mitigation: the change is intentionally limited to the reserved qmd/ namespace that readFile() already treats specially, so it restores roundtrip safety without changing unrelated workspace paths.

@openclaw-barnacle openclaw-barnacle Bot added extensions: memory-core Extension: memory-core size: S maintainer Maintainer-authored PR labels Mar 30, 2026
@greptile-apps

greptile-apps Bot commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a roundtrip safety bug in QmdMemoryManager.buildSearchPath where session exports living under the workspace qmd/ directory serialized to a workspace-relative path (e.g. qmd/sessions/session-1.md) that readFile() then misinterpreted as a virtual QMD namespace with a non-existent collection name (sessions), rather than the real registered collection (sessions-main).

Key changes:

  • extensions/memory-core/src/memory/qmd-manager.ts: Moves sanitized declaration before the isInsideWorkspace branch, and adds an early-return for the collision case — when relativeToWorkspace equals \"qmd\" or starts with \"qmd/\", the function now emits the explicit collection-scoped virtual path (qmd/${collection}/${sanitized}) instead of the ambiguous workspace-relative path. All other paths are unaffected.
  • extensions/memory-core/src/memory/qmd-manager.test.ts: Adds a regression test that wires up sessions: { enabled: true } (session root lands at {stateDir}/agents/{agentId}/qmd/sessions, i.e. inside workspace/qmd/), mocks the QMD binary to return a qmd://sessions-main/… URI, and asserts that both the search result path and the subsequent readFile call resolve correctly end-to-end.
  • CHANGELOG.md: Entry added for the fix.

Confidence Score: 5/5

Safe to merge — targeted one-line guard in a single private method, a clear regression test, and no unrelated behaviour changed.

The fix is minimal and correctly scoped: it only triggers when relativeToWorkspace is exactly qmd or starts with qmd/, which is the exact reserved namespace that readFile() already handles specially. The fallback for non-workspace files is unchanged, normal workspace-relative paths below other directories are untouched, the sanitized variable move is purely mechanical, and the new test traces the full search→readFile roundtrip through the collision path. No P0/P1 issues found.

No files require special attention.

Important Files Changed

Filename Overview
extensions/memory-core/src/memory/qmd-manager.ts Minimal targeted guard added to buildSearchPath: moves sanitized declaration before the isInsideWorkspace branch and returns the explicit collection-scoped virtual path when relativeToWorkspace collides with the reserved qmd/ namespace.
extensions/memory-core/src/memory/qmd-manager.test.ts Adds regression test covering the full search→readFile roundtrip for session exports under the workspace qmd/ directory, plus adds agents.defaults to the shared cfg fixture.
CHANGELOG.md Changelog entry added for the QMD session-hit path roundtrip fix.

Reviews (1): Last reviewed commit: "Merge branch 'main' into fix/qmd-binary-..." | Re-trigger Greptile

@vincentkoc vincentkoc merged commit 118a497 into main Mar 30, 2026
30 of 36 checks passed
@vincentkoc vincentkoc deleted the fix/qmd-binary-warning branch March 30, 2026 09:57
pgondhi987 pushed a commit to pgondhi987/openclaw that referenced this pull request Mar 31, 2026
pgondhi987 pushed a commit to pgondhi987/openclaw that referenced this pull request Mar 31, 2026
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
Tardisyuan pushed a commit to Tardisyuan/openclaw that referenced this pull request Apr 30, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

extensions: memory-core Extension: memory-core maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

QMD session recall roundtrip breaks because memory_search returns source alias instead of real collection name

1 participant