fix(schedular): a dead calendar workbench resets instead of failing forever - #435
Open
hugolytics wants to merge 1 commit into
Open
fix(schedular): a dead calendar workbench resets instead of failing forever#435hugolytics wants to merge 1 commit into
hugolytics wants to merge 1 commit into
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The incident
google-calendar-mcpcrash-looped from 09:13 today on a zero-bytetokens.json(
JSON.parse("")→ "Unexpected end of JSON input"). The Slack bot started at11: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 tokenfile 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_workbenchcached oneMcpWorkbenchfor the processlifetime, with no health check and no reset:
AutoGen's
McpWorkbench.call_toollazily callsstart()only whenself._actoris falsy. Once an actor exists but its session has died, everylater call raises forever. Nothing in the planner path could recover.
McpCalendarClienthad 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_eventwrites to a real calendar, so blanket retry wasnot an option.
list-events,get-eventdelete-eventcreate-event,update-eventMCP Actor not runningis raised locally, before any request leaves theprocess, 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 skippedacross 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_vsadmonish-1_), and the one actuallymounted got zeroed. Worth reconciling separately — the same split can silently
strand credentials again.
🤖 Generated with Claude Code