{"record":{"id":"1a9c4ffb2961cb4e","repo":"charmbracelet/crush","slug":"session-id-is-required","errorCode":null,"errorMessage":"session_id is required","messagePattern":"session_id is required","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/write.go","lineNumber":63,"sourceCode":"\nfunc NewWriteTool(\n\tlspManager *lsp.Manager,\n\tpermissions permission.Service,\n\tfiles history.Service,\n\tfiletracker filetracker.Service,\n\tworkingDir string,\n) fantasy.AgentTool {\n\treturn fantasy.NewAgentTool(\n\t\tWriteToolName,\n\t\twriteDescription,\n\t\tfunc(ctx context.Context, params WriteParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {\n\t\t\tif params.FilePath == \"\" {\n\t\t\t\treturn fantasy.NewTextErrorResponse(\"file_path is required\"), nil\n\t\t\t}\n\n\t\t\tsessionID := GetSessionFromContext(ctx)\n\t\t\tif sessionID == \"\" {\n\t\t\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"session_id is required\")\n\t\t\t}\n\n\t\t\tfilePath := filepathext.SmartJoin(workingDir, params.FilePath)\n\n\t\t\tfileInfo, err := os.Stat(filePath)\n\t\t\tif err == nil {\n\t\t\t\tif fileInfo.IsDir() {\n\t\t\t\t\treturn fantasy.NewTextErrorResponse(fmt.Sprintf(\"Path is a directory, not a file: %s\", filePath)), nil\n\t\t\t\t}\n\n\t\t\t\tmodTime := fileInfo.ModTime().Truncate(time.Second)\n\t\t\t\tlastRead := filetracker.LastReadTime(ctx, sessionID, filePath)\n\t\t\t\tif modTime.After(lastRead) {\n\t\t\t\t\treturn fantasy.NewTextErrorResponse(fmt.Sprintf(\"File %s has been modified since it was last read.\\nLast modification: %s\\nLast read: %s\\n\\nPlease read the file again before modifying it.\",\n\t\t\t\t\t\tfilePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339))), nil\n\t\t\t\t}\n\n\t\t\t\toldContent, readErr := os.ReadFile(filePath)","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/agent/tools/write.go#L45-L81","documentation":"The write tool requires a session ID embedded in the tool-call context to attribute file writes (and their history) to a session. GetSessionFromContext returned an empty string, meaning the context was built without a session. This is an internal invariant violation rather than a user-facing input problem.","triggerScenarios":"Calling the write tool's Run with a context.Context that was not populated via the session-context helper (e.g. invoking the tool directly in tests, custom harnesses, or code paths that construct tool calls manually without the session value).","commonSituations":"Unit/integration tests that invoke tools directly; custom agent runners or MCP-style drivers that skip the coordinator's context setup; code refactors that pass a bare ctx instead of the one produced by the agent pipeline.","solutions":["Populate the session ID in the context before invoking the tool, using the same helper the agent pipeline uses (WithSession/GetSessionFromContext pair).","If driving tools manually, wrap the context with the session ID from the created session before each tool call.","If you own the calling code, assert sessionID != \"\" before dispatching tool calls to fail fast with a clearer message."],"exampleFix":"// before\ntool.Run(ctx, params)\n\n// after\nctx = agent.WithSession(ctx, sess.ID)\ntool.Run(ctx, params)","handlingStrategy":"validation","validationCode":"if agent.GetSessionFromContext(ctx) == \"\" {\n    return fmt.Errorf(\"tool call aborted: no session in context\")\n}\n_ = tool.Run(ctx, params)","typeGuard":"func hasSession(ctx context.Context) bool {\n    return agent.GetSessionFromContext(ctx) != \"\"\n}","tryCatchPattern":"if _, err := tool.Run(ctx, params); err != nil {\n    if err.Error() == \"session_id is required\" {\n        return fmt.Errorf(\"tool invoked without session context; wrap ctx with the session helper: %w\", err)\n    }\n    return err\n}","preventionTips":["Always construct tool contexts through the agent pipeline helpers, never pass bare context.Background().","Add an assertion/helper in test harnesses that wraps ctx with a session ID before invoking tools.","Centralize tool dispatch in one function that guarantees session context."],"tags":["go","context","session","tooling"],"backgroundTag":"missing-session-id","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}