{"record":{"id":"84be42b5e271ac8a","repo":"alibaba/open-code-review","slug":"resume-rejected-provider-changed-from-q-to-q-wi","errorCode":null,"errorMessage":"resume rejected: provider changed from %q to %q without being asked for; %s to resume across providers on purpose","messagePattern":"resume rejected: provider changed from %q to %q without being asked for; (.+?) to resume across providers on purpose","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/session/resume_identity.go","lineNumber":61,"sourceCode":"// every rejection.\n//\n// Checks run top to bottom and the first mismatch decides. Order matters at\n// source_artifact vs rule_config: rule_config_sha256 is a single aggregate over\n// the rule-text layers and the file filter and cannot be decomposed, so a filter\n// 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 {","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/alibaba/open-code-review/blob/5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f/internal/session/resume_identity.go#L43-L79","documentation":"ValidateResume guards `ocr review --resume` against silently resuming a session under a different LLM provider than the original run. When the stored manifest's provider differs from the request's provider and the caller did not explicitly pass --provider, resume is rejected so config drift cannot quietly change which model vendor performs the review. Passing --provider explicitly signals the switch was intentional.","triggerScenarios":"Calling ValidateResume (via `ocr review --resume <id>`) where s.Manifest.Execution.Provider differs from req.Provider while req.ProviderExplicit is false — e.g. the default provider in config changed since the parent session, or an env var like a provider key switched the effective provider.","commonSituations":"User upgrades open-code-review or edits config to a new default provider, then resumes an old session; CI environment has a different provider configured than the machine that started the run; user toggles between providers (e.g. openai vs anthropic) between runs.","solutions":["Re-run with --provider <new-provider> to confirm you want to switch providers mid-session","Restore the original provider in config/env so it matches the parent session, then resume","Start a fresh review instead of resuming (as the resumeHint suggests)"],"exampleFix":"// before\nocr review --resume abc123            # provider drifted in config\n// after\nocr review --resume abc123 --provider anthropic  # explicit switch","handlingStrategy":"validation","validationCode":"// Check the stored session's provider before resuming\nif sess.Manifest != nil && sess.Manifest.Execution.Provider != requestedProvider && !req.ProviderExplicit {\n    fmt.Printf(\"session %s used provider %q; pass --provider %q to switch deliberately\\n\",\n        sess.SessionID, sess.Manifest.Execution.Provider, requestedProvider)\n}","typeGuard":"func resumeProviderMatches(s *session.State, want string) bool {\n    return s != nil && s.Manifest != nil && s.Manifest.Execution.Provider == want\n}","tryCatchPattern":"if err := ValidateResume(s, req); err != nil {\n    if strings.Contains(err.Error(), \"provider changed\") {\n        req.ProviderExplicit = true // or re-run with --provider\n        return ValidateResume(s, req)\n    }\n    return err\n}","preventionTips":["Pin the provider in config rather than relying on environment defaults","Record the provider alongside the session ID when scripting resumes","Use explicit --provider whenever you know the environment's default may differ"],"tags":["session","resume","provider","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"}