Skip to content

CodeNomad may persist and resend stale session.agent / session.model, causing invalid subagent model selection #416

@fastknifes

Description

@fastknifes

CodeNomad may persist and resend stale session.agent / session.model, causing invalid subagent model selection

Summary

During debugging of repeated subagent failures in an OpenCode + OMO setup, I found a strong correlation between CodeNomad being in the loop and subagent failures.

When CodeNomad is not used and the same workflow is continued from pure CLI, previously failing subagent_type="explore" smoke tests succeed.

At the same time, CodeNomad source shows it is not a passive client: it stores session.agent and session.model locally, updates them when the agent changes, and resends both values in subsequent session.promptAsync() requests.

That means stale or incorrect client-side session state can be fed back into OpenCode and keep poisoning later requests.

Observed behavior

With CodeNomad involved

  • background subagent tasks frequently failed
  • failures were not uniform; examples included:
    • Model not found: opencode/gpt-5-nano
    • requests reaching the LLM proxy with model: "minimax-m2.7" and failing because that provider was not configured in the proxy

Without CodeNomad (pure CLI)

  • category="quick" smoke test succeeded
  • subagent_type="explore" smoke test also succeeded

This strongly suggests CodeNomad is at least one of the variables that can corrupt the session state.

Source evidence from CodeNomad

1. CodeNomad resends locally stored session.agent and session.model

File: packages/ui/src/stores/session-actions.ts

const requestBody = {
  parts: requestParts,
  ...(session.agent && { agent: session.agent }),
  ...(session.model.providerId &&
    session.model.modelId && {
      model: {
        providerID: session.model.providerId,
        modelID: session.model.modelId,
      },
    }),
}

await client.session.promptAsync({
  sessionID: sessionId,
  ...(requestBody as any),
})

This means CodeNomad is not just forwarding user text. It actively resends local session state back to OpenCode.

2. Switching agent mutates session.model

File: packages/ui/src/stores/session-actions.ts

const nextModel = await getDefaultModel(instanceId, agent)
const shouldApplyModel = isModelValid(instanceId, nextModel)

withSession(instanceId, sessionId, (current) => {
  current.agent = agent
  if (shouldApplyModel) {
    current.model = nextModel
  }
})

This means CodeNomad actively rewrites the session model whenever it thinks the active agent changed.

3. Default model resolution prefers cached agent.model

File: packages/ui/src/stores/session-models.ts

if (agentName) {
  const agent = instanceAgents.find((a) => a.name === agentName)
  if (agent && agent.model && isModelValid(instanceId, agent.model)) {
    return {
      providerId: agent.model.providerId,
      modelId: agent.model.modelId,
    }
  }

  const stored = await getAgentModelPreference(instanceId, agentName)
  if (isModelValid(instanceId, stored)) {
    return stored
  }
}

Priority is:

  1. agent.model
  2. stored preference
  3. recent/default provider model

So if agent.model or stored preference is stale, it can be written back into the session and resent later.

4. CodeNomad caches agent -> model

File: packages/ui/src/stores/session-api.ts

const agentList = (response.data ?? []).map((agent) => ({
  name: agent.name,
  ...
  model: agent.model?.modelID
    ? {
        providerID: agent.model.providerID || "",
        modelID: agent.model.modelID,
      }
    : undefined,
}))

So the client maintains its own agent/model mapping and uses it to drive later session behavior.

Why this looks problematic

This creates a feedback loop:

  1. fetch agents from OpenCode
  2. cache agent.model locally
  3. switch agent -> compute nextModel
  4. overwrite local session.model
  5. next message -> resend both session.agent and session.model

If any part of that local state becomes stale or mismatched, the client can keep replaying the wrong model selection.

Request

Please review whether CodeNomad should:

  1. stop auto-overwriting session.model when changing session.agent, or
  2. stop resending cached session.model unless the user explicitly changed model, or
  3. distinguish server-authoritative session state from client preference state, or
  4. log session.promptAsync request bodies in a way that is easier to inspect when this happens.

Notes from my environment

  • OpenCode client is used against a local AI proxy
  • removing CodeNomad from the loop made the previously failing explore subagent smoke test succeed
  • another independent issue also existed in the LLM proxy (minimax-m2.7 had no configured provider), but that does not explain why the same OpenCode/OMO workflow succeeds once CodeNomad is removed from the path

Repro idea

  1. Start with a session whose agent/model was previously changed in UI
  2. Trigger subagent workflows from CodeNomad
  3. Compare:
    • CodeNomad UI path
    • pure CLI path
  4. Inspect whether session.agent / session.model in the outgoing session.promptAsync body differ between the two

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions