Skip to content

OTLP OIDC audience auth: id-token: write missing on activation/conclusion/safe_outputs jobs → "Unable to get ACTIONS_ID_TOKEN_REQUEST_URL" #45755

Description

@mason-tim

Summary

When a workflow uses OTLP OIDC audience authenticationobservability.otlp.github-app configured with only an audience and no app-id/private-key (the credential-less OIDC mode) — gh-aw injects a "Mint OTLP OIDC token" step (a core.getIDToken(audience) call) into every span-emitting job, but only grants id-token: write to some of them. Three jobs — activation, conclusion, safe_outputs — receive the mint step without the permission and fail at runtime:

Unhandled error: Error: Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable

The agent and detection jobs are granted the permission and work correctly. Header-token auth (the mode gh-aw dogfoods in its own repo) is unaffected, which is why this has gone unnoticed upstream.

This is a supported configuration, not a misconfiguration. observability.otlp.github-app with only audience is the intended credential-less OIDC path: getOTLPGitHubAppTokenConfig() returns nil (no app credentials), so hasOTLPGitHubOIDCAuth() returns true and gh-aw deliberately mints via core.getIDToken(audience). The github-app key is simply an umbrella that covers both the App-token and OIDC-audience variants. (A docs note or config alias clarifying that audience-only = OIDC mode would also help, but is secondary to the permission bug.)

Environment

  • gh-aw compiler: observed on v0.82.9; the gap is still present on main (pkg/workflow/compiler_activation_permissions.go contains no OTLP/OIDC/id-token references as of 2026-07-15).
  • Auth mode: observability.otlp.github-app with audience only (credential-less OIDC).

Root cause

The OTLP OIDC mint step is injected from a single chokepoint — generateOTLPOIDCMintStepgenerateSetupStepWithArtifactClientCondition (pkg/workflow/compiler_yaml_step_generation.go:134, :174) — into each job's step list. The core.getIDToken(audience) branch requires the enclosing job to declare permissions: id-token: write.

However, the id-token: write grant is applied per-job, by hand, and only where a builder remembered to add it:

  • pkg/workflow/threat_detection_job.go:177-178 (detection) ✅ — with the explanatory comment: "When observability.otlp.github-app is configured without app-id/private-key credentials, id-token: write is also needed."
  • pkg/workflow/evals_job.go:96-97 (evals) ✅

It is missing from the builders for the failing jobs:

  • pkg/workflow/compiler_activation_permissions.gobuildActivationBasePermissions / buildActivationPermissions (~lines 123-154) grant no id-token and contain no OTLP/OIDC reference at all ❌ (activation)
  • the conclusion/notify and safe_outputs permission builders ❌ (conclusion, safe_outputs)

Because the grant is duplicated per job instead of co-located with the step injection, any job that emits the mint step can silently miss the permission.

Confirmed in a compiled lock (OIDC-audience workflow)

Each failing job has the identical two-step pattern — the mint step feeds its token into Setup Scripts, so the permission is genuinely required (both to run core.getIDToken() at all, and because the token authenticates that job's OTLP span):

Job Mint step Token consumer
activation Mint OTLP OIDC token Setup Scriptsotlp-oidc-token: ${{ steps.mint-otlp-oidc-token.outputs.token }}
conclusion Mint OTLP OIDC token Setup Scripts ← same
safe_outputs Mint OTLP OIDC token Setup Scripts ← same

Reproduction

  1. Configure a workflow with observability.otlp.github-app providing only an audience (no app-id/private-key).
  2. Compile (gh aw compile) and run the workflow.
  3. The activation job fails with Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable. conclusion and safe_outputs fail the same way. (activation runs first, so it surfaces first.)

Expected behaviour

Any compiled job that receives the OTLP OIDC (core.getIDToken) mint step must also declare permissions: id-token: write.

Proposed implementation plan

  1. Minimal fix — add the existing guard to the activation, conclusion, and safe_outputs permission builders, mirroring threat_detection_job.go:177-178:

    if hasOTLPGitHubOIDCAuth(data.ParsedFrontmatter, data.RawFrontmatter) {
        perms.Set(PermissionIdToken, PermissionWrite)
    }
    • buildActivationBasePermissions in pkg/workflow/compiler_activation_permissions.go
    • the conclusion/notify permission builder
    • the safe_outputs permission builder (pkg/workflow/compiler_safe_outputs_job.go)
  2. Robust fix (preferred, to prevent recurrence) — co-locate the grant with the injection so the two can't drift: have the mint-step path (generateOTLPOIDCMintStep / generateSetupStepWithArtifactClientCondition) mark the enclosing job as requiring id-token: write, and apply that when the job's permissions are rendered. This automatically covers any other setup-bearing job compiled under OIDC-audience mode.

  3. Regression test — for a workflow using observability.otlp.github-app audience-only auth, assert that every compiled job whose steps contain Mint OTLP OIDC token also renders id-token: write. This invariant is what would have caught the original omission.

  4. Recompile & verify — confirm the lock grants id-token: write on activation, conclusion, and safe_outputs.

Workaround (in use)

As a stop-gap we run a deploy-time lock post-processor that adds id-token: write to any job containing the native Mint OTLP OIDC token step. It resolves the failure but should be unnecessary once the compiler grants the permission at compile time.


Suggested labels (for triage): bug, permissions.

Metadata

Metadata

Type

No type

Fields

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