{"record":{"id":"47f900151246cf5d","repo":"gastownhall/beads","slug":"erridletimeout","errorCode":"errIdleTimeout","errorMessage":"idle timeout reached","messagePattern":"idle timeout reached","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"info","filePath":"internal/storage/dbproxy/proxy/server.go","lineNumber":92,"sourceCode":"const LockHeldExitCode = 75\n\n// ErrLockHeld is returned from ListenAndServe when another proxy already\n// holds proxy.lock for the same rootDir. It is a normal \"lost the race\"\n// outcome, not a failure: callers spawned as children should map it to\n// LockHeldExitCode and exit cleanly.\nvar ErrLockHeld = errors.New(\"proxy lock held by another proxy on this rootDir\")\n\nconst (\n\tserverReadyTimeout     = 30 * time.Second\n\treadyDialTimeout       = 2 * time.Second\n\treadyInitialBackoff    = 50 * time.Millisecond\n\treadyMaxBackoff        = 1 * time.Second\n\tidleWatcherMinInterval = 1 * time.Second\n\tbackendStopTimeout     = 5 * time.Minute\n\ttcpKeepAlivePeriod     = 30 * time.Second\n)\n\nvar errIdleTimeout = errors.New(\"idle timeout reached\")\n\nfunc NewProxyServer(opts ProxyOpts) *proxyServer {\n\treturn &proxyServer{\n\t\trootDir:     opts.RootDir,\n\t\tport:        opts.Port,\n\t\tidleTimeout: opts.IdleTimeout,\n\t\tserver:      opts.Server,\n\t\tstats:       opts.Stats,\n\t\tstopEpoch:   opts.StopEpoch,\n\t}\n}\n\nfunc (p *proxyServer) tracef(format string, args ...any) {\n\tp.logger.Printf(format, args...)\n}\n\nfunc (p *proxyServer) ListenAndServe(parentCtx context.Context) error {\n\tlock, err := util.TryLock(filepath.Join(p.rootDir, LockFileName))","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/proxy/server.go#L74-L110","documentation":"errIdleTimeout is returned by the proxy's idleWatcher when no client activity occurs for the configured idle timeout. The proxy then shuts itself down (including the backend) as a resource-saving measure. It is an internal lifecycle sentinel, not an operational fault.","triggerScenarios":"Running ListenAndServe with a short ProxyOpts.IdleTimeout and letting no clients connect/dial for that duration; long-running sessions with a quiet period exceeding the timeout.","commonSituations":"Long think-time pauses in agent workflows; background daemon stopped unexpectedly by the idle watcher; misconfigured IdleTimeout (e.g. seconds instead of minutes).","solutions":["Increase IdleTimeout in ProxyOpts to cover expected quiet periods","Set a long/very large idle timeout for daemons that must stay up","Reconnect on demand: the proxy is designed to restart when a client needs it"],"exampleFix":"// before\nsrv := proxy.NewProxyServer(proxy.ProxyOpts{RootDir: root, IdleTimeout: 30 * time.Second})\n// after\nsrv := proxy.NewProxyServer(proxy.ProxyOpts{RootDir: root, IdleTimeout: 30 * time.Minute})","handlingStrategy":"try-catch","validationCode":"if opts.IdleTimeout != 0 && opts.IdleTimeout < time.Minute {\n    opts.IdleTimeout = time.Minute // floor for interactive workloads\n}","typeGuard":null,"tryCatchPattern":"if err := srv.ListenAndServe(ctx); err != nil {\n    if errors.Is(err, errIdleTimeout) || strings.Contains(err.Error(), \"idle timeout\") {\n        return nil // expected self-shutdown; clients will restart on demand\n    }\n    return err\n}","preventionTips":["Size IdleTimeout to your longest expected quiet period","Treat idle shutdown as normal lifecycle, not failure","Keep clients resilient: reconnect transparently when the proxy is down"],"tags":["go","timeout","lifecycle"],"backgroundTag":"idle-timeout","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}