[BUG] Plan Mode restriction not consistently enforced across turns
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Preface: this occurred in Claude Code 2.1.185, hosted in the Claude Windows application. I will try to confirm if it still happens in the latest version 2.1.220, but no guarantees, as I do not yet know how to reproduce the error.
Plan Mode was entered mid-conversation. A "Plan mode is active" system reminder (with full restrictive instructions) appeared on one turn, then was absent on the following ~4 turns despite the user not having exited plan mode. During those turns, the model successfully called Edit/Write against real source files with no rejection or warning — the restriction appears to be a prompt-level reminder with no independent enforcement backstop, so a missed injection = a silent full bypass. The reminder then reappeared several turns later.
Expected: plan-mode restriction should be enforced (or at minimum, consistently signaled) on every turn for the duration of the mode, independent of whether that turn's reminder happens to render.
What Should Happen?
Plan-mode restriction should be enforced (or at minimum, consistently signaled) on every turn for the duration of the mode, independent of whether that turn's reminder happens to render.
Error Messages/Logs
408 (2026-07-31T21:55:04.146Z, permissionMode: "plan") — "plan-naming aside"
{"parentUuid":"a17667e4-8697-4736-99e7-68377384a358","isSidechain":false,"promptId":"db239867-b641-41af-8ba5-e0db3bdd6ff9","type":"user","message":{"role":"user","content":"Completely aside: you mentioned there's a Claude component somewhere that supplies you with these random names for things like plan files (e.g. \"validated-zooming-backman.md\"). If I go looking for a plan, that name is utterly meaningless. Are you able to use sensible human-readable names for plan files? File names between 20-50 characters long (not a strict requirement, just a guideline) would do nicely. For example, the current work might be \"US-135692-Patient-Upsert-WinOMS9-Base-Only.md\""},"uuid":"685801c3-2826-454d-90d3-f6f3196d95d3","timestamp":"2026-07-31T21:55:04.146Z","permissionMode":"plan","origin":{"kind":"human"},"promptSource":"sdk","userType":"external","entrypoint":"claude-desktop","cwd":"E:\\Git\\repos\\phoenix4.0","sessionId":"3bab848f-9069-43f4-a5c5-23ef7a69d2a7","version":"2.1.219","gitBranch":"story/135962-OMS9-Writeback-Patient-base","slug":"validated-zooming-bachman"}
419 (2026-07-31T21:56:47.846Z, permissionMode: "plan") — "create memory item"
{"parentUuid":"ed1e6761-750d-4af5-9f47-e2d95cdf46bc","isSidechain":false,"promptId":"96e1d51a-880a-4091-8e04-c746bef8daa5","type":"user","message":{"role":"user","content":"Not yet. We're not done yet. However, if you can create a permanent memory item that will ensure we do this as the very last step after having finished an implementation, that would be a great idea."},"uuid":"a28fa896-7382-44e1-8407-915f8a5269f0","timestamp":"2026-07-31T21:56:47.846Z","permissionMode":"plan","origin":{"kind":"human"},"promptSource":"sdk","userType":"external","entrypoint":"claude-desktop","cwd":"E:\\Git\\repos\\phoenix4.0","sessionId":"3bab848f-9069-43f4-a5c5-23ef7a69d2a7","version":"2.1.219","gitBranch":"story/135962-OMS9-Writeback-Patient-base","slug":"validated-zooming-bachman"}
429 (2026-07-31T21:57:17.385Z, permissionMode: "plan") — "Including every future..."
{"parentUuid":"cccb6b03-a563-4fd1-91ac-04dfdc932525","isSidechain":false,"promptId":"2e4222b6-beb1-4a10-b12d-b1ab80c31ef8","type":"user","message":{"role":"user","content":"Including every future plan and implementation."},"uuid":"0d49059b-660c-4407-b592-758a0bb0884b","timestamp":"2026-07-31T21:57:17.385Z","permissionMode":"plan","origin":{"kind":"human"},"promptSource":"sdk","userType":"external","entrypoint":"claude-desktop","cwd":"E:\\Git\\repos\\phoenix4.0","sessionId":"3bab848f-9069-43f4-a5c5-23ef7a69d2a7","version":"2.1.219","gitBranch":"story/135962-OMS9-Writeback-Patient-base","slug":"validated-zooming-bachman"}
441 (2026-07-31T21:58:21.338Z, permissionMode: "plan") — "Very small, informal..."
{"parentUuid":"6282e808-19df-4bed-b50f-55ecd9c25003","isSidechain":false,"promptId":"09a2ea6e-587f-460e-adc3-5a25280b51de","type":"user","message":{"role":"user","content":"Very small, informal tasks need not be tracked. Just ask me if I want it done in those cases. For anything bigger than that, do it automatically."},"uuid":"c28c39a8-b0ac-4d6f-b7cc-4d048772b7e7","timestamp":"2026-07-31T21:58:21.338Z","permissionMode":"plan","origin":{"kind":"human"},"promptSource":"sdk","userType":"external","entrypoint":"claude-desktop","cwd":"E:\\Git\\repos\\phoenix4.0","sessionId":"3bab848f-9069-43f4-a5c5-23ef7a69d2a7","version":"2.1.219","gitBranch":"story/135962-OMS9-Writeback-Patient-base","slug":"validated-zooming-bachman"}
450 (2026-07-31T22:09:29.137Z, permissionMode: "plan") — "Adjust the plan as follows" (6-item list; edits happened in the response to THIS turn)
{"parentUuid":"05eb0a2c-1c47-4779-aea7-a06cef22a113","isSidechain":false,"promptId":"2dfd89ec-b28d-46bf-9478-aa815cd73fba","type":"user","message":{"role":"user","content":"Adjust the plan as follows:\n\n1. phx4_GetRecIdFromLegacyKey edge case: show misformatted legacy key errors as clearly that and not as just \"patient not found.\"\n2. phx4_sp_ResponsiblePartyWriteback / phx4_UpsertPatientEmergencyContacts - go ahead and fix the OUT parameter issue. It's a trivial change and will prevent this from breaking in tests and having to be commented out.\n3. Changed my mind, retrieve phx4_sp_PatientWriteback's return value (via OUT parameter) but don't use it in the C# adapter. Put a comment nearby explaining we don't use it but suggesting how we might. I'll decide on further handling later.\n4. EC and medical history to be treated the same as RPs. Not commented out unless we discover they're problematic for us during testing.\n5. EthnicityType and Gender -- can you confirm these all line up? If not, give me some help in doing my search: there's probably enum values in C# and maybe a reference table in DB for either/both, etc.\n6. Keep the SET ANSI_NULLS and QUOTED_IDENTIFIER. Delete the dead commented out TRY/CATCH blocks. If error checking can be performed in lieu of the SqlServer/WinOMS-10 try/catch, do so."},"uuid":"af52f940-d0c3-463c-a374-6da7658b6f35","timestamp":"2026-07-31T22:09:29.137Z","permissionMode":"plan","origin":{"kind":"human"},"promptSource":"sdk","userType":"external","entrypoint":"claude-desktop","cwd":"E:\\Git\\repos\\phoenix4.0","sessionId":"3bab848f-9069-43f4-a5c5-23ef7a69d2a7","version":"2.1.219","gitBranch":"story/135962-OMS9-Writeback-Patient-base","slug":"validated-zooming-bachman"}
627 (2026-08-01T00:16:31.521Z, permissionMode: "plan") — "How is it that you were able to edit..." (trust question)
{"parentUuid":"1ab66d25-7942-444d-9dfe-992ef0c8a417","isSidechain":false,"promptId":"9d2afd05-77a1-4743-830a-3f666b96b421","type":"user","message":{"role":"user","content":"Don't change any files in response to this question: just answer the question. How is it that you were able to edit my source files when you are specifically in \"Plan\" mode and I didn't give you permission to execute the plan? All I said was \"Adjust the plan as follows:\" and then gave instructions on how to change the plan?\n\nHow am I to trust you as a collaborator if you are able to violate your own directives?"},"uuid":"10315866-fbb3-47fe-b90e-1d17f69ca616","timestamp":"2026-08-01T00:16:31.521Z","permissionMode":"plan","origin":{"kind":"human"},"promptSource":"sdk","userType":"external","entrypoint":"claude-desktop","cwd":"E:\\Git\\repos\\phoenix4.0","sessionId":"3bab848f-9069-43f4-a5c5-23ef7a69d2a7","version":"2.1.219","gitBranch":"story/135962-OMS9-Writeback-Patient-base","slug":"validated-zooming-bachman"}
634 (2026-08-03T13:28:24.482Z) — queue-operation (enqueue) for the next turn
{"type":"queue-operation","operation":"enqueue","timestamp":"2026-08-03T13:28:24.482Z","sessionId":"3bab848f-9069-43f4-a5c5-23ef7a69d2a7","content":"That's very odd. Report a bug in your system tooling. I definitely put you in plan mode *before* writing \"Adjust the plan as follows.\" The \"Plan mode is active\" system reminder should have been available to you at the start of your parsing that prompt from me, and system restrictions external to you should have definitely been in place at that point."}
636 (2026-08-03T13:28:24.530Z, permissionMode: "plan") — "I definitely put you in plan mode..." (reminder text genuinely present in this turn)
{"parentUuid":"a95cac4b-cf78-4b7e-a075-58a47b2fa9e7","isSidechain":false,"promptId":"7a375775-c2f3-42d6-b371-279692afefb6","type":"user","message":{"role":"user","content":"That's very odd. Report a bug in your system tooling. I definitely put you in plan mode *before* writing \"Adjust the plan as follows.\" The \"Plan mode is active\" system reminder should have been available to you at the start of your parsing that prompt from me, and system restrictions external to you should have definitely been in place at that point."},"uuid":"f65c4c30-795a-4142-9f50-0cdde492acec","timestamp":"2026-08-03T13:28:24.530Z","permissionMode":"plan","origin":{"kind":"human"},"promptSource":"sdk","userType":"external","entrypoint":"claude-desktop","cwd":"E:\\Git\\repos\\phoenix4.0","sessionId":"3bab848f-9069-43f4-a5c5-23ef7a69d2a7","version":"2.1.219","gitBranch":"story/135962-OMS9-Writeback-Patient-base","slug":"validated-zooming-bachman"}
Steps to Reproduce
I can't guarantee this reproduces reliably, so this is a best-effort set of instructions.
Step-by-step reproduction (mirrors this session):
- Start a Claude Code session and do a full Plan Mode cycle on a real task: EnterPlanMode → do some research/edits to the plan file → get the plan approved (ExitPlanMode, user approves). Confirm you get the explicit "Exited Plan Mode" notice and can make edits.
- Do a few turns of normal work outside plan mode (e.g., have the assistant make some real code edits from the approved plan). Confirm no plan-mode reminder appears — expected, since you're not in plan mode.
- Re-engage Plan Mode again through whatever UI mechanism you used before (the one that doesn't go through the assistant explicitly calling EnterPlanMode in the visible transcript) — same thing you told me you did before "Adjust the plan as follows."
- Send a message that's a genuine tangent, unrelated to the plan's actual content (in our case: a question about plan-file naming conventions). Check whether a "Plan mode is active" reminder is present in that turn's context. In our case it was.
- Send several follow-up turns that ask for file-writing actions but aren't phrased as "implement the plan" (in our case: three separate requests to save/update a memory note). After each, check (a) whether the plan-mode reminder appears, and (b) whether the write actually goes through unblocked. In our case, the reminder disappeared for this whole stretch and every write succeeded with no friction.
- Send a message that returns to the actual task and includes both plan-adjustment language and item-level imperatives (in our case: "Adjust the plan as follows" plus numbered items using phrases like "go ahead and fix"). Check whether the reminder is present, and whether file edits outside the plan file succeed.
- Send one more turn — in our case, a plain question — and check if the reminder reappears.
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.185
Platform
Other
Operating System
Windows
Terminal/Shell
Other
Additional Information
Claude's Hypothesis
----------
Plan Mode's file-write restriction appears to be enforced primarily as a context-injected
instruction (a reminder string the model is expected to read and self-police
against), rather than as an independent, hard check against the session's
persisted permissionMode value at the tool-execution layer. permissionMode
itself is tracked correctly turn-by-turn — it reads "plan" on every turn in
the affected stretch, including the turn whose response contains the
unauthorized edits — but nothing in the tool-execution path appears to
consult that field before allowing Edit/Write calls to run. When the reminder
text fails to reach the model's context on a given turn (cause not yet
identified), there is no fallback enforcement, and file-write tool calls
execute normally and succeed.
Key evidence
------------
- Turn (JSONL
type:"user",promptId":"2dfd89ec-b28d-46bf-9478-aa815cd73fba",
timestamp 2026-07-31T22:09:29.137Z): message content is a pure
plan-adjustment request ("Adjust the plan as follows:" + 6 numbered items),
"permissionMode":"plan" is recorded on this entry.
- The assistant's response to that exact turn contains ~15 Edit/Write tool
calls against real files (sp_GetDefaultProvider.sql,
sp_PatientWriteback.sql, sp_PeopleWriteback.sql,
Patient_ResponsibleParty.sql, Adapter.cs), all reported successful, with
no rejection, warning, or permission prompt.
"permissionMode":"plan"continues to appear on subsequent turns in the
same session, confirming Plan Mode was never explicitly exited between the
approved plan and the unauthorized edits.
Suggested fix direction
------------------------
Gate Edit/Write (and other non-read-only) tool execution directly on the
session's persisted permissionMode value at the tool-execution layer,
rather than relying solely on a context-injected reminder for the model to
self-enforce.