From 9f662bf08a169b873ea084e653eb43a29c533af7 Mon Sep 17 00:00:00 2001 From: David Porter Date: Mon, 15 Jun 2026 23:53:36 -0700 Subject: [PATCH] scheduler: fix nil LastCompletionResult after migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CreateSchedulerFromMigration stored whatever LastCompletionResult the migrated V1 state carried, which is nil when the schedule had no prior completion (e.g. migrated before its first action) — convertLastCompletionLegacyToCHASM returns nil in that case. InvokerExecuteTaskHandler.startWorkflow then dereferences lastCompletionState.Success/.Failure unconditionally, panicking on the first workflow start after migration. The normal CreateScheduler/NewScheduler path defaults to a non-nil empty &schedulerpb.LastCompletionResult{}, so it never hit this. Default to the same non-nil empty value in CreateSchedulerFromMigration. Found by the V2->V1->V2 migration round-trip test. Co-Authored-By: Claude Opus 4.8 (1M context) (cherry picked from commit 23ad378b19a638df2603c47c0e3a806e9877bf2e) --- chasm/lib/scheduler/scheduler.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/chasm/lib/scheduler/scheduler.go b/chasm/lib/scheduler/scheduler.go index 4514b6af17..5e281f5cda 100644 --- a/chasm/lib/scheduler/scheduler.go +++ b/chasm/lib/scheduler/scheduler.go @@ -274,11 +274,19 @@ func CreateSchedulerFromMigration( ) (*Scheduler, error) { state := req.GetState() + // Default to a non-nil empty result, matching NewScheduler. Migrated state + // may carry no completion result (e.g. a schedule migrated before its first + // action), and startWorkflow dereferences LastCompletionResult unconditionally. + lastCompletion := state.GetLastCompletionResult() + if lastCompletion == nil { + lastCompletion = &schedulerpb.LastCompletionResult{} + } + sched := &Scheduler{ SchedulerState: state.GetSchedulerState(), cacheConflictToken: state.GetSchedulerState().GetConflictToken(), Backfillers: make(chasm.Map[string, *Backfiller]), - LastCompletionResult: chasm.NewDataField(ctx, state.GetLastCompletionResult()), + LastCompletionResult: chasm.NewDataField(ctx, lastCompletion), EventLog: chasm.NewComponentField(ctx, NewEventLog(ctx)), } sched.setNullableFields()