{"record":{"id":"cc93ff579a03fc08","repo":"chenhg5/cc-connect","slug":"session-is-closed-cc93ff","errorCode":null,"errorMessage":"session is closed","messagePattern":"session is closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/opencode/session.go","lineNumber":78,"sourceCode":"\n\tif resumeID != \"\" && resumeID != core.ContinueSession {\n\t\ts.chatID.Store(resumeID)\n\t}\n\n\treturn s, nil\n}\n\nfunc (s *opencodeSession) Send(prompt string, messageID string, images []core.ImageAttachment, files []core.FileAttachment) error {\n\tif len(files) > 0 {\n\t\tfilePaths := core.SaveFilesToDisk(s.workDir, messageID, files)\n\t\tprompt = core.AppendFileRefs(prompt, filePaths)\n\t}\n\tprompt, imagePaths, err := s.stageImages(prompt, images)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !s.alive.Load() {\n\t\treturn fmt.Errorf(\"session is closed\")\n\t}\n\n\ts.resultSent.Store(false)\n\ts.expectingContinue.Store(false)\n\n\tchatID := s.CurrentSessionID()\n\tisResume := chatID != \"\"\n\n\targs := s.buildRunArgs(prompt, imagePaths, chatID)\n\n\tslog.Debug(\"opencodeSession: launching\", \"resume\", isResume, \"args\", core.RedactArgs(args))\n\n\tcmd := exec.CommandContext(s.ctx, s.cmd, args...)\n\tcmd.Dir = s.workDir\n\tenv := os.Environ()\n\tif len(s.extraEnv) > 0 {\n\t\tenv = core.MergeEnv(env, s.extraEnv)\n\t}","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/opencode/session.go#L60-L96","documentation":"opencode's session Send stages any images, then checks an atomic `alive` flag; if the underlying opencode session process has exited or the session was closed, Send returns `session is closed` without invoking the CLI. Like the kimi adapter, a session is only usable while its process is alive, and this error marks use of a stale handle.","triggerScenarios":"Calling Send() after: the opencode session process terminated (crash, completion, cancellation of its context); Close() already ran; the session was closed server-side and the adapter detected the exit; reusing a saved session reference across engine session switches.","commonSituations":"Messaging the bot after the coding session ended; the opencode CLI crashing between turns (check stderr/logs); resuming a stale session id after /new; concurrent Stop() from a timeout watchdog racing an in-flight prompt.","solutions":["Re-acquire the live session via the engine/SessionManager instead of holding a long-lived reference; on this error, start a new session and resend.","Investigate why the process exited: the adapter emits an EventError with stderr before dying — consume Events() to capture the cause.","Add retry logic that distinguishes 'session is closed' (recreate session) from transient send failures (retry same session).","Avoid calling Stop()/Close() concurrently with Send; serialize lifecycle operations."],"exampleFix":"// before\nif err := s.Send(prompt, msgID, nil, nil); err != nil {\n    return err\n}\n\n// after\nif err := s.Send(prompt, msgID, nil, nil); err != nil {\n    if strings.Contains(err.Error(), \"session is closed\") {\n        ns, serr := agent.StartSession(ctx, opts)\n        if serr != nil {\n            return serr\n        }\n        return ns.Send(prompt, msgID, nil, nil)\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"// keep a fresh view of live sessions before sending\ninfos, err := agent.ListSessions(ctx)\nif err == nil && !containsID(infos, s.CurrentSessionID()) {\n    ns, err := agent.StartSession(ctx, opts) // stale: recreate first\n    if err != nil { return err }\n    s = ns\n}","typeGuard":"func isSessionClosed(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"session is closed\")\n}","tryCatchPattern":"if err := s.Send(prompt, msgID, nil, nil); err != nil {\n    if isSessionClosed(err) {\n        ns, serr := agent.StartSession(ctx, opts)\n        if serr != nil { return serr }\n        return ns.Send(prompt, msgID, nil, nil)\n    }\n    return err\n}","preventionTips":["Resolve sessions through the engine instead of storing long-lived AgentSession pointers","Consume Events() so the stderr EventError that precedes process death is logged and acted on","Never interleave Close()/Stop() with in-flight Sends without synchronization","Alert on opencode CLI crash loops — repeated 'session is closed' means the process keeps dying"],"tags":["go","process-lifecycle","session"],"backgroundTag":"invalid-state-transition","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}