Skip to content

fix(ci): open coreutils drift PRs with a token permitted to create them - #2760

Merged
chaliy merged 2 commits into
mainfrom
eve/clever-mccarthy-206chj
Oct 20, 2026
Merged

chaliy merged 2 commits into
mainfrom
eve/clever-mccarthy-206chj

Conversation

@chaliy

@chaliy chaliy commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

What changed

The Coreutils Args Drift workflow's open-pr job now uses two tokens instead of
one. The drift branch is pushed with github.token (that only needs
contents: write), and the pull request is opened with a PAT read from
Doppler's GITHUB_TOKEN, gated on DOPPLER_TOKEN being configured and masked
before it crosses a step boundary — the pattern ci.yml already uses for
privileged CI credentials.

If no PAT is available the PR step fails with a message naming the branch the
drift was pushed to, rather than skipping the PR.

Why

The weekly run regenerated the drift, force-pushed it to
chore/coreutils-args-drift, then died on gh pr create:

pull request create failed: GraphQL: GitHub Actions is not permitted to
create or approve pull requests (createPullRequest)

(run 39234801977).
Everything before that was green — regeneration, build, cat/tac spec tests and
the differential harness all passed — so the run went red at the last step with
no PR to show for it, and the drift stayed invisible. That is the same
silent-absorption failure the open-PR-only lookup was added to prevent (#2161),
arriving through a different door.

The job token cannot open a PR while "Allow GitHub Actions to create and approve
pull requests" is off. Leaving that setting off is the right call — the same
switch also lets Actions approve PRs, which would weaken review gating — so
the fix gives the PR step its own credential instead of widening what the job
token may do.

Failing loudly on a missing PAT is deliberate. The drift is already pushed by
the time that step runs, so a green run with no PR would recreate exactly the
invisible-drift mode; a red run with the branch name in the error is
recoverable by hand.

The pending drift this run produced is in #2759.

Before / After

Before — Open pull request if drifted fails on every drifted run:

[chore/coreutils-args-drift 439fd13] chore(builtins): sync uutils/coreutils ...
 20 files changed, 22 insertions(+), 20 deletions(-)
 + d65b0d0...439fd13 chore/coreutils-args-drift -> chore/coreutils-args-drift (forced update)
pull request create failed: GraphQL: GitHub Actions is not permitted to create or approve pull requests (createPullRequest)
##[error]Process completed with exit code 1.

After — the push and the PR run on separate tokens, and the no-PAT path is a
named error instead of a GraphQL failure that reads like a bug in the drift
tooling. Both halves are pinned by tests that execute the workflow's own run
scripts with stubbed gh/git:

$ python3 -m unittest discover -s scripts/tests -p 'test_*.py'
Ran 179 tests in 7.673s
OK

$ python3 -m unittest scripts.tests.test_coreutils_drift_pr -v
test_missing_pat_fails_loudly_without_touching_the_pr ... ok
test_open_state_is_scoped_in_the_lookup ... ok
test_opens_a_new_pr_when_only_a_closed_one_matches ... ok
test_push_and_pr_use_separate_tokens ... ok
test_updates_the_existing_pr_when_one_is_open ... ok
Ran 5 tests in 0.132s
OK

Also checked: every run block in the workflow passes bash -n (this file has
been broken by shell/YAML slips before), the YAML parses, just check-okf
reports conformant, and just check-doc-links is clean.

The secret boundary this workflow documents is preserved, and verified
mechanically: the regenerate job — the only one that builds or executes
third-party uutils code — has no Doppler step and no secret access. The PAT
lives only in open-pr, which never checks out, builds or runs uutils; its two
remaining mentions of "uutils" are the commit message and the PR body text. The
workflow is also schedule + workflow_dispatch only, so no pull-request
trigger can reach the secret.

Risk

  • Low
  • The workflow is weekly and workflow_dispatch, and the change is confined to
    its open-pr job; no library or runtime code is touched.
  • The one dependency worth calling out: this needs Doppler to hold a
    GITHUB_TOKEN with pull-requests: write on this repository. I could not
    verify that from the sandbox — reading the secret store is blocked there — so
    if the key is absent or under-scoped, the next drifted run will say so
    explicitly in its error rather than failing obscurely. Worth a
    workflow_dispatch run to confirm end to end.

Checklist

  • Tests added or updated — existing tests retargeted to the split steps,
    plus two new ones: the fail-loud guard and the token split
  • Backward compatibility considered — CI-only change; no public surface

The weekly Coreutils Args Drift run regenerated the drift, force-pushed it to
`chore/coreutils-args-drift`, then died on `gh pr create` with "GitHub Actions
is not permitted to create or approve pull requests" (run 39234801977). The run
went red after all regeneration and tests were green, and no PR ever opened, so
the drift stayed invisible — the exact silent-absorption mode the open-PR-only
lookup was added to prevent.

The job token can push (it only needs `contents: write`) but cannot open a pull
request while "Allow GitHub Actions to create and approve pull requests" is off,
which is the setting worth keeping since the same switch also governs approvals.
So the step is split in two: the branch goes up on `github.token`, and the PR is
opened with a PAT read from Doppler's `GITHUB_TOKEN`, gated on `DOPPLER_TOKEN`
and masked before it crosses a step boundary — the pattern `ci.yml` already uses
for privileged CI credentials.

With no PAT the PR step fails naming the branch instead of skipping the PR: the
drift is pushed by then, so a green run without a PR would reintroduce silent
absorption.

Regression tests pin both halves: a missing PAT is a red run that touches no PR,
and the push keeps the job token while only the PR step gets the PAT.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bashkit e8c0474 Commit Preview URL

Branch Preview URL
Oct 20 2026, 09:43 AM

Introducing a credential into this workflow makes the job split a security
contract, not just a layout: `regenerate` executes third-party uutils code and
must stay secret-free, `open-pr` holds the PAT and must never build or run
uutils. Write it down next to the token-split rationale so a later change does
not move a Doppler step into the wrong job.
chaliy added a commit that referenced this pull request Oct 20, 2026
… modules (#2759)

Regenerated by `bashkit-coreutils-port` against uutils/coreutils@d2d96946b
(was 8613dffab). Utilities: cat ls mktemp od readlink realpath shuf stat tac
tee truncate. Vendored module: format.

Two upstream argument-surface changes; the rest is the rev header bump.

- `od`: the hidden FILENAME positional gains
  `value_parser(OsStringValueParser::new())`. No consumer change needed —
  #2754 landed `helpers::string_value` / `string_values`, which read od's
  arguments without naming the value type, so the switch is absorbed.
- `tac`: `-s/--separator` gains `allow_hyphen_values(true)`, so a
  hyphen-leading separator parses as a value rather than a flag, matching
  upstream.

This drift was produced by run 39234801977, which regenerated and pushed it
but could not open a PR: the Actions job token is not permitted to create
pull requests. The workflow's token is fixed separately in #2760.

Verified on the pinned toolchain (1.95.0): `cargo build -p bashkit` clean,
48 od unit tests and 14 tac unit tests pass, and the bash spec suite passes —
the same gate the drift workflow runs before proposing a diff.
@chaliy
chaliy merged commit 52d9232 into main Oct 20, 2026
22 checks passed
@chaliy
chaliy deleted the eve/clever-mccarthy-206chj branch October 20, 2026 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant