You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[bug] The summariser stores transient platform state as permanent memory — an instance still carries "write access is not enabled" while its own prompt says consent is GRANTED #495
An instance is still carrying "Write access to terminal connector is not enabled" — four days, and one fix, after it stopped being true
GET /v1/instances/cda75e28-cace-4958-ac3e-6a7528e6b719/memory, read live today. Every entry below
is source: "summary" — none was written by a user or by write_memory:
key
content
updatedAt
fact:Write access to terminal connector:is not enabled
"Write access to terminal connector is not enabled Write access to terminal connector"
2026-08-10T07:13:25Z
fact:tmux sessions:exist
"tmux sessions exist five"
2026-08-07T06:29:08Z
fact:tmux session creation:failed due to write access issue
"tmux session creation failed due to write access issue true"
2026-08-07T06:29:08Z
fact:User initiated tmux session:started tmux
"User initiated tmux session started tmux tmux"
2026-08-10T08:12:39Z
fact:GitHub issue documentation:instructed Claude Code to document everything through GitHub issues
"GitHub issue documentation instructed Claude Code to document everything through GitHub issues GitHub issues"
2026-08-10T08:12:39Z
fact:commit-strategy:commit-strategy
"commit-strategy commit-strategy Always push to main"
2026-08-10T10:30:54Z
Four of the six are transient runtime state, stored as permanent knowledge. One of them
("five tmux sessions exist") was false within the hour. One of them
("write access is not enabled") was false within 84 seconds — the same instance's trace shows the
write tool terminal_new_target succeeding at 06:30:32, having been recorded as blocked at 06:29:08 —
and it is still in memory, and still in every prompt, today.
What it cost, verified in the transcript
2026-08-07 07:15:19Z, agent_trace tool log for that turn:
✅ read_memory [{"key":"fact:Write access to terminal connector:is not enabled","type":"knowledge", …
Eight minutes later, 07:23:33Z, asked whether it could send a command to tmux:
"Yes — I can see the heartfull-tmux session is live and Claude Code is running inside it. I just need write access to be enabled on the connector, which based on past attempts has been blocked — you can check that in the Settings tab."
And again at 23:37:22Z:
"I can see two sessions running but no tmux session, and write access is currently blocked so I can't send commands directly. To enable this, please go to the Settings tab and turn on write access for the terminal connector."
The tool log for that second turn records one execution, terminal_list_targets. It did not try.
Write consent was granted (it still reads writeConsent: "granted" on all five write tools today, and
the agent had successfully written at 06:30 and 06:32, and wrote again on 08-10).
This half of the incident is already fixed and I am not re-filing it.#399 diagnosed exactly this
message and shipped the resolved consent label — connector-tool-prompt.ts:60-72
(" [write — consent GRANTED, you may call this]") and CONSENT_RULE at :174-182. Verified: the
refusal has not recurred on this instance since. What #399 could not reach is the false belief that
was already written to durable storage, and the generator that keeps producing more of them.
The mechanism
workers/api/src/agent-storage/summaries.ts
:108 — the extraction instruction: "Extract key facts about the user, their preferences,
decisions made, and information shared." Nothing distinguishes a durable fact about the user from
a reading of the platform's own momentary state.
:156-168 — anything with confidence >= 0.8 is written to mem:fact:{subject}:{predicate}
(:157, key truncated at 100 chars, which is why the keys read as truncated sentences) with source: "summary" (:165) and content built as ${fact.subject} ${fact.predicate} ${fact.object}
— the join that produces "commit-strategy commit-strategy Always push to main" and "tmux sessions
exist five". No TTL, no expiry, no re-confirmation.
workers/api/src/agent-do.ts:922-925 — getAllMemory() lists everymem: entry, unbounded and
unfiltered.
workers/api/src/agent-think.ts:295-304 — each is injected verbatim on every turn under ## Your Memory, with provenance limited to a (user-set) marker for the user's own entries and no date at all:
The same file that fixed #399 already names the trap it is now on the wrong side of
(connector-tool-prompt.ts:126-129):
"Reading them per turn rather than caching is deliberate: a consent granted mid-conversation must
take effect on the next message, and the failure mode of a stale cache here is exactly the bug — an
agent insisting it is blocked while the console shows the switch on."
The consent read was made stale-proof, and the summariser reintroduced a stale copy of the same state
by a different door — one that outlives the conversation, unlike a cache. Right now this instance's
prompt contains both[write — consent GRANTED, you may call this] and - [knowledge] fact:Write access to terminal connector:is not enabled: …, on every turn.
summaries.ts:88-92 already recognises the general danger, in the comment #406 left there:
"the chat window ages an invented turn out after ten messages, whereas a summary distils it into fact:* memory entries that are injected into EVERY future prompt and outlive the conversation
that produced them. A fabrication that reaches this function stops being a message and becomes a
belief."
The same sentence is true of a true-at-the-time observation. #406 hardened the input (redaction);
nothing bounds the lifetime of what is extracted.
Same family as #440 ("a transport failure is stored as the repo's state, and no verdict is ever
re-taken").
What to do — cheapest first
Step 1 — date the entries (one line, no data migration). Summary-derived facts are the only
entries in the block with no provenance beyond their absence of (user-set). Render them as what
they are:
- [knowledge] fact:… (noted automatically on 7 Aug, may be out of date — prefer what your tools return now): …
This is the consentLabel pattern applied to memory: state the fact, including how old it is. It
costs nothing, needs no schema change, and makes a four-day-old reading lose to a live tool result
instead of competing with it.
Step 2 — stop extracting platform state. Amend the extraction instruction at :108 to exclude
the platform's own resolved state — permissions, connection status, session inventories, tool
outcomes — because all of those are already answered authoritatively elsewhere in the prompt every
turn (CONNECTED TOOLS consent labels, runner status, live session lists). A model instruction is not
a guarantee, which is why it is step 2 and not step 1.
Step 3 — a volatile class, or a cap (design). Either mark summary-derived entries volatile and
drop them from injection after N days unless re-confirmed, or bound the block (it is unbounded today
and grows for the life of the instance). Needs an owner decision on N and on whether an aged-out entry
is deleted or merely un-injected; I would un-inject and keep, so a Memory tab audit still shows what
the agent once believed.
Alternatives considered and rejected
Delete every source:"summary" entry. Rejected: the same mechanism produced fact:commit-strategy … Always push to main, which is a real standing preference the owner stated
twice and relies on. The problem is lifetime and provenance, not extraction.
Raise the confidence threshold above 0.8. Rejected: "write access is not enabled" was true
when extracted. Confidence is orthogonal to volatility.
Verified: the live memory dump; the two refusal turns and the read_memory tool call that preceded
them (agent_trace); the successful write tool calls that bracket them; the five file:line
citations; that writeConsent reads granted on all five write tools today.
Inferred: that the memory entry, rather than something else, produced the two refusals. The turn's
tool log shows the entry was read into context eight minutes before the first refusal and the refusal
repeats its content; I did not observe the assembled prompt. The prompt-level contradiction that
exists today is not inferred — both blocks are built from inputs I read directly.
An instance is still carrying "Write access to terminal connector is not enabled" — four days, and one fix, after it stopped being true
GET /v1/instances/cda75e28-cace-4958-ac3e-6a7528e6b719/memory, read live today. Every entry belowis
source: "summary"— none was written by a user or bywrite_memory:fact:Write access to terminal connector:is not enabledfact:tmux sessions:existfact:tmux session creation:failed due to write access issuefact:User initiated tmux session:started tmuxfact:GitHub issue documentation:instructed Claude Code to document everything through GitHub issuesfact:commit-strategy:commit-strategyFour of the six are transient runtime state, stored as permanent knowledge. One of them
("five tmux sessions exist") was false within the hour. One of them
("write access is not enabled") was false within 84 seconds — the same instance's trace shows the
write tool
terminal_new_targetsucceeding at 06:30:32, having been recorded as blocked at 06:29:08 —and it is still in memory, and still in every prompt, today.
What it cost, verified in the transcript
2026-08-07 07:15:19Z,
agent_tracetool log for that turn:Eight minutes later, 07:23:33Z, asked whether it could send a command to tmux:
And again at 23:37:22Z:
The tool log for that second turn records one execution,
terminal_list_targets. It did not try.Write consent was granted (it still reads
writeConsent: "granted"on all five write tools today, andthe agent had successfully written at 06:30 and 06:32, and wrote again on 08-10).
This half of the incident is already fixed and I am not re-filing it. #399 diagnosed exactly this
message and shipped the resolved consent label —
connector-tool-prompt.ts:60-72(
" [write — consent GRANTED, you may call this]") andCONSENT_RULEat:174-182. Verified: therefusal has not recurred on this instance since. What #399 could not reach is the false belief that
was already written to durable storage, and the generator that keeps producing more of them.
The mechanism
workers/api/src/agent-storage/summaries.ts:108— the extraction instruction: "Extract key facts about the user, their preferences,decisions made, and information shared." Nothing distinguishes a durable fact about the user from
a reading of the platform's own momentary state.
:156-168— anything withconfidence >= 0.8is written tomem:fact:{subject}:{predicate}(
:157, key truncated at 100 chars, which is why the keys read as truncated sentences) withsource: "summary"(:165) and content built as${fact.subject} ${fact.predicate} ${fact.object}— the join that produces "commit-strategy commit-strategy Always push to main" and "tmux sessions
exist five". No TTL, no expiry, no re-confirmation.
workers/api/src/agent-do.ts:922-925—getAllMemory()lists everymem:entry, unbounded andunfiltered.
workers/api/src/agent-think.ts:295-304— each is injected verbatim on every turn under## Your Memory, with provenance limited to a(user-set)marker for the user's own entries andno date at all:
The composition, and why it is still live
The same file that fixed #399 already names the trap it is now on the wrong side of
(
connector-tool-prompt.ts:126-129):The consent read was made stale-proof, and the summariser reintroduced a stale copy of the same state
by a different door — one that outlives the conversation, unlike a cache. Right now this instance's
prompt contains both
[write — consent GRANTED, you may call this]and- [knowledge] fact:Write access to terminal connector:is not enabled: …, on every turn.summaries.ts:88-92already recognises the general danger, in the comment #406 left there:The same sentence is true of a true-at-the-time observation. #406 hardened the input (redaction);
nothing bounds the lifetime of what is extracted.
Same family as #440 ("a transport failure is stored as the repo's state, and no verdict is ever
re-taken").
What to do — cheapest first
Step 1 — date the entries (one line, no data migration). Summary-derived facts are the only
entries in the block with no provenance beyond their absence of
(user-set). Render them as whatthey are:
This is the
consentLabelpattern applied to memory: state the fact, including how old it is. Itcosts nothing, needs no schema change, and makes a four-day-old reading lose to a live tool result
instead of competing with it.
Step 2 — stop extracting platform state. Amend the extraction instruction at
:108to excludethe platform's own resolved state — permissions, connection status, session inventories, tool
outcomes — because all of those are already answered authoritatively elsewhere in the prompt every
turn (CONNECTED TOOLS consent labels, runner status, live session lists). A model instruction is not
a guarantee, which is why it is step 2 and not step 1.
Step 3 — a volatile class, or a cap (design). Either mark summary-derived entries
volatileanddrop them from injection after N days unless re-confirmed, or bound the block (it is unbounded today
and grows for the life of the instance). Needs an owner decision on N and on whether an aged-out entry
is deleted or merely un-injected; I would un-inject and keep, so a Memory tab audit still shows what
the agent once believed.
Alternatives considered and rejected
source:"summary"entry. Rejected: the same mechanism producedfact:commit-strategy … Always push to main, which is a real standing preference the owner statedtwice and relies on. The problem is lifetime and provenance, not extraction.
when extracted. Confidence is orthogonal to volatility.
behaviourStrayPromptdoes (Agent Behaviour: stop character leaking into Memory; migrate preference:* and responseStyle #226). Rejected as thefirst move: that pattern asks the model to migrate an entry it is asked about, which works for a
misfiled preference but not for a fact whose wrongness is exactly what stops it being questioned.
Must not regress
summaries.ts:159-160,if (existing?.source === "user") continue;) — the rule Agent Behaviour: stop character leaking into Memory; migrate preference:* and responseStyle #226/[bug] Behaviour self-heal can delete a user-set memory entry, contradicting the never-delete-user-set rule #230 established. Any change here must preserve it, and step 3 must notage out a user-set entry.
redactFabricatedHistory([bug] A caught fabrication is only caught once — the invented turns stay in history and the model re-reads them as fact #406) runs before extraction (summaries.ts:94). Keep it first.not touch
maybeSummarize's resume logic.Acceptance criteria
## Your Memoryblock carries a date for every non-user entry.gain a staleness marker.
source:"summary"entry that contradicts a resolved consent label,the agent calls the granted tool rather than refusing (behavioural eval, in the shape [enhancement] Operator-agent interactive-CLI protocol in seed prompt + behavioral-eval scenario for the dropped-first-message failure #483
established).
Evidence trail
Verified: the live memory dump; the two refusal turns and the
read_memorytool call that precededthem (
agent_trace); the successful write tool calls that bracket them; the fivefile:linecitations; that
writeConsentreadsgrantedon all five write tools today.Inferred: that the memory entry, rather than something else, produced the two refusals. The turn's
tool log shows the entry was read into context eight minutes before the first refusal and the refusal
repeats its content; I did not observe the assembled prompt. The prompt-level contradiction that
exists today is not inferred — both blocks are built from inputs I read directly.