fix(api): show user-scope memories in /api/memories listing#124
Merged
Conversation
`handleListMemories` without a tag filter only walked project-scope shards
and discarded any row whose `container_tag` didn't include the literal
substring `_project_`. User-scope memories (canonical tag format
`opencode_user_<sha16>`) were therefore structurally invisible:
- The /api/memories endpoint returned `items: []` even when user-scope
memories existed in the store
- `/api/stats` already counted both scopes correctly (see lines 742-743),
so the listing endpoint disagreed with the stats endpoint about what
exists
- The Web UI navigation, which is built on /api/memories, had no way to
surface user-scope memories at all
Reproduction (current main, before this patch):
```
POST /api/memories body: { "content": "...", "containerTag": "opencode_user_xxx" }
→ { success: true, data: { id: "mem_..." } }
GET /api/stats
→ { byScope: { user: 1, project: 0 }, byType: { fact: 1 } }
GET /api/memories ← BUG: hides the row above
→ { items: [], total: 0 }
GET /api/search?q=... ← finds it (unaffected)
→ { items: [{ id, content, ... }] }
```
Fix: iterate both project- and user-scope shards in the no-tag path and
widen the defense-in-depth filter to match both canonical scope markers
(`_project_` or `_user_`). Behavior with a tag filter is unchanged
because `extractScopeFromTag` still resolves the requested scope.
Verification:
- `bun test`: 143 pass / 0 fail (no existing tests regressed)
- `bun run typecheck`: clean
- `bun run build`: clean
- `npx prettier --check`: clean
- Manually reproduced the bug on v2.15.0 and confirmed this patch resolves
it: user-scope memories now appear in /api/memories the same way they
appear in /api/stats and /api/search.
Note: `handleListTags` (line 105) is intentionally project-only by its
return-shape contract (`{ project: TagInfo[] }`) — that's a separate
design decision, not touched here. If user-scope tag listing is wanted, it
should be a new endpoint or a contract-breaking response-shape change,
neither of which fits this small fix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
handleListMemorieswithout a tag filter only walks project-scope shards and discards any row whosecontainer_tagdoesn't include the literal substring_project_. User-scope memories — canonical tag formatopencode_user_<sha16>— are therefore structurally invisible from this endpoint, even though they store and search correctly.The result is a confusing UX:
POST /api/memories(writing them)GET /api/searchGET /api/stats(byScope.userandbyType)GET /api/memories(listing)/api/memories)Reproduction (against current main, v2.15.0)
Root cause
src/services/api-handlers.ts:161-167:Two parallel filters: shard scope is hardcoded
"project", and the row filter only keeps_project_tags. Both have to be widened.handleStatsat line 742-743 was already correct (counts both_user_and_project_), so this PR just brings the listing endpoint into agreement with what stats reports.Fix
Iterate both project- and user-scope shards in the no-tag path, widen the defense-in-depth filter to match both canonical scope markers. The tagged path (when
tagis provided) is unchanged becauseextractScopeFromTagalready resolves the requested scope correctly.Out of scope
handleListTags(line 105) is intentionally project-only by its return-shape contract ({ project: TagInfo[] }). Exposing user-scope tags there would be a contract-breaking change to the response shape, which doesn't fit a small UX fix. Happy to do that as a follow-up if you want — could either add auserkey to the response or split into/api/tags/project+/api/tags/user.Verification
bun test: 143 pass / 0 fail (no regressions)bun run typecheck: cleanbun run build: cleannpx prettier --check: cleanEnvironment