microsoft/typescript-go · error · ErrClientError

%w: failed to stop CPU profile: %w

Error message

%w: failed to stop CPU profile: %w

What it means

stopCPUProfile forwarded the profiler error, wrapped in ErrClientError. The typical cause is stopping when no CPU profile is active (the start failed earlier or was never issued), or an error finalizing/flushing the profile file when profiling stops.

Source

Thrown at internal/api/session.go:882

	default:
		return nil, fmt.Errorf("unknown method: %s", method)
	}
}

func (s *Session) handleStartCPUProfile(_ context.Context, params *ProfileParams) (any, error) {
	if params == nil || params.Dir == "" {
		return nil, fmt.Errorf("%w: dir is required", ErrClientError)
	}
	if err := s.cpuProfiler.StartCPUProfile(params.Dir); err != nil {
		return nil, fmt.Errorf("%w: failed to start CPU profile: %w", ErrClientError, err)
	}
	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

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Only call stopCPUProfile after a startCPUProfile that returned success
  2. Track start/stop state client-side and reset it on any error
  3. Treat a stop error as non-fatal during shutdown; capture the file path only on success

Example fix

// before
startCPUProfile(dir) // error ignored
stopCPUProfile() // "failed to stop"

// after
if err := startCPUProfile(dir); err != nil { return err }
res, err := stopCPUProfile() // only reached when start succeeded
Defensive patterns

Strategy: validation

Validate before calling

// Track profiler state; only stop what you successfully started.
var profiling atomic.Bool
if err := startCPUProfile(dir); err != nil { return err }
profiling.Store(true)
defer func() {
    if profiling.CompareAndSwap(true, false) {
        _, _ = stopCPUProfile()
    }
}()

Type guard

func shouldStopCPU(profiling bool) bool { return profiling }

Try / catch

if _, err := session.HandleRequest(ctx, string(api.MethodStopCPUProfile), nil); err != nil {
    // Most common: no active profile. Treat as non-fatal during shutdown; log and continue.
    log.Printf("stopCPUProfile: %v", err)
}

Prevention

When it happens

Trigger: Calling stopCPUProfile after a startCPUProfile that returned an error; calling stop twice; disk/full-permission problems flushing the profile on stop.

Common situations: Error from start swallowed by client code, so the caller believes a profile is running; shutdown hooks that unconditionally stop profiling; profiling guards implemented with bare flags that drift from real profiler state.

Related errors


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