{"record":{"id":"ae21a8fe79143e9a","repo":"alibaba/open-code-review","slug":"resume-rejected-review-rule-identity-changed-ei","errorCode":null,"errorMessage":"resume rejected: review rule identity changed — either a rule text layer (custom, project, global or system) or the include/exclude file filter differs from session %q; %s","messagePattern":"resume rejected: review rule identity changed — either a rule text layer \\(custom, project, global or system\\) or the include/exclude file filter differs from session %q; (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/session/resume_identity.go","lineNumber":125,"sourceCode":"\tif m.Input.Mode != id.Mode {\n\t\t// Mode feeds item_id derivation, so parent and child items cannot even be\n\t\t// put side by side.\n\t\treturn fmt.Errorf(\"resume rejected: input mode changed from %q to %q; %s\", m.Input.Mode, id.Mode, resumeHint)\n\t}\n\t// Both sides empty means a repository with no remote, which is unchanged.\n\tif m.Repository.IdentitySHA256 != id.RepositorySHA256 {\n\t\treturn fmt.Errorf(\"resume rejected: repository identity changed, so this is not the repository the parent run reviewed; %s\", resumeHint)\n\t}\n\tif m.Input.SourceArtifactSHA256 != id.SourceArtifactSHA256 {\n\t\treturn fmt.Errorf(\"resume rejected: the reviewed input changed since session %q — a ref may now point at a different commit, or the selected file set changed; %s\", s.SessionID, resumeHint)\n\t}\n\tif m.Execution.RuleConfigSHA256 == \"\" {\n\t\treturn fmt.Errorf(\"resume session %q recorded no rule identity, so it cannot be verified against the current rules; %s\", s.SessionID, resumeHint)\n\t}\n\tif m.Execution.RuleConfigSHA256 != id.RuleConfigSHA256 {\n\t\t// The digest is one aggregate, so it can only be attributed to a layer,\n\t\t// never to a specific rule or pattern.\n\t\treturn fmt.Errorf(\"resume rejected: review rule identity changed — either a rule text layer (custom, project, global or system) or the include/exclude file filter differs from session %q; %s\", s.SessionID, resumeHint)\n\t}\n\treturn nil\n}\n\n// explicitFlagHint renders the actionable half of a transition rejection. value\n// is empty whenever the endpoint has no provider name — one configured straight\n// from environment variables has none — and `pass --provider ` is not a command\n// anyone can run, so name the flag rather than echoing the empty value.\nfunc explicitFlagHint(flag, value string) string {\n\tif value == \"\" {\n\t\treturn \"pass \" + flag + \" <name> explicitly\"\n\t}\n\treturn \"pass \" + flag + \" \" + value\n}\n\n// ResumeLineageSchemaVersion versions the resume_lineage event independently of\n// the run manifest: lineage records a transition between runs, not a run's\n// coverage, so the two evolve separately.","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/alibaba/open-code-review/blob/5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f/internal/session/resume_identity.go#L107-L143","documentation":"Raised when the parent manifest's rule_config_sha256 differs from the freshly computed rule identity of the current invocation. The digest is a single aggregate over all rule text layers (custom, project, global, system) plus the include/exclude file filter, so it can only say 'rules changed', never which rule changed. Resume is rejected because mixing findings computed under two rule sets would produce an incoherent report.","triggerScenarios":"Calling ValidateResume after any of: editing a custom/project/global rule text file between the parent run and the resume; upgrading the CLI (system rule layer changed); changing the --include/--exclude file filter; adding or removing rule config files on disk.","commonSituations":"A teammate committed a new project rule; the user pulled latest and got a newer CLI with updated built-in rules; the resume command line passes different include/exclude globs than the original run.","solutions":["Revert the rule/config/filter change (checkout the rule files, reinstall the previous CLI version, restore the original include/exclude flags) and retry the resume.","Accept the change and start a new review instead of resuming checkpoints computed under the old rules.","Identify which layer changed by diffing each rule text layer and the file filter against the parent run's values, then decide whether to restore it."],"exampleFix":"// before: rules changed on disk, resume fails\nocr review --resume abc123 --include \"**/*.go\"\n// after: restore the original filter (and rule files) used by the parent run\nocr review --resume abc123 --include \"**/*.ts\"","handlingStrategy":"validation","validationCode":"current := agent.ResolveIdentity(mode, artifact, ruleCfg, repoID)\nif current.RuleConfigSHA256 != manifest.Execution.RuleConfigSHA256 {\n    // rules changed; prompt the user before resuming\n    return errRuleIdentityChanged\n}","typeGuard":"func ruleIdentityMatches(m *Manifest, id RunIdentity) bool { return m != nil && id.RuleConfigSHA256 != \"\" && m.Execution.RuleConfigSHA256 == id.RuleConfigSHA256 }","tryCatchPattern":"if err := state.ValidateResume(req); err != nil {\n    if strings.Contains(err.Error(), \"rule identity changed\") {\n        return startNewReview(req) // intentional rule change\n    }\n    return err\n}","preventionTips":["Keep rule files (custom/project/global) stable for the lifetime of a resumable session.","Pin the CLI version between a parent run and its resume so the system rule layer cannot drift.","Reuse the exact include/exclude flags from the original run when resuming.","Commit rule config to the repo and avoid uncommitted edits mid-review."],"tags":["session","resume","rules","config-drift"],"backgroundTag":"resume-identity-mismatch","analyzedSha":"5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f","analyzedAt":"2026-09-02T02:08:09.116Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}