GitHub gh-aw Pre-Activation Checks: Team Membership, Rate Limiting, and Skip Conditions Explained

The github/gh-aw compiler generates a dedicated pre-activation job that evaluates team membership, rate limits, stop-times, and skip conditions via AND-combined boolean expressions before allowing the main agent workflow to execute.

The github/gh-aw repository provides a workflow compiler that automatically injects pre-activation checks into generated GitHub Actions. These checks determine whether a workflow should proceed based on configurable conditions such as team membership, rate limiting, and temporal constraints. When any check fails, the workflow halts immediately without consuming agent resources.

What Are Pre-Activation Checks in gh-aw?

When a workflow is compiled, gh-aw creates a dedicated "pre-activation" job that runs before the main agent job. This job aggregates a set of checks that are evaluated in a single boolean expression. If any check fails, the workflow stops without running the agent.

The pre-activation checks are built in pkg/workflow/compiler_activation_jobs.go by the function buildPreActivationJob. Each check lives in its own helper function or inline logic, and all enabled checks produce a single output activated whose value is a GitHub Actions expression built from the individual step outputs.

Available Pre-Activation Checks

The compiler supports eight distinct pre-activation checks, each triggered by specific front-matter configuration.

Team Membership (Permission) Check

File: pkg/workflow/role_checks.gogenerateMembershipCheck
Script: actions/setup/js/check_membership.cjs
Front-matter key: roles or bots

This check executes via actions/github-script to verify the triggering user belongs to one of the required GitHub teams or roles. It also supports optional bot allowlisting. The check is added when needsPermissionCheck == true, which occurs when a roles or bots front-matter entry is present.

Rate Limit Check

File: pkg/workflow/role_checks.gogenerateRateLimitCheck
Script: actions/setup/js/check_rate_limit.cjs
Front-matter key: rate-limit

This check enforces a per-user limit on how many times the workflow may be triggered in a configurable time window. It is added when data.RateLimit != nil, meaning a rate-limit block is defined in the front-matter.

Stop-Time Check

File: pkg/workflow/compiler_activation_jobs.go (lines 106-120)
Script: actions/setup/js/check_stop_time.cjs
Front-matter key: stop-time

This check stops the workflow after a given datetime (UTC). It runs check_stop_time.cjs to compare the current time against the configured threshold.

Skip-If-Match Check

File: pkg/workflow/compiler_activation_jobs.go (lines 124-138)
Script: actions/setup/js/check_skip_if_match.cjs
Front-matter key: skip-if-match

This check executes a searchable query. If the query returns any matches, the workflow is skipped. It is useful for preventing runs when blocking issues or labels exist.

Skip-If-No-Match Check

File: pkg/workflow/compiler_activation_jobs.go (lines 140-154)
Script: actions/setup/js/check_skip_if_no_match.cjs
Front-matter key: skip-if-no-match

Conversely, this check skips the workflow when a query returns no matches. It ensures the workflow only runs when specific preconditions (like required labels) are present.

Skip-Roles Check

File: pkg/workflow/compiler_activation_jobs.go (lines 157-172)
Script: actions/setup/js/check_skip_roles.cjs
Front-matter key: skip-roles

This check skips the workflow when the triggering user belongs to any of the listed roles. It acts as a negative permission filter.

Skip-Bots Check

File: pkg/workflow/compiler_activation_jobs.go (lines 174-188)
Script: actions/setup/js/check_skip_bots.cjs
Front-matter key: skip-bots

This check skips the workflow when the actor is a bot matching the supplied identifiers, preventing automated accounts from triggering expensive runs.

Command-Position Check

File: pkg/workflow/compiler_activation_jobs.go (lines 190-202)
Script: actions/setup/js/check_command_position.cjs
Front-matter key: command

For command-driven workflows, this check validates that the comment triggering the workflow contains the command at the expected position.

How Pre-Activation Checks Are Combined

All enabled checks produce boolean outputs that are AND-combined into a single activated expression. The compiler assembles this expression within buildPreActivationJob in pkg/workflow/compiler_activation_jobs.go.

The generated expression follows this pattern:

// Simplified representation of the expression assembly
${{ steps.check_membership.outputs.is_team_member == 'true' &&
    steps.check_rate_limit.outputs.rate_limit_ok == 'true' &&
    steps.check_stop_time.outputs.stop_time_ok == 'true' &&
    steps.check_skip_if_match.outputs.skip_ok == 'true' &&
    steps.check_skip_if_no_match.outputs.skip_ok == 'true' &&
    steps.check_skip_roles.outputs.skip_roles_ok == 'true' &&
    steps.check_skip_bots.outputs.skip_bots_ok == 'true' &&
    steps.check_command_position.outputs.command_ok == 'true' }}

If the list of enabled checks is empty, the compiler panics, as this represents a developer error. The step IDs referenced in these expressions are defined as constants in pkg/constants/constants.go, including CheckMembershipStepID, CheckRateLimitStepID, CheckStopTimeStepID, and others.

Configuring Pre-Activation Checks in Workflow Front-Matter

You enable checks by adding specific keys to the YAML front-matter of your workflow definition file (typically .github/workflows/*.md).

Here is a comprehensive configuration example:

---

# Team membership requirement

roles: ["maintainer", "team-ops"]

# Optional bot allowlist

bots: ["github-actions[bot]"]

# Rate limiting: max 5 triggers per hour per user

rate-limit:
  max: 5
  window: 60
  events: ["issue_comment", "pull_request_review_comment"]

# Hard stop after UTC datetime

stop-time: "2026-03-01T12:00:00Z"

# Skip if blocking issues exist

skip-if-match:
  query: "repo:github/gh-aw is:open label:blocked"
  max: 1

# Skip if no ready items found

skip-if-no-match:
  query: "repo:github/gh-aw is:open label:ready"

# Skip for specific roles

skip-roles: ["admin", "owner"]

# Skip specific bots

skip-bots: ["dependabot[bot]"]

# Command trigger validation

command: ["/gh aw run"]
---

When the compiler processes this front-matter, it detects each non-nil block and generates the corresponding check step in the pre-activation job.

Programmatic Access to Pre-Activation Logic

If you need to invoke the compiler programmatically and inspect the generated pre-activation job, use the following Go snippet:

package main

import (
    "fmt"
    "github.com/github/gh-aw/pkg/workflow"
)

func main() {
    // Load a test workflow file (or construct WorkflowData manually)
    data, _ := workflow.LoadWorkflowFile("example.md")

    // Instantiate the compiler (default settings)
    comp := workflow.NewCompiler()

    // Build the pre-activation job; the second arg indicates whether a permission check is required
    job, err := comp.buildPreActivationJob(data, len(data.Roles) > 0 || len(data.Bots) > 0)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Pre-activation job has %d steps and the expression:\n%s\n",
        len(job.Steps), job.Outputs["activated"])
}

Running this snippet prints the list of generated steps (membership, rate-limit, etc.) and the combined ${{ … }} expression that the activation job evaluates at runtime.

Summary

  • gh-aw generates a dedicated pre-activation job that runs before the main agent workflow to validate execution conditions.
  • Eight distinct checks are available: team membership, rate limiting, stop-time, skip-if-match, skip-if-no-match, skip-roles, skip-bots, and command-position.
  • Configuration occurs via YAML front-matter in workflow definition files, with each check triggered by specific keys like roles, rate-limit, or skip-if-match.
  • Implementation resides in pkg/workflow/compiler_activation_jobs.go (job assembly) and pkg/workflow/role_checks.go (membership and rate-limit logic), with step IDs defined in pkg/constants/constants.go.
  • Runtime execution uses JavaScript scripts located in actions/setup/js/*.cjs, with outputs combined into a single boolean activated expression that determines workflow continuation.

Frequently Asked Questions

How do I enable team membership validation in gh-aw?

Add a roles array to your workflow's front-matter. The compiler invokes generateMembershipCheck in pkg/workflow/role_checks.go, which creates a step running check_membership.cjs to verify the triggering user belongs to at least one specified GitHub team. You can optionally include a bots array to allowlist specific automated accounts.

What happens when multiple pre-activation checks are configured?

The compiler builds a single boolean expression in buildPreActivationJob that AND-combines all enabled check outputs. Each step (membership, rate-limit, stop-time, etc.) produces an output that must equal 'true' for the final activated output to evaluate as true. If any check fails, the workflow stops immediately without running the agent job.

How does the rate limiting check work?

When you define a rate-limit block in front-matter with max, window, and events fields, the compiler calls generateRateLimitCheck from pkg/workflow/role_checks.go. This generates a step that executes check_rate_limit.cjs to enforce per-user trigger limits within the specified time window, preventing workflow abuse or excessive API consumption.

Can I skip workflows based on search queries?

Yes. Use skip-if-match to skip the workflow when a GitHub search query returns results (e.g., existing blocked issues), or skip-if-no-match to skip when no results are found (e.g., missing required labels). These checks run check_skip_if_match.cjs and check_skip_if_no_match.cjs respectively, and are implemented in pkg/workflow/compiler_activation_jobs.go around lines 124-154.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →