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

Like the download tool, createNewFile requires a session ID from the request context to attach to the permission request and file tracking for the newly created file. An empty session ID is an invariant violation and the tool refuses to create the file.

Source

Thrown at internal/agent/tools/edit.go:125

func createNewFile(edit editContext, filePath, content string, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
	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
		}
		return fantasy.NewTextErrorResponse(fmt.Sprintf("file already exists: %s", filePath)), nil
	} else if !os.IsNotExist(err) {
		return fantasy.ToolResponse{}, fmt.Errorf("failed to access file: %w", err)
	}

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

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

	_, additions, removals := diff.GenerateDiff(
		"",
		content,
		strings.TrimPrefix(filePath, edit.workingDir),
	)
	p, err := edit.permissions.Request(
		edit.ctx,
		permission.CreatePermissionRequest{
			SessionID:   sessionID,
			Path:        fsext.PathOrPrefix(filePath, edit.workingDir),
			ToolCallID:  call.ID,
			ToolName:    EditToolName,
			Action:      "write",
			Description: fmt.Sprintf("Create file %s", filePath),
			Params: EditPermissionsParams{
				FilePath:   filePath,

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Run file creation through the standard agent path, which injects the session ID
  2. In tests, add the session ID to the context before invoking the handler
  3. Audit any code that constructs a fresh context.Context for tool calls and re-attach session values

Example fix

// before
resp, _ := editTool.Handle(context.Background(), params, call)
// after
ctx := WithSessionContext(context.Background(), sessionID)
resp, _ := editTool.Handle(ctx, params, call)
Defensive patterns

Strategy: validation

Validate before calling

if GetSessionFromContext(edit.ctx) == "" {
    return nil, fmt.Errorf("edit tool requires a session-scoped context")
}

Try / catch

resp, err := editTool.Handle(ctx, params, call)
if err != nil && strings.Contains(err.Error(), "session ID is required") {
    return fmt.Errorf("wiring bug: re-invoke via the agent so ctx carries the session ID")
}

Prevention

When it happens

Trigger: The edit tool's createNewFile handler runs with a context lacking the session ID key — direct invocation in tests, a custom agent harness not setting the session, or context being replaced between agent setup and tool execution.

Common situations: Tests calling the tool handler directly without the session context; embedding edit in a custom pipeline that rebuilds the context; regressions in agent wiring after refactors.

Related errors


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