MCPcopy Index your code
hub / github.com/coder/coder / CreateStep

Method CreateStep

coderd/x/chatd/chatdebug/service.go:362–432  ·  view source on GitHub ↗

CreateStep inserts a new debug step and emits a step update event. It returns errRunFinalized if the parent run has already finished, or errRunNotFound if the run_id/chat_id pair does not match an existing run. The finalization guard is enforced atomically by the INSERT's CTE, which issues an UPDATE

(
	ctx context.Context,
	params CreateStepParams,
)

Source from the content-addressed store, hash-verified

360// row lock). This prevents concurrent FinalizeStale from setting
361// finished_at between the check and the INSERT.
362func (s *Service) CreateStep(
363 ctx context.Context,
364 params CreateStepParams,
365) (database.ChatDebugStep, error) {
366 now := s.clock.Now()
367 insert := database.InsertChatDebugStepParams{
368 RunID: params.RunID,
369 StepNumber: params.StepNumber,
370 Operation: string(params.Operation),
371 Status: string(params.Status),
372 HistoryTipMessageID: nullInt64(params.HistoryTipMessageID),
373 AssistantMessageID: sql.NullInt64{},
374 NormalizedRequest: s.nullJSON(ctx, params.NormalizedRequest),
375 NormalizedResponse: pqtype.NullRawMessage{},
376 Usage: pqtype.NullRawMessage{},
377 Attempts: pqtype.NullRawMessage{},
378 Error: pqtype.NullRawMessage{},
379 Metadata: pqtype.NullRawMessage{},
380 StartedAt: sql.NullTime{Time: now, Valid: true},
381 UpdatedAt: sql.NullTime{Time: now, Valid: true},
382 FinishedAt: sql.NullTime{},
383 ChatID: params.ChatID,
384 }
385
386 // Cap retry attempts to prevent infinite loops under
387 // pathological concurrency. Each iteration performs two DB
388 // round-trips (insert + list), so 10 retries is generous.
389 const maxCreateStepRetries = 10
390
391 for range maxCreateStepRetries {
392 if err := ctx.Err(); err != nil {
393 return database.ChatDebugStep{}, err
394 }
395
396 step, err := s.db.InsertChatDebugStep(chatdContext(ctx), insert)
397 if err == nil {
398 // The INSERT CTE atomically bumps the parent run's
399 // updated_at, so no separate touch call is needed.
400 s.publishEvent(ctx, step.ChatID, EventKindStepUpdate, step.RunID, step.ID)
401 return step, nil
402 }
403 // The INSERT's locked_run CTE filters on id, chat_id, and
404 // finished_at IS NULL, so sql.ErrNoRows can mean "run not
405 // found", "chat_id mismatch", or "already finalized." Look
406 // the run up to disambiguate instead of conflating
407 // caller-side data bugs with the legitimate terminal case.
408 if errors.Is(err, sql.ErrNoRows) {
409 return database.ChatDebugStep{}, s.classifyMissingRun(ctx, params)
410 }
411 if !database.IsUniqueViolation(err, database.UniqueIndexChatDebugStepsRunStep) {
412 return database.ChatDebugStep{}, err
413 }
414
415 steps, listErr := s.db.GetChatDebugStepsByRunID(chatdContext(ctx), params.RunID)
416 if listErr != nil {
417 return database.ChatDebugStep{}, listErr
418 }
419 nextStepNumber := insert.StepNumber + 1

Calls 11

nullJSONMethod · 0.95
publishEventMethod · 0.95
classifyMissingRunMethod · 0.95
IsUniqueViolationFunction · 0.92
nullInt64Function · 0.85
chatdContextFunction · 0.85
ErrMethod · 0.80
InsertChatDebugStepMethod · 0.65
IsMethod · 0.45
ErrorfMethod · 0.45