charmbracelet/crush · error
session_id is required
Error message
session_id is required
What it means
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.
Source
Thrown at internal/agent/tools/write.go:63
func NewWriteTool(
lspManager *lsp.Manager,
permissions permission.Service,
files history.Service,
filetracker filetracker.Service,
workingDir string,
) fantasy.AgentTool {
return fantasy.NewAgentTool(
WriteToolName,
writeDescription,
func(ctx context.Context, params WriteParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.FilePath == "" {
return fantasy.NewTextErrorResponse("file_path is required"), nil
}
sessionID := GetSessionFromContext(ctx)
if sessionID == "" {
return fantasy.ToolResponse{}, fmt.Errorf("session_id is required")
}
filePath := filepathext.SmartJoin(workingDir, params.FilePath)
fileInfo, err := os.Stat(filePath)
if err == nil {
if fileInfo.IsDir() {
return fantasy.NewTextErrorResponse(fmt.Sprintf("Path is a directory, not a file: %s", filePath)), nil
}
modTime := fileInfo.ModTime().Truncate(time.Second)
lastRead := filetracker.LastReadTime(ctx, sessionID, filePath)
if modTime.After(lastRead) {
return 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.",
filePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339))), nil
}
oldContent, readErr := os.ReadFile(filePath)View on GitHub (pinned to 7944b8e522)
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.
Example fix
// before tool.Run(ctx, params) // after ctx = agent.WithSession(ctx, sess.ID) tool.Run(ctx, params)
Defensive patterns
Strategy: validation
Validate before calling
if agent.GetSessionFromContext(ctx) == "" {
return fmt.Errorf("tool call aborted: no session in context")
}
_ = tool.Run(ctx, params) Type guard
func hasSession(ctx context.Context) bool {
return agent.GetSessionFromContext(ctx) != ""
} Try / catch
if _, err := tool.Run(ctx, params); err != nil {
if err.Error() == "session_id is required" {
return fmt.Errorf("tool invoked without session context; wrap ctx with the session helper: %w", err)
}
return err
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- session ID is required for managing todos
- %s
- session ID is required for creating a new file
- session ID is required for accessing files outside working d
- session id missing from context
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/1a9c4ffb2961cb4e.
Report an issue: GitHub.