charmbracelet/crush · error
panic: %s
Error message
panic: %s
What it means
This is the panic-recovery path in MCP client initialization. When a goroutine initializing an MCP client panics with a string value, the recover() converts it into an error via fmt.Errorf("panic: %s", v). The MCP server session is then marked StateError and the panic is logged instead of crashing the whole application.
Source
Thrown at internal/agent/tools/mcp/init.go:619
// Shared by Initialize and Reinitialize so the panic-to-state policy
// lives in one place. wg, if non-nil, is Done when the attempt finishes
// (success or failure); Initialize uses it to await startup. The goroutine
// captures the server's generation at launch so a concurrent teardown
// invalidates its result rather than letting it register a stale session.
func goInitClient(ctx context.Context, cfg *config.ConfigStore, name string, m config.MCPConfig, wg *sync.WaitGroup) {
gen := currentGen(name)
go func() {
if wg != nil {
defer wg.Done()
}
defer func() {
if r := recover(); r != nil {
var err error
switch v := r.(type) {
case error:
err = v
case string:
err = fmt.Errorf("panic: %s", v)
default:
err = fmt.Errorf("panic: %v", v)
}
updateState(name, StateError, err, nil, Counts{})
slog.Error("Panic in MCP client initialization", "error", err, "name", name)
}
}()
start := time.Now()
err := initClient(ctx, cfg, name, m, gen, cfg.Resolver())
slog.Debug(
"MCP client initialization finished",
"name", name,
"duration", time.Since(start).Truncate(time.Millisecond).String(),
"error", err,
)
}()
}
View on GitHub (pinned to 7944b8e522)
Solutions
- Check the slog output for 'Panic in MCP client initialization' to get the panic string and stack context
- Reproduce with the specific MCP server config to identify which server's init panics
- Upgrade the MCP SDK / transport library, the panic is usually inside library code
- Validate the server config (command, args, env, url) so the library never receives invalid input
Example fix
// before: init goroutine panics with a string and crashes
panic("unexpected nil transport")
// after: this recover already exists; ensure config is validated before starting
if strings.TrimSpace(cfg.Command) == "" { return errors.New("empty command") } Defensive patterns
Strategy: try-catch
Type guard
func asError(r any) error {
switch v := r.(type) {
case error: return v
case string: return fmt.Errorf("panic: %s", v)
default: return fmt.Errorf("panic: %v", v)
}
} Try / catch
// the library already recovers; callers should treat StateError sessions as dead
sess, ok := sessions.Get(name)
if !ok || sess.State == StateError {
slog.Warn("MCP session unavailable after panic", "name", name)
} Prevention
- Avoid panic() for error signaling in custom transports and handlers
- Keep MCP SDK/transport libraries up to date
- Validate server configs before starting clients
- Monitor the 'Panic in MCP client initialization' log line
When it happens
Trigger: Any panic in the MCP init goroutine where the panic value is a plain string, e.g. panic("conn closed") or a library calling panic with a string. The deferred recover at the top of the init function catches it and formats this message.
Common situations: Third-party MCP SDK code panicking on malformed server responses; nil-map or nil-pointer bugs inside custom transports; stdio subprocess dying in a way that makes the client library panic with a string.
Related errors
- panic: %v
- command execution panic: %v
- command execution panic: %v
- interactive OAuth authorization required
- failed to start OAuth callback listener: all candidate ports
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/22ddca09a0e8f0cf.
Report an issue: GitHub.