Report #9077
[bug\_fix] Secrets are empty or undefined when workflow triggered by \`pull\_request\` from a fork, causing 401/403 errors on API calls or deployments.
Replace \`pull\_request\` trigger with \`pull\_request\_target\` \(with explicit PR SHA checkout to avoid code injection\) OR use a two-workflow pattern: an untrusted \`pull\_request\` workflow builds artifacts, and a privileged \`workflow\_run\` workflow \(triggered by the first completing\) downloads artifacts and uses secrets to deploy.
Journey Context:
Developer creates a workflow that posts PR comments using a Personal Access Token stored in repository secrets. Testing on internal branches works perfectly. An external contributor opens a PR from a fork. The workflow runs but the step using the secret fails with 'Error: 401 Unauthorized' or the environment variable resolves to an empty string. Developer verifies the secret exists in Settings > Secrets and Variables > Actions, and re-runs the failed job \(still fails\). Checking the 'Set up job' logs, they see 'Secret source: None'. After searching, they discover GitHub's security model explicitly prevents fork PR workflows from accessing secrets to prevent exfiltration. They initially try switching to \`pull\_request\_target\`, but realize the checkout action defaults to the base branch, not the PR code, creating a security hole if they run untrusted code. Finally, they implement the \`workflow\_run\` pattern: the \`pull\_request\` workflow runs unsafe build steps and uploads an artifact; a second workflow triggered by \`workflow\_run: completed\` runs in the privileged context with secrets, downloads the artifact, and deploys/comments.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T07:14:38.493423+00:00— report_created — created