Skip to content

fix(schedular): a dead calendar workbench resets instead of failing forever - #435

Open
hugolytics wants to merge 1 commit into
mainfrom
fix/planner-workbench-retry
Open

fix(schedular): a dead calendar workbench resets instead of failing forever#435
hugolytics wants to merge 1 commit into
mainfrom
fix/planner-workbench-retry

Conversation

@hugolytics

Copy link
Copy Markdown
Collaborator

The incident

google-calendar-mcp crash-looped from 09:13 today on a zero-byte tokens.json
(JSON.parse("")"Unexpected end of JSON input"). The Slack bot started at
11:24 while it was still down.

Every Add to calendar press then failed with MCP Actor not running, call initialize() first — and so did Try again, repeatedly. Restoring the token
file and restarting the container was not enough: the bot stayed broken until it
was restarted too.

Why a transient outage became a permanent failure

PlannerAgent._ensure_workbench cached one McpWorkbench for the process
lifetime, with no health check and no reset:

def _ensure_workbench(self) -> McpWorkbench:
    if self._workbench:
        return self._workbench
    ...

AutoGen's McpWorkbench.call_tool lazily calls start() only when
self._actor is falsy
. Once an actor exists but its session has died, every
later call raises forever. Nothing in the planner path could recover.

McpCalendarClient had already solved exactly this — recoverable-error markers,
a workbench reset, and a retry-once loop. The planner simply never got the same
treatment. This ports it, and moves the shared half somewhere both can read so
they cannot drift apart again.

The write-safety decision

handle_upsert_calendar_event writes to a real calendar, so blanket retry was
not an option.

call retries? why
list-events, get-event yes reads; resending is free
delete-event yes idempotent
create-event, update-event no resets the workbench so the next press works, but never resends

MCP Actor not running is raised locally, before any request leaves the
process, so resending after that marker would in fact be safe. But the same
classifier also matches response timeouts and dropped connections, where the
first request may already have reached Google. One classifier covers all the
markers, so the writes take the conservative branch: a duplicate event on a
real calendar is worse than the bug this fixes.
The reset still happens, so a
user's next Try again meets a healthy workbench.

Tests

Six regression tests, each observed failing before the implementation existed:
one reset and one retry on a recoverable error; immediate propagation with no
reset on a non-recoverable one; a retry budget of exactly one; and a test that
fails if anyone later makes creates retry blindly.

584 passed, 6 skipped across the planner/calendar/mcp/schedular slice.

Not included

The container-side cause is already fixed live and is not code: two compose
files create two token volumes (infra_ vs admonish-1_), and the one actually
mounted got zeroed. Worth reconciling separately — the same split can silently
strand credentials again.

🤖 Generated with Claude Code

…ing dead forever

Today, google-calendar-mcp crash-looped from 09:13 on a zero-byte
tokens.json. The Slack bot started at 11:24 while it was down, and every
"Add to calendar" press failed with "MCP Actor not running, call
initialize() first" -- including "Try again" -- and stayed broken even
after the server recovered. Only a bot restart fixed it.

PlannerAgent cached one McpWorkbench for the process lifetime with no
health check and no reset. AutoGen's McpWorkbench.call_tool only calls
start() when its actor is falsy, so once an actor existed with a dead
session, every later call raised that error forever. McpCalendarClient
(timeboxing) already solved this shape of problem with a recoverable-error
classifier, a workbench reset, and a retry-once loop; PlannerAgent never
got the same treatment.

This pulls the classifier into a new shared, pure module
(fateforger/core/mcp_transport.py) so both clients read it instead of
McpCalendarClient keeping a private copy, and gives PlannerAgent the same
reset-on-recoverable-error behaviour via a new _call_tool_with_retry used
at all six of its workbench.call_tool sites.

Reads (list-events, get-event) and the idempotent delete-event retry once
automatically. create-event/update-event do not: "MCP Actor not running"
is raised locally before any request reaches the server, so it is known
safe there, but the same classifier also matches markers (timeout,
disconnect) that can fire after a write already left the client, and
resending those blindly risks a duplicate event on a real calendar. Those
calls still reset the dead workbench on failure -- fixing the actual
"stays broken until restart" bug -- they just don't auto-resend the
mutating request itself; the next user press gets a working workbench.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants