code-yeongyu / code-yeongyu/senpi
_getCompactEntries() leaks evicted resident-string refs into resumed image data
- Dominant language
- TypeScript
- Stars
- 429
- Forks
- 98
- Avg merge
- 5h 3m
- Merged PRs (30d)
- 526
Description
### What happened?
Reopening an image-heavy persisted session can replace valid `ImageContent.data` with an internal token such as `\0senpi-resident-string:v1:71`. The Codex Responses converter then emits `data:image/png;base64,\0senpi-resident-string:v1:71`, and the turn fails with:
`Invalid 'input[11].output[1].image_url' ... invalid base64-encoded value.`
I reproduced this directly through core `SessionManager`, without extensions or an API call. In a 155 MB session containing 97 valid raw-base64 image blocks, `SessionManager.open(...).buildSessionContext()` returned 35 image blocks containing resident-reference tokens. One persisted 2,785,804-character PNG base64 value became the 28-character token above.
### Steps to reproduce
1. Persist a session whose valid image/tool-result strings exceed the 64 MiB resident-string budget.
2. Reopen it with `SessionManager.open(path)` and call `buildSessionContext()`.
3. Inspect image blocks in the returned messages.
4. Pass that context through `convertResponsesMessages()`.
The reconstructed context contains `senpi-resident-string` tokens, which become invalid data URLs.
### Suspected cause
`_getCompactEntries()` reloads missing entries, then calls `residentStore.externalize(persisted)` into the same bounded FIFO cache. That recovery pass can evict strings still needed later. The final `materialize(entry)` call has no missing-value callback, so the internal reference itself escapes. The separate `materializeSessionEntries()` helper avoids re-externalizing recovered persisted entries.
This path is still present in `main` and `v2026.9.12-3`.
Related but distinct: #1407 fixed repeated history reads; #1260 covers double-prefixed data URLs. The persisted images in this reproduction had no data-URL prefix and all passed strict base64 validation.
### Expected behavior
Session reconstruction must preserve authoritative persisted strings regardless of resident-cache eviction. Internal resident references must never reach provider payloads.
### Version
`@code-yeongyu/senpi 2026.9.11` via `omo-ai 5.0.0-0.beta.55`; Node.js 26.8.1; macOS arm64.
Contributor guide
Research direction
Start at SessionManager.open(), buildSessionContext(), and _getCompactEntries(), then compare their recovery behavior with materializeSessionEntries(). Reproduce the persisted image case and pass the rebuilt context through convertResponsesMessages(); done means authoritative strings survive resident-cache eviction and no senpi-resident-string token reaches the provider payload.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 63/100