[BUG] Cowork (web): synced plugin re-sync moves hook scripts under a live session; every prompt is then blocked permanently
Concerns Cowork on claude.ai (web), cloud sessions, not the CLI. Container CC version 2.1.287. CLI-only template fields: not applicable.
What happened
A long-running Cowork cloud session (created 2026-10-01 10:44Z, ~444k context, idle between 15:17Z and 23:26Z) loads an org-synced plugin whose hooks include UserPromptSubmit scripts. When it resumed at 23:26Z, the hook commands still pointed at the plugin's old synced directory:
/root/.claude/plugins/synced/<org>_<id>/answering-aviv-gate~g2/hooks/scripts/ensure-answering-aviv.py
A re-sync had already replaced that directory with .../answering-aviv-gate/ (no ~g2 suffix). Each hook then fails with:
python3: can't open file '.../answering-aviv-gate~g2/hooks/scripts/...py': [Errno 2] No such file or directory
python3 exits with code 2 on a missing file, and a UserPromptSubmit hook that exits 2 means "block". So every user message gets UserPromptSubmit operation blocked by hook and a zero-duration result, and the model never runs. Three prompts in a row were rejected this way, the session status stayed idle / review_ready, and nothing in the UI told the user why.
Why it's a bug
- The resolved plugin path changes under a running session, but the session keeps the old hook paths. It should re-resolve them, or keep the old directory until the session ends.
- A hook whose script file is missing is a broken install, not a deliberate "block" decision. It should fail open (or show a visible error), not silently brick the session.
- The user can't recover from inside the session, because every message, including
/plugins-style commands, goes through the blocked hook.
Expected
- Hook paths follow the plugin's current synced location, or stay valid for the life of the session.
- A missing hook command surfaces as a hook error, separate from exit-code-2 blocking.
- The session UI shows that prompts are being blocked by a hook.
Repro sketch
- Install an org-synced plugin with a
UserPromptSubmithook that runspython3 ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/x.py. - Start a Cowork cloud session and leave it idle.
- Publish a new plugin version so the synced directory name changes.
- Send a message in the old session: it gets blocked, with no reply.
Related, but different: #92421 (a failed plugin load is invisible inside a session).
3 Comments
Happened again, 8 Oct 2026 05:03 to 05:57 UTC, on a claude.ai chat backed by a code session (resumed with SessionStart:resume). The plugin had been re-synced from
answering-aviv-gate~g2to a new folder; the resumed session kept the old${CLAUDE_PLUGIN_ROOT}, so every UserPromptSubmit hook failed withpython3: can't open file '.../answering-aviv-gate~g2/hooks/scripts/closing-signal.py': [Errno 2], exit 2, and every user message got a blank reply with no error shown in the UI. The user's message was an instruction ("merge PR 1238"), so it was silently never acted on.Two asks: (1) on resume, re-resolve CLAUDE_PLUGIN_ROOT to the plugin's current folder; (2) treat a hook whose command file is missing as a hook error shown to the user, not as exit 2 = block, or at least render the block reason in the chat so a blank reply is never the only symptom.
Recurred 8 Oct 2026, claude.ai chat (Opus 5.5 upgraded conversation, claude_code_version 2.1.294), session
cse_01VHAFMwKp66Fezv8E4AW3fc. Two consecutive prompts (16:54:08Z and 17:00:58Z) were refused with "A plugin or hook stopped this message"; the second attempt ran in a resumed container (session_mode: resume) and was still blocked, so the stale registration survives a resume.From the session's own
/events:.../plugins/synced/<org>_<id>/answering-aviv-gate/hooks/scripts/*.py(exit 0), and once from a generation path.../answering-aviv-gate~g3/hooks/scripts/*.pythat no longer exists.~g3copies fail withpython3: can't open file ... [Errno 2] No such file or directory, which python reports as exit code 2, which Claude Code treats as a UserPromptSubmit block:UserPromptSubmit operation blocked by hook: [python3 .../answering-aviv-gate~g3/hooks/scripts/repeat-ask-brief.py].resultshowsnum_turns: 0; the assistant message is stored withstop_reason: errorand empty content.So the re-sync replaces the plugin directory while a running session keeps the old
~gNpath registered, and a missing hook script is indistinguishable from an intentional block. Two asks: (1) re-resolve or drop hook registrations whose plugin root has been replaced, at least on resume; (2) a hook command that fails because its script file is missing should be treated as a non-blocking error, not exit-2 block.Correction to my comment above (6065065756). On a closer read, the stale
~g3commands in that session were our own registration: a helper script of ours copies the plugin's hooks into~/.claude/settings.jsonwith${CLAUDE_PLUGIN_ROOT}expanded to the absolute generation path. That file survives a resume, and the~gNfolder does not survive a re-sync. Which is why the same hooks fired twice, once natively from the live path (exit 0) and once from the dead path (exit 2), and why it still blocked on 2.1.295 after a fresh resume at 04:30Z on 9 Oct. We fixed it on our side.What still stands as a platform issue: a hook command that fails because its script file is missing exits 2 and is treated as a deliberate block, with only "A plugin or hook stopped this message" shown in the chat UI and no way to see or clear the offending hook from a claude.ai chat. A missing-file failure as non-blocking, plus naming the hook in the banner, would have made this a one-minute fix.