microsoft/typescript-go · error · ErrClientError

%w: dir is required

Error message

%w: dir is required

What it means

startCPUProfile rejects params that are nil or have an empty Dir field. ProfileParams has a single string field dir; an omitted field decodes to the Go zero value "", which fails this check. The dir is where the server writes the pprof CPU profile file.

Source

Thrown at internal/api/session.go:871

		return s.handleStopCPUProfile(ctx)
	case string(MethodSaveHeapProfile):
		return s.handleSaveHeapProfile(ctx, parsed.(*ProfileParams))
	case string(MethodGetReferencesToSymbolInFile):
		return s.handleGetReferencesToSymbolInFile(ctx, parsed.(*GetReferencesToSymbolInFileParams))
	case string(MethodGetReferencedSymbolsForNode):
		return s.handleGetReferencedSymbolsForNode(ctx, parsed.(*GetReferencedSymbolsForNodeParams))
	case string(MethodGetSignatureUsages):
		return s.handleGetSignatureUsages(ctx, parsed.(*GetSignatureUsagesParams))
	case string(MethodGetCompletionsAtPosition):
		return s.handleGetCompletionsAtPosition(ctx, parsed.(*GetCompletionsAtPositionParams))
	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)

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Pass a non-empty absolute directory path in the dir field
  2. Default the value client-side (e.g. os.TempDir()) before sending when no explicit dir is configured

Example fix

// before
conn.Call(ctx, string(api.MethodStartCPUProfile), api.ProfileParams{})

// after
conn.Call(ctx, string(api.MethodStartCPUProfile), api.ProfileParams{Dir: "/tmp/tsprofiles"})
Defensive patterns

Strategy: validation

Validate before calling

func profileParams(dir string) (api.ProfileParams, error) {
    if dir == "" { dir = os.TempDir() }
    if !filepath.IsAbs(dir) { dir, _ = filepath.Abs(dir) }
    return api.ProfileParams{Dir: dir}, nil
}

Type guard

func hasProfileDir(p *api.ProfileParams) bool {
    return p != nil && p.Dir != ""
}

Prevention

When it happens

Trigger: Sending {} or null as params to startCPUProfile; omitting the dir key in the JSON payload; sending "dir": "" explicitly.

Common situations: Optional-looking params treated as skippable; client defaulting to empty string when a config knob (profile output directory) is unset.

Related errors


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