Skip to content

Session model pinning persists indefinitely: snap-back probe (PR #82676) defeated by origin-field pollution upstream — repros on 2026.5.28 through 2026.6.7-beta.1, byte-identical paths #92776

Description

@falonrozfatemi

Bug type

Behavior bug (state-persistence regression). Closely related to closed issue #82544; reporting here separately because the original snap-back-probe fix (PR #82676) is built and shipped but is rendered inert by an upstream origin-tracking bug. This is the precise mechanism by which #82544's clawsweeper review bot was correct to say "current main and the latest release still preserve auto-created fallback model pins across later turns, so the reported indefinite session pinning is not fixed."

Summary

The resolveAutoFallbackPrimaryProbe() function in dist/agent-scope-*.js is gated by a guard that requires the session entry's modelOverrideFallbackOriginProvider/modelOverrideFallbackOriginModel to match the agent's currently-configured primary. In practice the origin fields tend to get written to whichever model failed on the chain walk (often the chain's terminal tier, e.g. a Haiku model), not the configured primary that the fallback originated from. The guard then returns early on every subsequent turn, the probe never runs, clearAutoFallbackPrimaryProbeSelection() is never reached, and the session stays pinned to the fallback model indefinitely.

Net effect: PR #82676's snap-back fix is present in the runtime but does not reach the code paths it was supposed to clear. End-user symptom is identical to pre-#82676: sessions stuck on a degraded fallback model forever, with manual sessions.json editing as the only mitigation.

Environment

Field Value
OpenClaw version (reproducer) 2026.5.28
Reproduces in latest published 2026.6.6 (stable) and 2026.6.7-beta.1 — byte-identical code paths
Node 22.22.2
OS macOS 26.4.1 (arm64)
Provider chain claude-cli (Anthropic via Claude Code subscription)
Agent count 10 agents, all direct kind with canonical agent:<id>:main sessions

Where the bug is

The probe guard (correct, but starved of valid input)

dist/agent-scope-*.jsresolveAutoFallbackPrimaryProbe():

function resolveAutoFallbackPrimaryProbe(params) {
  const entry = params.entry;
  if (!entry) return;
  const recoveredAutoFallbackOverride = entry.modelOverrideSource === void 0 && hasSessionAutoModelFallbackProvenance(entry);
  if (entry.modelOverrideSource !== "auto" && !recoveredAutoFallbackOverride) return;
  const originProvider = normalizeOptionalString(entry.modelOverrideFallbackOriginProvider);
  const originModel    = normalizeOptionalString(entry.modelOverrideFallbackOriginModel);
  const overrideProvider = normalizeOptionalString(entry.providerOverride);
  const overrideModel    = normalizeOptionalString(entry.modelOverride);
  const primaryProvider  = normalizeOptionalString(params.primaryProvider);
  const primaryModel     = normalizeOptionalString(params.primaryModel);
  if (!originProvider || !originModel || !overrideProvider || !overrideModel) return;
  if (!primaryProvider || !primaryModel) return;
  if (originProvider !== primaryProvider || originModel !== primaryModel) return;   // <-- GUARD
  ...
}

The guard at the marked line is correct in intent: only probe the primary if the session's recorded origin matches the current configured primary, otherwise the configured primary has changed and the probe is stale. The problem is that the origin fields are not necessarily the configured primary from the moment fallback first happened — they are written elsewhere with the last failed model in the chain walk.

The origin-write site (precise location of the upstream defect)

dist/agent-command-*.js, in the run-attempt resolution path that handles fallback outcomes:

if (autoFallbackPrimaryProbe && !autoFallbackPrimaryProbeInterruptedByLiveSwitch && sessionEntry && sessionStore && sessionKey && !suppressVisibleSessionEffects && !preserveUserFacingSessionModelState && entryMatchesAutoFallbackPrimaryProbe(sessionEntry, autoFallbackPrimaryProbe)) {
  const nextSessionEntry = { ...sessionEntry };
  if (fallbackProvider === autoFallbackPrimaryProbe.provider && fallbackModel === autoFallbackPrimaryProbe.model) {
    clearAutoFallbackPrimaryProbeSelection(nextSessionEntry);
  } else {
    nextSessionEntry.providerOverride = fallbackProvider;
    nextSessionEntry.modelOverride    = fallbackModel;
    nextSessionEntry.modelOverrideSource = "auto";
    nextSessionEntry.modelOverrideFallbackOriginProvider = autoFallbackPrimaryProbe.provider;   // <-- writes probe.provider (origin) back
    nextSessionEntry.modelOverrideFallbackOriginModel    = autoFallbackPrimaryProbe.model;      // <-- writes probe.model (origin) back
    ...
  }
}

This is the re-write branch when a probe fires. It is gated by autoFallbackPrimaryProbe being non-null, which (as established above) does not fire once origin has been polluted. So this re-write path is healthy.

The initial origin-write — where origin gets set the very first time a session falls back, and where the pollution can be introduced — appears to be elsewhere in the fallback-chain resolution loop, and the value written there is the failing model rather than the configured primary that we fell back FROM. (I have not pinpointed the exact line for the initial write; if maintainers can point me at it, I'm happy to confirm and PR a fix.)

Live reproducer

Single-user environment. One representative direct-kind session, redacted of session-id:

"agent:<agentId>:main": {
  "model":                          "claude-opus-4-7",
  "modelProvider":                  "claude-cli",
  "modelOverride":                  "claude-opus-4-7",
  "providerOverride":               "anthropic",
  "modelOverrideSource":            "auto",
  "modelOverrideFallbackOriginProvider": "anthropic",
  "modelOverrideFallbackOriginModel":    "claude-haiku-4-5",
  "fallbackNoticeSelectedModel":    "anthropic/claude-haiku-4-5",
  "fallbackNoticeActiveModel":      "claude-cli/claude-opus-4-7",
  "fallbackNoticeReason":           "selected model unavailable"
}

Configured primary for this agent (from agents.defaults.model inheritance): anthropic/claude-opus-4-8 with fallback chain [claude-opus-4-7, claude-opus-4-6].

resolveAutoFallbackPrimaryProbe({ primaryProvider: "anthropic", primaryModel: "claude-opus-4-8", entry: <above> }) returns undefined because originModel === "claude-haiku-4-5" !== primaryModel === "claude-opus-4-8". Snap-back probe never fires. Session has been stuck on claude-opus-4-7 across many turns; the only mitigation is manual reset of the override fields.

Additional evidence: across 10 agents, multiple subagent sessions are also stuck on claude-opus-4-6 with similar polluted origin fields. Same mechanism, same impossible-to-clear pin.

Cross-check: which versions does this repro on?

Verified byte-identical code paths between [email protected] and [email protected] (latest beta as of 2026-06-13):

diff <(sed -n '/function resolveAutoFallbackPrimaryProbe/,/^}/p' <5.28>/dist/agent-scope-*.js) \
     <(sed -n '/function resolveAutoFallbackPrimaryProbe/,/^}/p' <6.7-beta.1>/dist/agent-scope-*.js)
# (empty diff)

diff <(grep -B 2 -A 8 'modelOverrideFallbackOriginProvider = autoFallbackPrimaryProbe' <5.28>/dist/agent-command-*.js) \
     <(grep -B 2 -A 8 'modelOverrideFallbackOriginProvider = autoFallbackPrimaryProbe' <6.7-beta.1>/dist/agent-command-*.js)
# (empty diff)

Both critical functions are unchanged across all published versions between the running one and the latest beta. The CHANGELOG between these versions contains no entries about origin tracking, session model overrides, sticky pinning, or snap-back beyond #82676 itself.

Proposed fix (sketch — I'm happy to PR if maintainers confirm this matches their model)

At the initial origin-write site in the fallback-chain resolution loop, set modelOverrideFallbackOriginProvider/Model to the agent's currently-configured primary at the moment of first fallback (resolvable via resolveAgentEffectiveModelPrimary(), already imported in agent-command-*.js), not to whichever model failed during chain walk.

Plus a one-shot migration that, on session-store load, rewrites entries with modelOverrideSource: "auto" whose modelOverrideFallbackOriginProvider/Model do not match the agent's configured primary — so existing polluted sessions snap back on their next turn rather than requiring per-session manual reset.

Two questions for the maintainers

  1. Is a fix in flight that I missed? I grepped open PRs for origin | primary.?probe | fallback.*pin | snap | sticky and only found PR fix: preserve config-selected subagent model overrides #92573 (closing Subagent model override from agents.defaults.subagents.model is silently discarded (modelOverrideSource:"auto" matches legacy-cleanup heuristic) #92486, subagent variant of a different but adjacent override-source bug). If there's a main-session-sticky-pin fix in flight I'd love a pointer.
  2. If not, would you accept a PR along the lines sketched above? I have the local environment to test it end-to-end before submitting.

Filing this rather than re-opening #82544 because #82544 was closed with a comprehensive description of the symptom — this issue narrows it to the precise upstream mechanism and links the live repro evidence to specific code paths, which I think is a different ticket.

Thanks for the work on OpenClaw.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:auth-providerAuth, provider routing, model choice, or SecretRef resolution may break.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions