{"record":{"id":"d48da0d0370357f3","repo":"alibaba/open-code-review","slug":"resume-rejected-model-changed-from-q-to-q-witho","errorCode":null,"errorMessage":"resume rejected: model changed from %q to %q without being asked for; %s to resume across models on purpose","messagePattern":"resume rejected: model changed from %q to %q without being asked for; (.+?) to resume across models on purpose","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/session/resume_identity.go","lineNumber":66,"sourceCode":"// change is reported as the input mismatch it actually caused rather than as an\n// unattributable rule change.\nfunc (s *ResumeState) ValidateResume(req ResumeRequest) error {\n\tif s == nil {\n\t\treturn nil\n\t}\n\tif err := s.validateInputIdentity(req.Identity); err != nil {\n\t\treturn err\n\t}\n\n\tm := s.Manifest\n\tproviderChanged := m.Execution.Provider != req.Provider\n\tif providerChanged && !req.ProviderExplicit {\n\t\treturn fmt.Errorf(\"resume rejected: provider changed from %q to %q without being asked for; %s to resume across providers on purpose\", m.Execution.Provider, req.Provider, explicitFlagHint(\"--provider\", req.Provider))\n\t}\n\t// Model is only compared within the same provider: switching provider on\n\t// purpose necessarily brings that provider's own model with it.\n\tif !providerChanged && m.Execution.Model != req.Model && !req.ModelExplicit {\n\t\treturn fmt.Errorf(\"resume rejected: model changed from %q to %q without being asked for; %s to resume across models on purpose\", m.Execution.Model, req.Model, explicitFlagHint(\"--model\", req.Model))\n\t}\n\treturn nil\n}\n\n// validateInputIdentity compares only the input half of the resume contract: the\n// parent manifest must be verifiable, and every input field must match. Provider\n// and model are deliberately left out: those are command-line intent, not\n// something derived from the input.\n//\n// This runs once, at admission, and is never repeated during the run: the caller\n// pins the run to the commit endpoints this comparison was made against (see\n// agent.SealedInput), so a second comparison could only ever confirm the first.\nfunc (s *ResumeState) validateInputIdentity(id RunIdentity) error {\n\tif s == nil {\n\t\treturn nil\n\t}\n\n\tm := s.Manifest","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/alibaba/open-code-review/blob/5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f/internal/session/resume_identity.go#L48-L84","documentation":"ValidateResume rejects resuming a session whose model changed when the provider stayed the same and the caller did not explicitly pass --model. Model identity is only compared within one provider because switching providers intentionally brings that provider's own model. This prevents accidental model drift between the parent run and the resumed run.","triggerScenarios":"ValidateResume with providerChanged==false, m.Execution.Model != req.Model, and req.ModelExplicit==false — e.g. the configured model was bumped (gpt-4o to gpt-4.1) or a config default changed between the original run and the resume.","commonSituations":"User changes their default model in config or environment after starting a review, then resumes; team updated a shared config file pinning a newer model; a model alias resolves differently across environments.","solutions":["Re-run with --model <new-model> to accept the model change explicitly","Set the model back to the value recorded in the parent session manifest, then resume","Start a new review instead of resuming"],"exampleFix":"// before\nocr review --resume abc123            # model default changed since parent run\n// after\nocr review --resume abc123 --model gpt-4o  # explicit model override","handlingStrategy":"validation","validationCode":"// Verify the model matches the parent manifest before resuming\nif sess.Manifest != nil && !providerChanged && sess.Manifest.Execution.Model != requestedModel && !req.ModelExplicit {\n    fmt.Printf(\"session %s used model %q; pass --model to switch deliberately\\n\",\n        sess.SessionID, sess.Manifest.Execution.Model)\n}","typeGuard":"func resumeModelMatches(s *session.State, want string) bool {\n    return s != nil && s.Manifest != nil && s.Manifest.Execution.Model == want\n}","tryCatchPattern":"if err := ValidateResume(s, req); err != nil {\n    if strings.Contains(err.Error(), \"model changed\") {\n        req.ModelExplicit = true // or re-run with --model\n        return ValidateResume(s, req)\n    }\n    return err\n}","preventionTips":["Pin the model in config; avoid mutable aliases/defaults that drift","Capture the parent run's provider+model in your script and reuse them on resume","Treat provider switches as new runs: switch both flags together explicitly"],"tags":["session","resume","model","cli"],"backgroundTag":"resume-state-mismatch","analyzedSha":"5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f","analyzedAt":"2026-09-02T02:08:09.116Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}