This private repository is a deliberately narrow command queue. ChatGPT creates one issue containing one JSON job; GitHub Actions runs trusted relay code; the relay validates the job, applies the supplied unified diff as data, pushes one new branch to an aryansk fork, opens one upstream pull request, and records the result on the issue.
The relay never runs code from the target repository. It does not install dependencies, execute project tests, run build scripts, execute issue-body commands, approve or merge pull requests, force-push, or push to an upstream repository.
ChatGPT GitHub connector
|
| creates one [pr-relay] issue in this private repository
v
GitHub Actions (per-issue concurrency group)
|
| trusted scripts/relay.mjs + PR_RELAY_TOKEN
v
public upstream metadata -> fresh base checkout -> safe git apply
|
v
aryansk fork branch -> upstream pull request -> relay issue result
The workflow uses a read-only GITHUB_TOKEN for checkout. Requested GitHub writes use PR_RELAY_TOKEN, which is never hard-coded and is never printed. The repository variable PR_RELAY_ENABLED defaults to a safe disabled state until explicitly enabled.
ChatGPT writes validated jobs to the Airtable Jobs table. GitHub Actions reads that table directly every five minutes; no intermediary service is required:
ChatGPT creates Airtable Pending record
-> scheduled GitHub Actions Airtable poller
-> [pr-relay] GitHub issue
-> existing issue-triggered relay
-> aryansk fork branch
-> upstream draft PR
The poll_queue job supports both the */5 * * * * schedule and workflow_dispatch. It uses the AIRTABLE_TOKEN Actions secret for Airtable reads/status updates, the existing PR_RELAY_TOKEN secret only for GitHub writes, and the same PR_RELAY_ENABLED kill switch. Queue polling and issue processing have separate concurrency groups: queue polls cannot overlap, while issue-triggered runs remain isolated by relay issue number.
The poller reads base appppBJ8XPrwVIi3L, table tblntNqkRKxAiP0G8, using the configured field IDs. It considers only Pending rows, sorts them by Airtable createdTime oldest-first, and processes at most 50 jobs per poll. Job ID, Title, Body, and Branch map directly to the validated relay job. Consumed and Failed rows are ignored.
Create an Airtable personal access token at Airtable → Developer hub → Personal access tokens with only data.records:read and data.records:write access restricted to the PR Relay Queue base. Store it directly as the GitHub Actions repository secret AIRTABLE_TOKEN; never put it in an issue, source code, or ChatGPT. PR_RELAY_TOKEN remains only in GitHub Actions.
Before any GitHub issue write, the poller reuses parseRelayTitle, parseIssueBody, and validateJob. A permanently malformed Pending row is marked Failed. If its validated branch already appears in an existing relay issue, the row is marked Consumed without creating an issue. Otherwise the poller creates exactly one relay issue and marks the Airtable row Consumed only after successful creation. GitHub/Airtable transient failures leave the row Pending and fail the workflow so the next poll can retry safely.
The issue body, title, patch, repository names, branch names, commit message, and PR text are untrusted input.
- The fork owner must be exactly
aryansk. - The upstream must be a public GitHub repository, and the fork must be a direct fork of that exact upstream.
- The relay title must be exactly
[pr-relay] owner/repository #123. - The base branch is validated; the requested branch must be a new, non-default, safe Git ref.
- Repository names and paths reject absolute paths, traversal components, control characters, shell metacharacters in refs, and Git metadata paths.
- There is no command or script field. All values are passed as argument-array values to
spawn;shellis disabled. - The patch is size-limited, must be a text unified diff, is checked with
git apply --check, and is then applied without--rejectso a partial application cannot be silently accepted. - Binary patches, symlinks, submodules, empty diffs, malformed diffs, and zero-change results are rejected.
.github/**, other common CI/automation entrypoints, credential-like paths, private keys, environment files, registry configuration, and Terraform state are blocked by default.- An administrator can explicitly allow a narrow path or directory with the repository variable
PR_RELAY_ALLOWED_SENSITIVE_PATHS, for example.github/workflows/approved.ymlor.github/workflows/**. The issue payload cannot override this policy. - Issue body size is limited to 128 KiB and patch size to 512 KiB. Git and API operations have timeouts, and every job uses a fresh temporary directory that is removed in a
finallyblock. - The target repository's package managers, hooks, workflows, tests, builds, and executable files are not run while the credential is present.
- The relay searches for an existing PR from
aryanskfor the requested branch and referenced issue, and for an existing fork branch. A processed issue has a deterministic job ID and terminal result marker. The same issue cannot create a second PR. - Workflow-level concurrency is keyed by relay issue number, and application-level duplicate checks run before and after the push. Pushes are non-force pushes to
aryansk/<fork>:<branch>only. - Status comments contain only a PR URL, branch, commit, or a short sanitized failure reason. Authentication-looking values and workflow-command syntax are redacted.
The repository is intended to be private. Do not make it public while it contains a write-capable credential secret.
For this relay's arbitrary-public-upstream use case, a fine-grained PAT is not reliable: GitHub documents that only classic PATs have write access to public repositories not owned by you or an organization you belong to. Use a dedicated, expiring classic PAT as the narrowest practical credential for this architecture.
- Open GitHub → profile photo → Settings → Developer settings → Personal access tokens → Tokens (classic).
- Choose Generate new token (classic), set a short practical expiration, and select only the
reposcope. - Do not select
workflow,admin:org,delete_repo,write:packages, or unrelated scopes. The default sensitive-path policy also rejects workflow changes. - Keep the token in your password manager. Never paste it into ChatGPT, Codex, an issue body, source code, or a terminal transcript.
The classic repo scope is broader than ideal because GitHub does not provide a fine-grained PAT that can reliably create PRs against arbitrary public repositories. If all future upstreams are owned by one organization, revisit a fine-grained PAT or GitHub App with repository-specific permissions instead.
In aryansk/pr-relay → Settings → Secrets and variables → Actions → Secrets → New repository secret:
- Name:
PR_RELAY_TOKEN - Secret: paste the PAT directly into GitHub
Save it. Do not send the token to Codex or include it in a message.
In aryansk/pr-relay → Settings → Secrets and variables → Actions → Variables:
- Create
PR_RELAY_ENABLEDwith valuefalsewhile setting up. - After the secret is saved and you are ready for a test, change it to
true. - Leave
PR_RELAY_ALLOWED_SENSITIVE_PATHSabsent or empty unless you have reviewed the exact sensitive path that should be allowed.
When PR_RELAY_ENABLED is anything other than the exact string true, the job is skipped and no relay write is performed. To stop immediately, set it to false, disable the workflow if needed, and revoke/delete PR_RELAY_TOKEN. Revoking the token removes the relay's GitHub write access even if a new run starts.
Create an issue in aryansk/pr-relay with this exact title shape:
[pr-relay] apify/crawlee-python #1985
The body must contain exactly one JSON object, either as plain JSON or as one json code fence. Do not add explanatory text, attachments, commands, tokens, or additional JSON objects.
{
"version": 1,
"upstream": "apify/crawlee-python",
"fork": "aryansk/crawlee-python",
"base": "master",
"branch": "fix/1985-request-queue-is-finished",
"commitMessage": "fix: make RequestQueueClient.is_finished abstract",
"prTitle": "fix: make RequestQueueClient.is_finished abstract",
"prBody": "Fixes #1985",
"patch": "<unified git patch generated against the current master>",
"draft": true,
"upstreamIssue": 1985
}upstreamIssue is optional, but when supplied it must match the issue number in the title. The payload schema is also checked by schemas/pr-job.schema.json; runtime validation is stricter about bytes, control characters, Git refs, patch headers, and sensitive paths.
One relay issue is one job. To submit another contribution, create another relay issue. A failed job can be intentionally retried by adding a relay/retry label, but a successful job is always terminal. A retry still cannot overwrite an existing branch or create a duplicate PR.
The workflow creates or reuses:
relay/pendingrelay/runningrelay/successrelay/failedrelay/retryfor an explicit manual retry request
Terminal comments have machine-readable markers such as <!-- pr-relay-result job=... state=success -->. Do not edit or copy these markers into the PR body.
Success:
SUCCESS
PR: https://github.com/upstream/project/pull/123
Branch: fix/example
Commit: <40-character SHA>
Failure:
FAILED
Stage: patch-validation
Reason: patch is not a unified git diff with a file path and hunk
Use a single issue per PR. Inspect the upstream repository, issue, base branch, and existing PRs first. Generate a text unified diff against the current upstream base. Then create one issue in aryansk/pr-relay with the exact title and JSON-only body above. Never put a PAT, API key, SSH key, or other secret in the issue. Wait for the relay issue's terminal SUCCESS or FAILED comment before reporting the outcome. Do not create five relay issues in one burst until one complete test has been inspected.
A short reusable prompt is:
For exactly one already-reviewed open-source fix, create one issue in the private
aryansk/pr-relayrepository. Use title[pr-relay] <upstream-owner>/<upstream-repo> #<issue-number>and a body containing only the validated JSON job described inREADME.md. Use a directaryanskfork, a fresh feature branch, a unified text patch, anddraft: truefor the first test. Never include credentials. Wait for the relay issue's terminal status comment and return its PR URL or failure stage.
- No workflow run: check that Actions are enabled,
PR_RELAY_ENABLEDis exactlytrue, the issue title starts with[pr-relay], and the event isopened,edited,reopened, or an explicitrelay/retrylabel event. - Airtable queue read failure: verify that the
AIRTABLE_TOKENGitHub Actions secret exists, has read/write access to the PR Relay Queue base, and that the configured base, table, and field IDs still match Airtable. - Airtable status update failure: the row remains Pending and the workflow fails safely; inspect Airtable/API availability and let the next scheduled poll retry. Existing branch/issue dedupe prevents duplicate relay work.
- Authentication failure: verify the secret name is exactly
PR_RELAY_TOKEN, the classic PAT has not expired/revoked, and it has thereposcope. Rotate it by creating a replacement, updating the repository secret, then revoking the old token. - Repository validation failure: the upstream must be public, the fork must already exist under
aryansk, and GitHub's fork metadata must identify that exact upstream as its direct parent. - Duplicate-protection failure: inspect the requested branch and existing PRs. The relay will not overwrite an existing fork branch or force-push it. Use a new branch and a new relay issue only when the existing work is intentionally separate.
- Patch failure: generate the diff from a clean checkout of the exact upstream base. Do not include binary changes, symlinks, submodules, workflow files, credential-like paths, or repository automation files.
- Failure after push: the relay may have safely pushed a branch before a later API call failed. Inspect GitHub for the branch/PR before retrying; duplicate checks prevent a second PR.
- Set
PR_RELAY_ENABLED=false. - Revoke/delete the old classic PAT in GitHub settings.
- Generate a replacement with the same minimum scope and expiration.
- Update
PR_RELAY_TOKENdirectly in the repository secret UI. - Confirm the variable remains
false; set it totrueonly after reviewing the replacement and any in-flight runs.
If the relay code itself is suspected of compromise, disable the workflow and revoke the token first. Because the workflow checks out the repository's trusted default-branch code, protect the default branch and review changes to .github/workflows/pr-relay.yml, scripts/relay.mjs, and the schema before re-enabling it.
This first version favors fail-closed behavior over maximum patch compatibility. It accepts ordinary text unified diffs only; it does not run tests or builds, handle binary files, modify sensitive paths by default, update existing branches, force-push, merge, approve, or bypass branch protections. GitHub Actions and PR checks remain the source of truth for whether the resulting contribution is correct.