Skip to content

Model routing: threat detection fails in routed workflows, so safe outputs are applied without a threat verdict #66224

Description

@SivaKesava1

Summary

In a workflow with engine.model-routing, the threat-detection job inherits the routing
configuration. Its AWF run then tries to route, can't find a routing conversation, and exits
before the detection model runs. Threat detection defaults to continue-on-error, so the job
still succeeds and the safe-outputs job applies the agent's outputs (issues, pull requests) with
no threat verdict. Every run looks green; the only signals are annotations on the detection job.

This was found while testing the routing path fix in #66221. With that fix applied locally,
routing itself works, but detection fails in every routed run.

Evidence

Reproduced on GitHub-hosted runners in the private test repository
githubnext/gh-aw-routing-sandbox, compiled from main with --gh-aw-ref and using AWF v0.28.35.
It happened in all 12 routed runs since 5 Oct, for example run 37397610441. In the detection
job, "Execute threat detection with AWF" fails:

[INFO] Staging model routing conversation...
[ERROR] Fatal error: Error: The routing conversation is unavailable
Process exiting with code: 1

"Conclude threat detection" then reports:

⚠️  Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.
##[error]ERR_SYSTEM: ❌ Detection result file not found at: /tmp/gh-aw/threat-detection/detection_result.json
  • The detection job concludes success because of continue-on-error: true.
  • safe_outputs (needs.detection.result == 'success') then created issues and pull requests in
    every one of those runs.

Cause

  • The detection engine config is cloned from the main engine config, including ModelRouting:
    • resolveExternalDetectorEngineConfig in pkg/workflow/threat_detection_helpers.go clones
      the main EngineConfig when detection uses the same engine;
    • resetDetectionEngineTaskSettings then clears agent-only fields (agent, max turns, runs,
      credits, tool limits, cwd, concurrency, Copilot SDK) but not ModelRouting.
  • Both the external threat-detect path (buildExternalDetectorWorkflowData) and the inline
    path (threat_detection_inline_engine.go) use this config.
  • So isModelRoutingEnabled is true for the detection job's workflow data, and the detection job
    gets the routing setup intended for the agent:
    • awf_config_build.go emits experimental.modelRouting and apiProxy.routing, with
      task.conversationFile: /tmp/gh-aw/routing-conversation.json;
    • copilot_engine_execution.go sets GH_AW_MODEL_ROUTING=1;
    • sandbox_agent_images.go applies the routing image set.
  • Only the agent job runs "Prepare model-routing conversation", so AWF in the detection job has
    no conversation to stage and fails closed.
  • cloneThreatDetectionEngineConfig, used for an explicit safe-outputs.threat-detection.engine
    override, doesn't apply that reset. An override that sets model-routing would hit the same
    failure.

Proposed plan

  1. Never route the detection job.
    • Clear ModelRouting in resetDetectionEngineTaskSettings.
    • Also clear it, or reject it with a compile error, on the
      safe-outputs.threat-detection.engine override path in cloneThreatDetectionEngineConfig.
    • Detection should run on its own fixed detection model. Routing is for the agent's task, and
      the detection input isn't that task.
    • With ModelRouting cleared, the detection job no longer gets the routing AWF config,
      GH_AW_MODEL_ROUTING, or the routing image set.
  2. Tests.
    • In the threat-detection or model-routing compiler tests, compile a routed workflow and
      assert that the detection job's AWF config has no experimental.modelRouting and no
      apiProxy.routing, and that its execution step has no GH_AW_MODEL_ROUTING. Cover both
      the external and the inline detection path.
    • Assert the same for a safe-outputs.threat-detection.engine override.
  3. Optional hardening. When detection fails with a tooling error, the step summary or a
    job-level warning could say so more visibly, since continue-on-error hides it. That's a
    separate decision, mentioned only because this failure went unnoticed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions