code-yeongyu / code-yeongyu/senpi

_getCompactEntries() leaks evicted resident-string refs into resumed image data

Open
#1,631 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.