Skip to content

fix(agents): avoid injecting memory file twice on case-insensitive mounts#26054

Merged
obviyus merged 3 commits intoopenclaw:mainfrom
Lanfei:fix/memory-bootstrap-dedup
Mar 13, 2026
Merged

fix(agents): avoid injecting memory file twice on case-insensitive mounts#26054
obviyus merged 3 commits intoopenclaw:mainfrom
Lanfei:fix/memory-bootstrap-dedup

Conversation

@Lanfei
Copy link
Copy Markdown
Contributor

@Lanfei Lanfei commented Feb 25, 2026

Summary

  • Problem: On case-insensitive file systems mounted into Docker from macOS, both MEMORY.md and memory.md pass fs.access() even when they are the same underlying file.
  • Why it matters: The previous dedup via fs.realpath() failed in this scenario because realpath does not normalise case through the Docker mount layer, so both paths were treated as distinct entries and the same content was injected into the bootstrap context twice, wasting tokens on every session start.
  • What changed: Replaced collect-then-dedup with early-exit — try MEMORY.md first, fall back to memory.md only when absent. The function now returns at most one entry regardless of filesystem case-sensitivity. Renamed resolveMemoryBootstrapEntriesresolveMemoryBootstrapEntry to reflect the singular return type.
  • What did NOT change: Behaviour on case-sensitive Linux (native or Docker) is identical. memory.md fallback still works. No other bootstrap files affected.

Change Type (select all)

  • Bug fix
  • Refactor

Scope (select all touched areas)

  • Memory / storage

Linked Issue/PR

User-visible / Behavior Changes

On Docker Desktop (macOS host), agents no longer receive duplicate memory context at session start, reducing unnecessary token usage.

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

Repro + Verification

Environment

  • OS: macOS (host), Linux (Docker container)
  • Runtime/container: Docker Desktop on macOS with workspace volume mount
  • Model/provider: any
  • Integration/channel: any

Steps

  1. Create a workspace with a file named memory.md (lowercase)
  2. Mount that workspace into a Docker container running OpenClaw
  3. Start an agent session and inspect bootstrap files

Expected

  • One memory entry injected into context

Actual (before fix)

  • Two memory entries injected with identical content (MEMORY.md and memory.md both pass fs.access(), realpath returns different strings through the mount layer, dedup fails)

Evidence

  • Failing test/log before + passing after — pnpm test src/agents/workspace.test.ts passes (12 tests)

Human Verification (required)

  • Verified scenarios: unit tests cover MEMORY.md present, memory.md fallback, neither present
  • Edge cases checked: early-exit ensures at most one entry on any filesystem
  • What you did not verify: live Docker on macOS manual repro (deduced from fs.realpath behaviour through Docker mount layers)

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert src/agents/workspace.ts to restore collect-then-dedup
  • Files/config to restore: src/agents/workspace.ts, src/agents/workspace.test.ts
  • Known bad symptoms: agent stops loading memory.md fallback (would only happen if fs.access on MEMORY.md erroneously succeeds without the file existing)

Risks and Mitigations

  • Risk: On case-sensitive Linux with both MEMORY.md and memory.md as genuinely different files, only MEMORY.md is now loaded (previously both were loaded).
    • Mitigation: Having two memory files differing only in case is an unusual/unsupported pattern with no documented use case; the original intent was always a single memory file with a case-insensitive fallback.

Greptile Summary

Fixes duplicate memory file injection on case-insensitive Docker mounts by replacing collect-then-deduplicate logic with early-exit strategy. The function now tries MEMORY.md first and only falls back to memory.md if absent, preventing the same content from being injected twice when fs.realpath() fails to normalize case through Docker mount layers. This eliminates token waste on macOS Docker setups while maintaining backward compatibility for the lowercase fallback.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The fix is straightforward and well-tested. The early-exit approach is simpler and more reliable than the previous deduplication logic. All existing tests pass and cover the relevant scenarios. The only behavioral change is for the edge case of having both MEMORY.md and memory.md on case-sensitive systems, which is unsupported and unlikely.
  • No files require special attention

Last reviewed commit: 4adff19

(5/5) You can turn off certain types of comments like style here!

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: XS labels Feb 25, 2026
@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Mar 3, 2026
@Lanfei Lanfei force-pushed the fix/memory-bootstrap-dedup branch from 4adff19 to 0fc9a38 Compare March 3, 2026 06:36
@Lanfei
Copy link
Copy Markdown
Contributor Author

Lanfei commented Mar 3, 2026

Rebased

@openclaw-barnacle openclaw-barnacle bot removed the stale Marked as stale due to inactivity label Mar 4, 2026
@Lanfei
Copy link
Copy Markdown
Contributor Author

Lanfei commented Mar 13, 2026

Hey @jalehman @obviyus — could you take a look when you have a moment? CI is green and the change is small. I currently have several open PRs waiting for review, which is making it hard for me to continue contributing. Any feedback or a quick merge would be much appreciated. Thanks!

@obviyus obviyus self-assigned this Mar 13, 2026
@openclaw-barnacle openclaw-barnacle bot added the docs Improvements or additions to documentation label Mar 13, 2026
Lanfei and others added 3 commits March 13, 2026 14:38
…unts

On case-insensitive file systems mounted into Docker from macOS, both
MEMORY.md and memory.md pass fs.access() even when they are the same
underlying file. The previous dedup via fs.realpath() failed in this
scenario because realpath does not normalise case through the Docker
mount layer, so both paths were treated as distinct entries and the
same content was injected into the bootstrap context twice, wasting
tokens.

Fix by replacing the collect-then-dedup approach with an early-exit:
try MEMORY.md first; fall back to memory.md only when MEMORY.md is
absent. This makes the function return at most one entry regardless
of filesystem case-sensitivity.
@obviyus obviyus force-pushed the fix/memory-bootstrap-dedup branch from 133988e to 5ed80a8 Compare March 13, 2026 09:09
@obviyus obviyus merged commit a3eed2b into openclaw:main Mar 13, 2026
10 checks passed
@obviyus
Copy link
Copy Markdown
Contributor

obviyus commented Mar 13, 2026

Landed on main.

Thanks @Lanfei.

mrosmarin added a commit to mrosmarin/openclaw that referenced this pull request Mar 13, 2026
* main: (168 commits)
  fix: stabilize macos daemon onboarding
  fix(ui): keep shared auth on insecure control-ui connects (openclaw#45088)
  docs(plugins): clarify workspace shadowing
  fix(node-host): harden perl approval binding
  fix(node-host): harden pnpm approval binding
  fix(discovery): add missing domain to wideArea Zod config schema (openclaw#35615)
  chore(gitignore): add docker-compose override (openclaw#42879)
  feat(ios): add onboarding welcome pager (openclaw#45054)
  fix(signal): add groups config to Signal channel schema (openclaw#27199)
  fix: restore web fetch firecrawl config in runtime zod schema (openclaw#42583)
  fix: polish Android QR scanner onboarding (openclaw#45021)
  fix(android): use Google Code Scanner for onboarding QR
  fix(config): add missing params field to agents.list[] validation schema (openclaw#41171)
  docs(contributing): update Android app ownership
  fix(agents): rephrase session reset prompt to avoid Azure content filter (openclaw#43403)
  test(config): cover requiresOpenAiAnthropicToolPayload in compat schema fixture
  fix(agents): respect explicit user compat overrides for non-native openai-completions (openclaw#44432)
  Android: fix HttpURLConnection leak in TalkModeVoiceResolver (openclaw#43780)
  Docker: add OPENCLAW_TZ timezone support (openclaw#34119)
  fix(agents): avoid injecting memory file twice on case-insensitive mounts (openclaw#26054)
  ...
@Lanfei Lanfei deleted the fix/memory-bootstrap-dedup branch March 14, 2026 02:19
@HenryLoenwind
Copy link
Copy Markdown
Contributor

This should have been marked as breaking change, not buried in fixes.

@obviyus
Copy link
Copy Markdown
Contributor

obviyus commented Mar 14, 2026

@HenryLoenwind agreed, that's on me. Will update the changelog.

hougangdev pushed a commit to hougangdev/clawdbot that referenced this pull request Mar 14, 2026
…unts (openclaw#26054)

* fix(agents): avoid injecting memory file twice on case-insensitive mounts

On case-insensitive file systems mounted into Docker from macOS, both
MEMORY.md and memory.md pass fs.access() even when they are the same
underlying file. The previous dedup via fs.realpath() failed in this
scenario because realpath does not normalise case through the Docker
mount layer, so both paths were treated as distinct entries and the
same content was injected into the bootstrap context twice, wasting
tokens.

Fix by replacing the collect-then-dedup approach with an early-exit:
try MEMORY.md first; fall back to memory.md only when MEMORY.md is
absent. This makes the function return at most one entry regardless
of filesystem case-sensitivity.

* docs: clarify singular memory bootstrap fallback

* fix: note memory bootstrap fallback docs and changelog (openclaw#26054) (thanks @Lanfei)

---------

Co-authored-by: Ayaan Zaidi <[email protected]>
@obviyus
Copy link
Copy Markdown
Contributor

obviyus commented Mar 14, 2026

Updated in 64e6df7.

ecochran76 pushed a commit to ecochran76/openclaw that referenced this pull request Mar 14, 2026
…unts (openclaw#26054)

* fix(agents): avoid injecting memory file twice on case-insensitive mounts

On case-insensitive file systems mounted into Docker from macOS, both
MEMORY.md and memory.md pass fs.access() even when they are the same
underlying file. The previous dedup via fs.realpath() failed in this
scenario because realpath does not normalise case through the Docker
mount layer, so both paths were treated as distinct entries and the
same content was injected into the bootstrap context twice, wasting
tokens.

Fix by replacing the collect-then-dedup approach with an early-exit:
try MEMORY.md first; fall back to memory.md only when MEMORY.md is
absent. This makes the function return at most one entry regardless
of filesystem case-sensitivity.

* docs: clarify singular memory bootstrap fallback

* fix: note memory bootstrap fallback docs and changelog (openclaw#26054) (thanks @Lanfei)

---------

Co-authored-by: Ayaan Zaidi <[email protected]>
Interstellar-code pushed a commit to Interstellar-code/operator1 that referenced this pull request Mar 16, 2026
…unts (openclaw#26054)

* fix(agents): avoid injecting memory file twice on case-insensitive mounts

On case-insensitive file systems mounted into Docker from macOS, both
MEMORY.md and memory.md pass fs.access() even when they are the same
underlying file. The previous dedup via fs.realpath() failed in this
scenario because realpath does not normalise case through the Docker
mount layer, so both paths were treated as distinct entries and the
same content was injected into the bootstrap context twice, wasting
tokens.

Fix by replacing the collect-then-dedup approach with an early-exit:
try MEMORY.md first; fall back to memory.md only when MEMORY.md is
absent. This makes the function return at most one entry regardless
of filesystem case-sensitivity.

* docs: clarify singular memory bootstrap fallback

* fix: note memory bootstrap fallback docs and changelog (openclaw#26054) (thanks @Lanfei)

---------

Co-authored-by: Ayaan Zaidi <[email protected]>
(cherry picked from commit a3eed2b)
sbezludny pushed a commit to sbezludny/openclaw that referenced this pull request Mar 27, 2026
…unts (openclaw#26054)

* fix(agents): avoid injecting memory file twice on case-insensitive mounts

On case-insensitive file systems mounted into Docker from macOS, both
MEMORY.md and memory.md pass fs.access() even when they are the same
underlying file. The previous dedup via fs.realpath() failed in this
scenario because realpath does not normalise case through the Docker
mount layer, so both paths were treated as distinct entries and the
same content was injected into the bootstrap context twice, wasting
tokens.

Fix by replacing the collect-then-dedup approach with an early-exit:
try MEMORY.md first; fall back to memory.md only when MEMORY.md is
absent. This makes the function return at most one entry regardless
of filesystem case-sensitivity.

* docs: clarify singular memory bootstrap fallback

* fix: note memory bootstrap fallback docs and changelog (openclaw#26054) (thanks @Lanfei)

---------

Co-authored-by: Ayaan Zaidi <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling docs Improvements or additions to documentation size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants