Skip to content

Feature Request: SubagentStart hook should support updatedInput to enable deterministic model routing #69545

Description

@Josef-Le

Problem

There is currently no deterministic way to enforce model routing for subagents spawned via the Agent() tool. The only available mechanisms are advisory (rely on the orchestrating model choosing the right model: param), or blunt (a single CLAUDE_CODE_SUBAGENT_MODEL env var that forces all agents to one model regardless of task).

What was tried and why it doesn't work

I spent significant time empirically testing every available hook and mechanism on CC 2.1.183:

Mechanism Fires for Agent? Can modify model?
PreToolUse(matcher: "Agent") ❌ Does not fire —
SubagentStart ✅ Fires ❌ updatedInput silently ignored
PermissionRequest(matcher: "Agent") ❌ Does not fire —
permissions.deny ✅ Fires ❌ Can block, not reroute
CLAUDE_CODE_SUBAGENT_MODEL env var ✅ (overrides everything) Blunt — no task awareness

SubagentStart fires correctly and receives the session_id, transcript_path, cwd, agent_id, and agent_type. However:

  • The hook input does not include the requested model
  • Returning {"hookSpecificOutput": {"updatedInput": {"model": "claude-haiku-4-5-20251001"}}} is silently ignored — subagent runs with whatever model was originally requested
  • Returning {"decision": "block"} or exiting with code 2 is also silently ignored — subagent runs anyway
  • Returning {"hookSpecificOutput": {"additionalContext": "..."}} is not injected into the subagent

CLAUDE_CODE_SUBAGENT_MODEL does work as a full override (tested empirically: setting it in the process environment forces haiku even when model: "sonnet" is explicitly specified in the Agent call). But it's a session-wide blunt instrument — it prevents using Sonnet agents for legitimate parallel judgment tasks.

Requested behavior

Either (or both) of the following would solve the problem:

Option A: SubagentStart hook supports updatedInput.model

When a SubagentStart hook returns updatedInput, CC should apply the modified model field before initializing the subagent context. The hook should also receive the requested model in its input so hooks can make informed decisions:

{
  "session_id": "...",
  "agent_id": "...",
  "agent_type": "general-purpose",
  "model": "claude-sonnet-4-6",          // ← add this
  "task_description": "...",              // ← add this (first user message or task prompt)
  "hook_event_name": "SubagentStart"
}

Return value to override the model:

{
  "hookSpecificOutput": {
    "updatedInput": {
      "model": "claude-haiku-4-5-20251001"
    }
  }
}

Option B: PreToolUse fires for Agent tool calls

Allow PreToolUse hooks with matcher: "Agent" to intercept Agent spawns, with the ability to modify tool_input.model via updatedInput — consistent with how PreToolUse works for all other tools.

Use case

This enables building a deterministic model router: a small classifier (keyword heuristic or local 3B model) that runs in a hook script, classifies the task as mechanical (→ haiku) or judgment-based (→ sonnet), and rewrites the model accordingly. The LLM orchestrator does not need to be involved in the routing decision at all.

Current workaround: all routing is advisory — the mandate is injected via UserPromptSubmit hook but the orchestrating model still decides whether to follow it. This produces inconsistent behavior in practice.

Environment

  • Claude Code: 2.1.183
  • Platform: macOS (arm64)
  • Testing methodology: empirical hook wiring + subagent spawning, not inference from docs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions