{"record":{"id":"3fc6c574eec70b91","repo":"charmbracelet/crush","slug":"session-id-is-required-for-creating-a-new-file-3fc6c5","errorCode":null,"errorMessage":"session ID is required for creating a new file","messagePattern":"session ID is required for creating a new file","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/multiedit.go","lineNumber":173,"sourceCode":"\t// Check if file already exists\n\tif _, err := os.Stat(params.FilePath); err == nil {\n\t\treturn fantasy.NewTextErrorResponse(fmt.Sprintf(\"file already exists: %s\", params.FilePath)), nil\n\t} else if !os.IsNotExist(err) {\n\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"failed to access file: %w\", err)\n\t}\n\n\t// Create parent directories\n\tdir := filepath.Dir(params.FilePath)\n\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"failed to create parent directories: %w\", err)\n\t}\n\n\tcurrentContent, failedEdits, whitespaceCorrected := applyEditsToContent(firstEdit.NewString, params.Edits[1:], 1)\n\n\t// Get session and message IDs\n\tsessionID := GetSessionFromContext(edit.ctx)\n\tif sessionID == \"\" {\n\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"session ID is required for creating a new file\")\n\t}\n\n\t// Check permissions\n\t_, additions, removals := diff.GenerateDiff(\"\", currentContent, strings.TrimPrefix(params.FilePath, edit.workingDir))\n\n\teditsApplied := len(params.Edits) - len(failedEdits)\n\tvar description string\n\tif len(failedEdits) > 0 {\n\t\tdescription = fmt.Sprintf(\"Create file %s with %d of %d edits (%d failed)\", params.FilePath, editsApplied, len(params.Edits), len(failedEdits))\n\t} else {\n\t\tdescription = fmt.Sprintf(\"Create file %s with %d edits\", params.FilePath, editsApplied)\n\t}\n\tp, err := edit.permissions.Request(edit.ctx, permission.CreatePermissionRequest{\n\t\tSessionID:   sessionID,\n\t\tPath:        fsext.PathOrPrefix(params.FilePath, edit.workingDir),\n\t\tToolCallID:  call.ID,\n\t\tToolName:    MultiEditToolName,\n\t\tAction:      \"write\",","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/agent/tools/multiedit.go#L155-L191","documentation":"The new-file path of MultiEdit requires a session ID so the created file can be linked to file history (files.Create). The session ID is read from the request context; when it is absent the tool refuses to create the file instead of silently losing history. This indicates the tool was invoked outside a proper agent session context.","triggerScenarios":"processMultiEditWithCreation runs with params.FilePath pointing to a non-existent file and GetSessionFromContext(edit.ctx) returns \"\" — i.e. the tool executed without a session-scoped context (direct/manual tool invocation, lost context values, or a harness that forgot to stash the session ID).","commonSituations":"Calling the tool from tests or custom code without WithContext session injection; a refactor moved tool construction outside the session scope; MCP or subagent plumbing dropping the session from the context chain.","solutions":["Ensure the tool executes inside the agent run loop where the session ID is stored in the context","When invoking tools programmatically, inject the session ID into ctx the same way the coordinator does (see GetSessionFromContext/SetSessionFromContext usage)","Retry via the normal agent pipeline rather than calling the tool directly","If hit in tests, set a session ID in the tool context before calling Run"],"exampleFix":"// before\nresp, _ := tool.Run(ctx, params) // ctx has no session\n// after\nctx = session.NewContext(ctx, sessionInfo) // inject session before invoking\nresp, _ := tool.Run(ctx, params)","handlingStrategy":"validation","validationCode":"if GetSessionFromContext(ctx) == \"\" {\n    return fmt.Errorf(\"session ID missing from context\")\n}\n// proceed with tool.Run(ctx, params)","typeGuard":null,"tryCatchPattern":"if _, err := tool.Run(ctx, params); err != nil {\n    if strings.Contains(err.Error(), \"session ID is required\") {\n        ctx = session.NewContext(ctx, sess)\n        _, err = tool.Run(ctx, params)\n    }\n}","preventionTips":["Always invoke tools through the agent/coordinator pipeline","Keep a single helper that builds tool contexts with session info","Add a test asserting session presence before tool Run","Don't rebuild context.Context from scratch when wrapping tool calls"],"tags":["session","context","tool"],"backgroundTag":"missing-session-id-in-context","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}