Skip to content

Bugfix: GitHub Token S2S TTL and Actions Timeouts #24920

@davidslater

Description

@davidslater

Problems

Three separate but related problems arise when features: copilot-requests: true is used.


Problem 1: create-agent-session still requires COPILOT_GITHUB_TOKEN

The intent of features: copilot-requests: true is to eliminate the need for the COPILOT_GITHUB_TOKEN PAT secret by using the built-in ${{ github.token }} (which gets the copilot-requests: write permission injected automatically). However, the create-agent-session safe output is not covered by this substitution.

Affected component

The create-agent-session safe output uses UseCopilotRequestsToken: true, which routes through getEffectiveCopilotRequestsToken() in pkg/workflow/github_token.go. That function's fallback chain is:

1. Custom github-token field (if set)
2. secrets.COPILOT_GITHUB_TOKEN
   (no further fallback)

It has no awareness of the copilot-requests feature flag and never substitutes ${{ github.token }}. If the user omits COPILOT_GITHUB_TOKEN (which is the whole point of using the feature flag), the create-agent-session step will receive an empty token and fail at runtime.

Additionally, the safe-outputs job does not receive copilot-requests: write permission — it only gets contents: read + issues: write (from ComputePermissionsForSafeOutputs() in pkg/workflow/safe_outputs_permissions.go). Even if the token substitution were fixed, the job permission would also need to be updated.

Not affected

The GitHub MCP server is not affected. Its token chain is:

secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN

GITHUB_TOKEN is always automatically provided by GitHub Actions and does not require a PAT. The MCP server will work without any user-configured secrets.

Current state

# Compiled safe-outputs step — always uses COPILOT_GITHUB_TOKEN regardless of feature flag
- name: Create Agent Session
  uses: actions/github-script@...
  with:
    github-token: ${{ secrets.COPILOT_GITHUB_TOKEN }}  # ← hardcoded, fails if secret absent
    script: |
      ...

Desired state

When features: copilot-requests: true is set, the create-agent-session step should use ${{ github.token }} instead of secrets.COPILOT_GITHUB_TOKEN, and the safe-outputs job permissions should include copilot-requests: write.

Solution

  1. Thread copilot-requests awareness into getEffectiveCopilotRequestsToken() — or add a new getEffectiveCopilotRequestsTokenForFeature(useCopilotRequests bool, customToken string) string that substitutes ${{ github.token }} when the flag is active.

  2. Pass copilot-requests flag into the safe-outputs job permission computation — in ComputePermissionsForSafeOutputs(), when copilot-requests is enabled and create-agent-session is present, merge copilot-requests: write into the safe-outputs job permissions.

  3. Update buildCreateAgentSessionStepConfig() in pkg/workflow/compiler_safe_outputs_specialized.go to accept and pass the useCopilotRequests boolean so the correct token is selected.


Problem 2: Agent job using github.token fails if it runs longer than ~1 hour

The Copilot API creates a server-side session when the agent starts. That session is bound to the token presented at session creation. The ${{ github.token }} value is baked into the step's environment at the moment the step starts — it is never refreshed during the step.

If the Copilot API session expires (approximately 1 hour), all subsequent tool calls and inference requests will fail mid-run with authentication errors. The agent job will appear to be running but will be unable to make any progress.

Why the detection job is not affected

The threat-detection job also receives copilot-requests: write and runs the Copilot CLI, but it completes in under 1 minute. It is a one-shot classification pass, not an interactive agent session. The 1-hour expiry is irrelevant for it.

Current state

# Schema allows any positive integer — no upper bound enforced
timeout-minutes: 120  # user sets this; schema only requires minimum: 1

# Compiled agent step — token baked in, never refreshed
- name: Execute GitHub Copilot CLI
  timeout-minutes: 120
  env:
    COPILOT_GITHUB_TOKEN: ${{ github.token }}  # ← expires ~1h after workflow start
    S2STOKENS: "true"
    ...

Desired state

Users should be warned (or blocked at compile time) if they set timeout-minutes > 60 while features: copilot-requests: true is enabled, since the combination is likely to produce silent mid-run failures.

Solution

In pkg/workflow/copilot_engine_execution.go, after detecting useCopilotRequests, check the configured timeout and emit a compile-time warning:

if useCopilotRequests && workflowData.TimeoutMinutes != "" {
    timeoutVal := parseTimeoutMinutesInt(workflowData.TimeoutMinutes)
    if timeoutVal > 60 {
        fmt.Fprintln(os.Stderr, console.FormatWarningMessage(
            "features: copilot-requests: true with timeout-minutes > 60 may cause mid-run " +
            "authentication failures: the github.token Copilot session expires after ~1 hour. " +
            "Set timeout-minutes ≤ 60 or configure COPILOT_GITHUB_TOKEN as a PAT instead."))
    }
}

This is a low-risk, high-value change that surfaces the failure mode to the user at gh aw compile time rather than mid-run.


Problem 3: Schema allows timeout-minutes values that exceed GitHub Actions limits

The compiled agent step receives the user-configured timeout-minutes directly. The JSON schema only enforces minimum: 1 with no upper bound. GitHub-hosted runners have a hard limit of 6 hours (360 minutes) per job. Any value above this will cause the job to fail immediately at startup with a GitHub Actions configuration error.

Current state

// pkg/parser/schemas/main_workflow_schema.json
"timeout-minutes": {
  "type": "integer",
  "minimum": 1
  // no maximum
}
# A user can write this and it compiles fine, but will fail at runtime
timeout-minutes: 500

Desired state

The schema should enforce a maximum of 360 (6 hours) to match the GitHub-hosted runner limit. This prevents users from compiling workflows that are guaranteed to fail at runtime due to the Actions platform constraint.

Note on self-hosted runners: Self-hosted runners technically support up to 35 days, but this is not a practical limit for agentic workflows. The 360-minute cap is the appropriate guard rail for typical use and matches the GitHub platform default.

Solution

Update pkg/parser/schemas/main_workflow_schema.json to add "maximum": 360 to the timeout-minutes integer schema:

"timeout-minutes": {
  "description": "Workflow timeout in minutes (GitHub Actions standard field). Defaults to 20 minutes for agentic workflows. Maximum is 360 minutes (6 hours) for GitHub-hosted runners.",
  "oneOf": [
    {
      "type": "integer",
      "minimum": 1,
      "maximum": 360,
      "examples": [5, 10, 30]
    },
    {
      "type": "string",
      "pattern": "^\\$\\{\\{.*\\}\\}$",
      "description": "GitHub Actions expression that resolves to an integer (e.g. '${{ inputs.timeout }}')"
    }
  ]
}

After changing the schema, run make build to rebuild the binary (schema files are embedded via //go:embed) and make recompile to recompile all workflow lock files.


Summary Table

# Problem Component Fix
1 create-agent-session ignores copilot-requests: true, still requires COPILOT_GITHUB_TOKEN PAT; safe-outputs job also missing copilot-requests: write permission compiler_safe_outputs_specialized.go, safe_outputs_permissions.go, github_token.go Propagate feature flag into token selection and permission computation
2 Agent step with timeout-minutes > 60 + copilot-requests: true silently fails after ~1 hour copilot_engine_execution.go Emit compile-time warning when timeout exceeds session lifetime
3 Schema allows timeout-minutes > 360, which GitHub-hosted runners reject at runtime pkg/parser/schemas/main_workflow_schema.json Add maximum: 360 to the schema

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions