Skip to content

rubber-duck: model-emitted model argument silently overrides the complementary strategy and the user's /subagents setting #4432

Open

Description

@eggboy

Summary

The rubber-duck sub-agent exists to give a cross-family second opinion — a Claude session gets a GPT reviewer, and vice versa. Its shipped definition deliberately omits model: so the complementary strategy can select an opposite-family model.

However, the task tool exposes an optional model parameter to the calling model. When the parent agent supplies it, that value wins over everything, including a complementary value the user explicitly configured in /subagents. The result is a Claude session getting a Claude reviewer — silently, with no warning, defeating the agent's entire purpose.

Version: GitHub Copilot CLI 1.0.79 (darwin-arm64)

Why this is a defect rather than expected precedence

"Explicit argument beats config file" is correct precedence when a human supplied the argument. Here the argument is a non-deterministic token sequence emitted by an LLM that has no knowledge that the user configured anything. So model discretion silently outranks explicit user configuration, and nothing surfaces the divergence.

Contributing factors:

  1. The design intent is documented in the shipped agent file. definitions/rubber-duck.agent.yaml:

    # model: omitted - will be selected dynamically at runtime based on user's current model preference
  2. No guidance is given to the calling model. The task tool describes model generically ("Use 'model' parameter to override the default model (20 models available)"). Neither that description nor the rubber-duck agent description mentions that setting model defeats the complementary strategy. The calling model has no signal that this agent is special.

  3. No guard and no warning exist. Searching the native runtime.node for complementary-related user-facing strings turns up only the unavailable error ("...uses a complementary model... but none is available..."). There is no warning when an explicit model collapses the agent to the parent's own family.

Reproduction

Observed in a real session (claude-opus-5 parent). From ~/.copilot/session-state/<id>/events.jsonl:

tool.execution_start, toolCallId: toolu_01DXhyxFyFHbcsQmGQseBwmi:

{
  "agent_type": "rubber-duck",
  "name": "reval-review",
  "description": "Review README revalidation changes",
  "model": "claude-opus-4.8",
  "prompt": "..."
}

Corresponding subagent.completed:

agentName: "rubber-duck"   model: "claude-opus-4.8"

A Claude session received a Claude reviewer.

Verified precedence

Calling the native resolver directly (runtime.node → subagentStartPlan), parent model claude-opus-5, available models claude-opus-5 / claude-opus-4.8 / gpt-5.6-sol all at price category high:

/subagents setting explicit model arg resolved model
(none) (none) gpt-5.6-sol ✅
(none) claude-opus-4.8 claude-opus-4.8 ❌
complementary claude-opus-4.8 claude-opus-4.8 ❌
gpt-5.6-sol claude-opus-4.8 claude-opus-4.8 ❌
gpt-5.6-sol (none) gpt-5.6-sol ✅

Effective precedence:

explicit `model` arg  >  /subagents setting  >  agent-declared model  >  complementary  >  inherit

Rows 3 and 4 are the problem: a setting the user deliberately chose is overridden by a value the model guessed.

Repro script

// from ~/.copilot/pkg/darwin-arm64/1.0.79/
const h = require("./prebuilds/darwin-arm64/runtime.node");
const models = [
  { id: "claude-opus-5",   label: "C5",  priceCategory: "high" },
  { id: "claude-opus-4.8", label: "C48", priceCategory: "high" },
  { id: "gpt-5.6-sol",     label: "Sol", priceCategory: "high" },
];
const plan = (o) => JSON.parse(h.subagentStartPlan(JSON.stringify({
  agentType: "rubber-duck",
  availableBuiltinNames: ["rubber-duck"],
  customAgentNames: [], isCustom: false,
  explicitModel: o.explicitModel,
  subagents: o.subagents,
  selectedParentModel: "claude-opus-5",
  autoMode: false,
  defaultToComplementary: true,
  complementarySelection: { model: "gpt-5.6-sol" },
  availableModels: models,
})));

const S = (m) => ({ agents: { "rubber-duck": { model: m } } });
console.log(plan({}));                                                        // gpt-5.6-sol
console.log(plan({ explicitModel: "claude-opus-4.8" }));                      // claude-opus-4.8
console.log(plan({ explicitModel: "claude-opus-4.8", subagents: S("complementary") })); // claude-opus-4.8
console.log(plan({ explicitModel: "claude-opus-4.8", subagents: S("gpt-5.6-sol") }));   // claude-opus-4.8

Expected behaviour

Any one of these would resolve it; they are listed cheapest first.

  1. Document the constraint to the calling model. Add to the task tool description, or the rubber-duck agent description: "Do not set model for this agent — it selects a complementary, opposite-family model automatically." Low-risk and likely fixes the majority of occurrences.

  2. Rank a configured /subagents value above a model-emitted model argument. Distinguish a human per-invocation override from one the model produced, and let explicit user configuration win. This is the principled fix.

  3. Warn on family collapse. When rubber-duck resolves to the same model family as the parent session, surface it in the timeline or emit a warning. Today the only way to discover this is to read events.jsonl by hand.

Impact

The failure is silent and the output is still plausible — a same-family reviewer returns a confident critique that simply shares the author model's blind spots. Users believe they are getting a cross-family second opinion when they are not, and the /subagents setting they configured to guarantee it does not hold.

Current workaround

Instruct the parent agent never to pass model for agent_type: "rubber-duck" (e.g. in ~/.copilot/copilot-instructions.md). This is per-machine and depends on instruction adherence, so it is not a substitute for a product fix.

Related

#4380 reports the same user-visible symptom (rubber-duck running the parent's own model family), but appears to be a different mechanism: there the reporter's /subagents shows complementary — unavailable, disabled with Overridden: No, i.e. no opposite-family model was available at all.

In the case above, complementary was available (gpt-5.6-sol was present at the same price category and is what resolves when no model argument is passed) and was overridden by the explicit argument. So the two may need separate fixes, and I have added this evidence there as well.

Activity

  1. nick-thinks commented on Aug 11, 2026

    @nick-thinks

    Hi @eggboy thanks for noting all of this. In some sense this is expected behavior from a technical perspective, though I appreciate it is unexpected for you and clearly not reliable in your use cases. I will look into this.

    FWIW: we consciously left the "model" argument overrideable by the session agent. We did so for the same reason the other task tool subagents' models can be overridden by the session agent: we allow the user to ask conversationally for some behavior, or the session agent may have its own "reason" to require it.

    I double-checked and actually we do provide an instruction inside the rubber duck task tool description not to override model, but this is only passed to Claude session models and not GPT ones (that's a bug). These instructions are also updated separately from any task tool instructions, e.g. to use the model override argument, which may clash. Instructions are not bullet-proof, though, and this becomes more of a product question about the use cases we want to support. Let me ask and get back to you on this.

  2. eggboy commented on Aug 12, 2026

    @eggboy
    Author

    Related, found while investigating this: #4457 — cross-family sub-agent spawns also mishandle the inherited tool list, emitting a spurious Unknown tool name in the tool excludedlist: "apply_patch" because apply_patch is registered only for Codex models.

    Same weak spot (cross-family sub-agent handling), different surface: this issue is functional (the wrong model runs), #4457 is presentational (a misleading, unactionable warning). rubber-duck is the common trigger for both, since its complementary strategy deliberately selects the opposite model family.

  3. added
    area:agentsSub-agents, fleet, autopilot, plan mode, background agents, and custom agents
    area:modelsModel selection, availability, switching, rate limits, and model-specific behavior
    on Nov 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:agentsSub-agents, fleet, autopilot, plan mode, background agents, and custom agentsarea:modelsModel selection, availability, switching, rate limits, and model-specific behavior

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions