microsoft/typescript-go · error · ErrClientError

%w: failed to save heap profile: %w

Error message

%w: failed to save heap profile: %w

What it means

saveHeapProfile forwarded the error from pprof.SaveHeapProfile, wrapped in ErrClientError. SaveHeapProfile creates and writes a file under dir, so failures are file-creation or write failures: missing directory, permission denied, read-only filesystem, or disk full.

Source

Thrown at internal/api/session.go:893

	}
	return nil, nil
}

func (s *Session) handleStopCPUProfile(_ context.Context) (*ProfileResult, error) {
	filePath, err := s.cpuProfiler.StopCPUProfile()
	if err != nil {
		return nil, fmt.Errorf("%w: failed to stop CPU profile: %w", ErrClientError, err)
	}
	return &ProfileResult{File: filePath}, nil
}

func (s *Session) handleSaveHeapProfile(_ context.Context, params *ProfileParams) (*ProfileResult, error) {
	if params == nil || params.Dir == "" {
		return nil, fmt.Errorf("%w: dir is required", ErrClientError)
	}
	filePath, err := pprof.SaveHeapProfile(params.Dir)
	if err != nil {
		return nil, fmt.Errorf("%w: failed to save heap profile: %w", ErrClientError, err)
	}
	return &ProfileResult{File: filePath}, nil
}

// HandleNotification implements Handler.
func (s *Session) HandleNotification(ctx context.Context, method string, params json.Value) error {
	// TODO: Implement notification handling
	return nil
}

func (s *Session) handleInitialize(ctx context.Context) (*InitializeResponse, error) {
	return &InitializeResponse{
		UseCaseSensitiveFileNames: s.projectSession.FS().UseCaseSensitiveFileNames(),
		CurrentDirectory:          s.projectSession.GetCurrentDirectory(),
	}, nil
}

// handleUpdateSnapshot creates a new snapshot, optionally opening or closing

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Create dir and verify writability before issuing the request (write a probe file from the server's user)
  2. Free disk space or choose a volume with headroom comparable to server memory
  3. Use an absolute path to avoid cwd-relative surprises

Example fix

// before
saveHeapProfile("profiles") // relative, may not exist where the server resolves it

// after
os.MkdirAll("/data/tsprofiles", 0o755)
saveHeapProfile("/data/tsprofiles")
Defensive patterns

Strategy: validation

Validate before calling

func ensureWritableDir(dir string) error {
    if err := os.MkdirAll(dir, 0o755); err != nil { return err }
    probe := filepath.Join(dir, ".probe")
    if err := os.WriteFile(probe, nil, 0o644); err != nil { return err }
    return os.Remove(probe)
}
// Call ensureWritableDir(params.Dir) from the server's user context before saveHeapProfile.

Try / catch

if _, err := session.HandleRequest(ctx, string(api.MethodSaveHeapProfile), params); err != nil {
    if strings.Contains(err.Error(), "heap profile") {
        // dir problem or disk full: check space and permissions, then retry once after fixing
    }
    return err
}

Prevention

When it happens

Trigger: Passing a dir that does not exist; server process lacking write permission to dir; disk exhaustion during the heap dump (heap profiles of large compile servers can be hundreds of MB); path pointing at a file instead of a directory.

Common situations: Profiling production containers with read-only layers; dirs created in a different mount namespace; disk pressure on CI runners.

Related errors


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/dbec9cc6cf5c4abb. Report an issue: GitHub.