larksuite/cli · error · LifecycleError
%v
Error message
%v
What it means
callLifecycleSafe in internal/hook/emit.go recovers panics thrown by a lifecycle hook handler and converts them into a *LifecycleError with Panic=true, storing the panic value formatted with fmt.Errorf("%v", r) as Cause. The message "%v" means the visible text is whatever the panic value stringifies to — it may not be an error at all (string, runtime error, nil-map write, etc.). Lifecycle hooks are user/plugin-supplied functions, so the CLI isolates a crash in one hook instead of crashing the process.
Source
Thrown at internal/hook/emit.go:117
// Shutdown errors are logged, not propagated -- exit is
// non-recoverable anyway.
fmt.Fprintf(stderr(), "warning: shutdown hook %q: %v\n", h.Name, err)
}
}
return nil
}
// callLifecycleSafe invokes a LifecycleHandler with panic recovery.
// Returns *LifecycleError with Panic=true on recovered panic, Panic=false
// on a regular returned error. nil if the handler succeeded.
func callLifecycleSafe(ctx context.Context, h LifecycleEntry, lc *platform.LifecycleContext) (err error) {
defer func() {
if r := recover(); r != nil {
err = &LifecycleError{
Event: lc.Event,
HookName: h.Name,
Panic: true,
Cause: fmt.Errorf("%v", r),
}
}
}()
if e := h.Fn(ctx, lc); e != nil {
return &LifecycleError{
Event: lc.Event,
HookName: h.Name,
Panic: false,
Cause: e,
}
}
return nil
}
View on GitHub (pinned to 7fd6ef3c07)
Solutions
- Read LifecycleError.Cause / HookName to identify which hook panicked, then fix the nil-dereference or bad state in that hook's code.
- Make the hook defensive: check nil pointers/maps/slices and closed resources before use, and return an error instead of panicking.
- Move one-shot initialization into Plugin.Install or the Plugin struct so per-hook state is never uninitialized.
- If the hook is third-party, update or remove the plugin; note that on the shutdown path the error is only logged as a warning, so check stderr output.
Example fix
// before (hook that panics)
func(ctx context.Context, lc *platform.LifecycleContext) error {
return state.conn.Close() // panics if state.conn is nil
}
// after
func(ctx context.Context, lc *platform.LifecycleContext) error {
if state == nil || state.conn == nil {
return nil
}
return state.conn.Close()
} Defensive patterns
Strategy: try-catch
Validate before calling
// before registering a hook, smoke-test it with a recovered call:
func safeProbe(fn func(ctx context.Context) error) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("hook panics during probe: %v", r)
}
}()
return fn(context.Background())
} Type guard
func isLifecyclePanic(err error) bool {
var le *hook.LifecycleError
return errors.As(err, &le) && le.Panic
} Try / catch
err := hookEmitter(ctx)
var le *hook.LifecycleError
if errors.As(err, &le) && le.Panic {
log.Printf("hook %s panicked: %v", le.HookName, le.Cause)
// fall back to default shutdown/startup behavior
} Prevention
- Never panic in hook code; return errors so they surface as Panic=false LifecycleErrors.
- Nil-check all captured state (connections, maps, slices) at the top of every hook.
- Initialize long-lived plugin state in Plugin.Install / on the Plugin struct, not lazily in hooks.
- Watch stderr warnings during shutdown — lifecycle panic errors there are logged, not propagated.
When it happens
Trigger: Any LifecycleHandler.Fn (shutdown/startup lifecycle hook, run via emitLifecycle/lifecycle emission) panics: indexing a nil slice, calling methods on a nil pointer, closing a nil channel, or panic("...") inside plugin hook code invoked with (ctx, *platform.LifecycleContext).
Common situations: A plugin's shutdown hook dereferences state that was never initialized; a hook's captured resource was already released by an earlier hook; Go runtime errors like "index out of range" surfacing as the cause string.
Related errors
- hook %q panic: %v
- multiple plugins customized skills; only one plugin may own
- content safety panic: %v
- Hooks.NewArgs is required
- Hooks.NewArgs must return *%s
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/b9e46e38dca46cb6.
Report an issue: GitHub.