charmbracelet/crush · error

session ID is required for creating a new file

Error message

session ID is required for creating a new file

What it means

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.

Source

Thrown at internal/agent/tools/multiedit.go:173

	// Check if file already exists
	if _, err := os.Stat(params.FilePath); err == nil {
		return fantasy.NewTextErrorResponse(fmt.Sprintf("file already exists: %s", params.FilePath)), nil
	} else if !os.IsNotExist(err) {
		return fantasy.ToolResponse{}, fmt.Errorf("failed to access file: %w", err)
	}

	// Create parent directories
	dir := filepath.Dir(params.FilePath)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return fantasy.ToolResponse{}, fmt.Errorf("failed to create parent directories: %w", err)
	}

	currentContent, failedEdits, whitespaceCorrected := applyEditsToContent(firstEdit.NewString, params.Edits[1:], 1)

	// Get session and message IDs
	sessionID := GetSessionFromContext(edit.ctx)
	if sessionID == "" {
		return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for creating a new file")
	}

	// Check permissions
	_, additions, removals := diff.GenerateDiff("", currentContent, strings.TrimPrefix(params.FilePath, edit.workingDir))

	editsApplied := len(params.Edits) - len(failedEdits)
	var description string
	if len(failedEdits) > 0 {
		description = fmt.Sprintf("Create file %s with %d of %d edits (%d failed)", params.FilePath, editsApplied, len(params.Edits), len(failedEdits))
	} else {
		description = fmt.Sprintf("Create file %s with %d edits", params.FilePath, editsApplied)
	}
	p, err := edit.permissions.Request(edit.ctx, permission.CreatePermissionRequest{
		SessionID:   sessionID,
		Path:        fsext.PathOrPrefix(params.FilePath, edit.workingDir),
		ToolCallID:  call.ID,
		ToolName:    MultiEditToolName,
		Action:      "write",

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Ensure the tool executes inside the agent run loop where the session ID is stored in the context
  2. When invoking tools programmatically, inject the session ID into ctx the same way the coordinator does (see GetSessionFromContext/SetSessionFromContext usage)
  3. Retry via the normal agent pipeline rather than calling the tool directly
  4. If hit in tests, set a session ID in the tool context before calling Run

Example fix

// before
resp, _ := tool.Run(ctx, params) // ctx has no session
// after
ctx = session.NewContext(ctx, sessionInfo) // inject session before invoking
resp, _ := tool.Run(ctx, params)
Defensive patterns

Strategy: validation

Validate before calling

if GetSessionFromContext(ctx) == "" {
    return fmt.Errorf("session ID missing from context")
}
// proceed with tool.Run(ctx, params)

Try / catch

if _, err := tool.Run(ctx, params); err != nil {
    if strings.Contains(err.Error(), "session ID is required") {
        ctx = session.NewContext(ctx, sess)
        _, err = tool.Run(ctx, params)
    }
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/3fc6c574eec70b91. Report an issue: GitHub.