# How the Detection Job Determines When to Activate Workflow Runs in gh-aw

> Discover how the detection job activates workflow runs in github gh-aw based on safe output types or code patches. Learn the logic behind timely workflow activation to streamline your security processes.

- Repository: [GitHub/gh-aw](https://github.com/github/gh-aw)
- Tags: internals
- Published: 2026-02-16

---

**The detection job activates when either the main agent job produces safe-output types (`output_types != ''`) or the workflow contains a code patch (`has_patch == 'true'`), as defined by a conditional `if` expression built in [`pkg/workflow/threat_detection.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/threat_detection.go).**

The `github/gh-aw` repository generates dynamic GitHub Actions workflows that include an optional threat detection step. This step does not run unconditionally; instead, the compiler injects a logical condition that evaluates the outputs of the preceding agent job to determine if threat analysis is necessary.

## How the Activation Condition Is Built in [`pkg/workflow/threat_detection.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/threat_detection.go)

The core logic resides in the `buildThreatDetectionJob` function within **[`pkg/workflow/threat_detection.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/threat_detection.go)**. This function constructs the `if` expression by inspecting the outputs of the main agent job that produced the safe-output artifacts.

### Gathering the Main Agent Job Outputs

The compiler first identifies the main agent job name, then checks two specific outputs that indicate whether threat detection should run:

- **`output_types`** – A comma-separated string listing the types of safe-outputs the agent produced (e.g., files, secrets, command outputs). An empty string indicates no safe-outputs were generated.
- **`has_patch`** – A boolean flag indicating whether the workflow includes a code patch that requires inspection.

### Building the Comparison Nodes

The function creates two comparison nodes using the internal expression builder. The first checks if any output types exist, and the second verifies if a patch is present:

```go
// output_types != ''
hasOutputTypes := BuildComparison(
    BuildPropertyAccess(fmt.Sprintf("needs.%s.outputs.output_types", mainJobName)),
    "!=", BuildStringLiteral(""),
)

// has_patch == 'true'
hasPatch := BuildComparison(
    BuildPropertyAccess(fmt.Sprintf("needs.%s.outputs.has_patch", mainJobName)),
    "==", BuildStringLiteral("true"),
)

```

### Combining Conditions with Logical OR

These two conditions are combined using a logical OR operation. The resulting expression evaluates to `true` if **either** safe-outputs were produced **or** a patch exists:

```go
// (output_types != '' || has_patch == 'true')
condition := BuildDisjunction(false, hasOutputTypes, hasPatch)

```

The compiler then assigns this rendered expression to the job's `If` field:

```go
job := &Job{
    Name: string(constants.DetectionJobName),
    If:   condition.Render(),
    // … other fields …
}

```

## When the Detection Job Appears in the Workflow

The detection job is not always present in the generated workflow. It is only created when specific configuration criteria are met:

- **Safe-outputs must be configured**: The job is added only if `data.SafeOutputs != nil && data.SafeOutputs.ThreatDetection != nil` evaluates to true.
- **Threat detection must be enabled**: If the user disables threat detection via `threat-detection: false` or `enabled: false`, the `parseThreatDetectionConfig` function returns `nil`, and the compiler omits the detection job entirely.

If these conditions are not satisfied, the workflow proceeds with only the main agent job, and no threat analysis step is generated.

## Practical Example of the Generated `if` Condition

When the compiler generates the final YAML, the detection job includes the conditional expression that references the main job's outputs. Assuming the main job is named `build`, the generated workflow snippet appears as:

```yaml
detection:
  if: |
    needs.build.outputs.output_types != '' ||
    needs.build.outputs.has_patch == 'true'
  runs-on: ubuntu-latest
  steps:
    - name: Download artifacts
      uses: actions/download-artifact@v4
    - name: Run threat detection
      run: ./threat-analysis-engine

```

In this example, if the `build` job produces no safe-outputs (`output_types` is empty) and contains no patch (`has_patch` is not `'true'`), the entire `detection` job is skipped automatically by GitHub Actions' conditional logic.

## Summary

- The detection job activation is controlled by a dynamic `if` expression built in **[`pkg/workflow/threat_detection.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/threat_detection.go)**.
- The condition checks two outputs from the main agent job: **`output_types`** (non-empty string) and **`has_patch`** (equals `'true'`).
- A logical **OR** combines these checks; the job runs if either condition is satisfied.
- The job only appears in workflows when **safe-outputs are configured** and **threat detection is enabled** in the repository settings.
- The generated YAML uses standard GitHub Actions `needs` context syntax to evaluate the condition at runtime.

## Frequently Asked Questions

### What happens if the main agent job produces no safe-outputs and has no patch?

If both `output_types` is an empty string and `has_patch` is not `'true'`, the logical OR condition evaluates to false. GitHub Actions skips the detection job entirely, and the workflow continues without executing the threat analysis step.

### Can I force the detection job to run on every workflow execution?

No. The current implementation in [`pkg/workflow/threat_detection.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/threat_detection.go) hardcodes the conditional logic based on the presence of outputs or patches. To run detection unconditionally, you would need to modify the source code to remove the `If` condition from the job definition in `buildThreatDetectionJob`.

### Where is the threat detection configuration validated?

The configuration is parsed and validated in **[`pkg/workflow/threat_detection.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/threat_detection.go)** within the `parseThreatDetectionConfig` function. This function checks whether the user has enabled threat detection and whether safe-outputs are properly configured. If validation fails or detection is disabled, the function returns `nil`, and the compiler omits the detection job from the generated workflow.

### How does the detection job access the main job's outputs?

The detection job uses the GitHub Actions `needs` context to reference outputs from the main agent job. The compiler constructs property access expressions like `needs.<main-job-name>.outputs.output_types` and `needs.<main-job-name>.outputs.has_patch`, which GitHub Actions evaluates at runtime to determine the job's execution status.