Skip to content

Commit 2ad2382

Browse files
hotlongclaude
andauthored
feat(pm): fleet GitHub writes act as objectstack-fleet[bot] behind one fleet-wide write gate (token minter · lease + batch gap · issue-create door · with-fleet wrapper) (#19642)
Fixes #19641 Clause-②: no ## What this lands Two seat accounts were restricted by the platform on 2026-09-21. The token the seats wrote with was **user-to-server** (`GET /user` answered a login; every write booked against a person), and the trigger was **rate shape**, not quota: `X-RateLimit-Remaining` never left 15000/15000, while 17 issues in 29 s — N processes each sleeping its own 1.2 s — got the account restricted for 08:16–08:52 UTC, after which every push by it got no `github-actions` check-suite. Both halves of the answer, mechanical: - **`scripts/pm/fleet-token.mjs`** — the fleet's identity. An RS256 App JWT built on node `crypto` (zero dependencies): `iat = now − 60`, `exp = now + 600` (the ceiling; `buildAppJwt` refuses longer), `iss = OS_FLEET_APP_ID`. `POST /app/installations/{id}/access_tokens`, then `GET /app` and `GET /users/<slug>[bot]` for the identity. Cache at mode 0600 beside the throttle's log, refreshed 5 min early; minting takes the write lease and re-reads the cache after the wait, so N processes starting together mint once. `--export` (for `eval`), `--print`, `--status` (redacted), `--refresh`; `getFleetToken()` for importers. `OS_FLEET_PRIVATE_KEY` as PEM, PEM with literal `\n`, or base64 — detected. Every log line, status line, error message and stack is redacted; the self-test feeds a known token and a known key through every one of those paths and greps them all. - **`scripts/pm/write-pace.mjs`** — the gate, extended rather than duplicated. Rule ④, the **lease**: one write in flight fleet-wide, held from reservation to response (through the gap sleep), a FIFO ticket queue beside a `mkdir` exclusion primitive (the shape of `os-verify-lock.sh`, whose `flock` this host lacks), dead or over-held holders broken and said out loud, a 15-minute bounded wait that refuses with exit 10 rather than spinning. Rule ⑤, **batch mode**: `--announce-batch N` / `announceBatch()` for N ≥ 5 raises the gap to 30 s for that identity's next N writes, fleet-wide through the shared log, and prints the count and the estimate. `--run [--read] [--kind K] [--batch N] -- <command…>` is the door for shell callers. The self-test's rule ④ and ⑤ batteries spawn **real child processes** and read their timestamps: no two gated commands overlapped, consecutive reservations sat ≥ the gap apart by the log's own stamps, and a batch announced in one process paced two others. - **`scripts/pm/issue-create.mjs`** — the door a new card leaves through (creation had no tool; a bare `curl` loop is where the burst came from). Prose from files, never argv; one POST behind both halves of the throttle; a read-back; the exit-code ladder 0 · 2 · 3 · 4 · 5 · 10. This PR's own card, its claim comment and its assignee were written through `issue-create.mjs`, `post-stamped.mjs` and `label-write.mjs` — through this branch's throttle, behind the lease. - **`scripts/pm/with-fleet.sh`** + **`scripts/pm/fleet-git-credential.sh`** — mint → `eval` the exports (`GITHUB_TOKEN`, `GH_TOKEN`: the existing `process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN` call sites change by **zero** lines) → git identity from the environment (committer = the bot by its **user** id; author = the bot, or a real login via `OS_FLEET_GIT_AUTHOR_LOGIN`, resolved by the minter) → git credential through `GIT_CONFIG_*` env pairs that reset the helper list and add the fleet helper (nothing on disk; no token in any URL; `git remote -v` unchanged) → the command behind `write-pace.mjs --run`. Its self-test reads a fixture cache in place of the platform and proves what git sees, including `git credential fill` answered by the fleet helper and not the host keychain. - **`label-write.mjs`, `close-cards.mjs`** — release the lease on a transport that threw (no response will come). **`AGENTS.md`** — the rule, one paragraph. **`lint.yml`, `package.json`** — the three new self-tests run in CI. ## Deviations from the brief, stated rather than hidden - The brief asked for a gate modelled on `os-verify-lock.sh`. That script's primitive is `flock`, which macOS does not ship — on this host it runs in its declared unlocked mode — and the repo already carried a cross-process write throttle with a `mkdir` lock (`write-pace.mjs`). A second lock beside it would be the second implementation the route-ownership rule forbids, so the lease and the batch mode extend `write-pace.mjs`; the ticket-queue **shape** of `os-verify-lock.sh` is mirrored, its primitive is not. - The brief asked for exponential back-off (2/4/8/16 s) on a 403/429 without `Retry-After`. Not done: that convention is for a dropped transport, and a secondary-limit refusal retried after 16 s inside one process is precisely the per-process pacing that produced the burst. Rule ③ (a 30-minute stop marker, or the `Retry-After` when it reaches further, refused loudly by every seat sharing the identity) is the conservative end of the platform's own guidance and stays. - The brief asked for the token cache in the session scratchpad. It lives beside the throttle's log (`~/.cache/objectstack-pm/fleet-token.json`, `OS_FLEET_TOKEN_CACHE_FILE` to move it): N sessions each minting into their own scratchpad is the concurrent minting the lock exists to prevent. - The brief spelled the bot's noreply address with the App id. GitHub's noreply form uses the bot **user** id (`332303061` for `objectstack-fleet[bot]`; the App id is `5028251`); the minter resolves it from `GET /users/<slug>[bot]` and the wrapper uses it. - **The lease and the token cache are FILESYSTEM-scoped, so "fleet-wide" reaches exactly as far as one filesystem — and the maintainer has confirmed there is no shared volume to mount into the cloud containers.** On a host where every session shares one home the lease serialises the whole fleet; inside one cloud container it covers the seat and every subagent it fans out into worktrees, which IS the shape of the incident (one seat's fan-out, each process pacing itself). Across containers it is not shared: N containers are N independently paced streams against one identity. The file's header now states that boundary rather than implying more, and the follow-up is filed as #19669 — measure the installation's shared `x-ratelimit-used` counter (carried on every response, zero extra requests) as a cross-container signal, and meanwhile adopt this file's overrides as a declared conservative per-container profile. A lock ref on GitHub was considered and rejected there: two extra write requests per content write against the platform's own hourly content limit. ## Findings the maintainer needs - **Installation permissions — resolved.** At 2026-09-22T03:10Z the `objectstack-fleet` installation carried no `pull_requests`; the maintainer added it at the App and the organization accepted it: the token minted at 05:10Z carries `actions:write checks:write contents:write issues:write metadata:read pull_requests:write workflows:write`. - `.claude/skills/pm-dispatch/references/rest-channel.md` still teaches bare `curl -X POST … ready_for_review` and `curl -X PUT … auto_merge` recipes. `.claude/**` is Tier S and untouched here; a follow-up should re-point them at `scripts/pm/with-fleet.sh -- gh api …`. - `scripts/pm/ensure-pm-labels.sh` issues `gh label create` / `gh label edit` writes outside the gate — a pre-existing shell path, left as is here. ## Two defects the live run found, fixed in the same PR - **A gated tool under `with-fleet.sh` queued behind its own wrapper.** `--run` held the lease for the whole command, and `issue-create.mjs` takes the lease for its POST, so the tool waited until the two-minute hold bound broke its parent's lease (the write still landed). Now `--run` names the holding pid in `OS_PM_WRITE_LEASE_HOLDER`, a child whose acquire finds that pid holding inherits the turn at once (still reserving its own slot, never releasing a lease it did not take), and `with-fleet.sh` runs a roster tool with identity only — no second gate around a tool that gates itself. Pinned in both self-tests. - **`--print` in a shell with no inputs and no session token printed nothing**, so a caller sent an empty bearer and read a 401 that looked like a revoked token. A fresh cache is now served as is in that shell; a stale one refuses naming both routes. ## Where the inputs live — organization variables, the maintainer's decision Configuring `OS_FLEET_*` into every Claude account's cloud environment did not scale, so the maintainer holds all three — the private key included — as **organization variables** of `objectstack-ai`, shared with the board repo. `fleet-token.mjs` reads whatever the environment lacks with the session's own GitHub token, through the REPO endpoints only: the repository variable first, then `GET /repos/{repo}/actions/organization-variables` (the `/orgs/` endpoint needs an org-level permission a seat's token does not carry — measured 403 with a `repo`-scoped token, 200 through the repo endpoint). The environment wins per variable; `OS_FLEET_INPUTS_FROM_GITHUB=0` closes the route; `OS_FLEET_VARIABLES_REPO` names another board. A key read this way lives in memory for one mint and is never written anywhere — the cache stays token-only, and the self-test pins that. The trust boundary this widens, stated in the file's header and here: a repository or organization variable is plaintext, readable by every collaborator and by every App installation holding `actions: read` on the repo, so every one of them can mint as the fleet. The maintainer took the decision with that consequence in front of them (a Secret would not serve: no API reads a Secret back). Whether a fleet container's egress proxy allows the two repo endpoints is the one thing not measurable from this host — online step 1 from a cloud session answers it. ## Ruling — `AGENTS.md` line ceiling 1109 → 1116 `AGENTS.md` sat at its shrink-only ceiling (1109, headroom 0); the write-gate rule this PR adds costs six lines after compression from sixteen. `check:pm-skill-ratchet` requires a maintainer ruling quoted in the raising PR's body. The maintainer's answer to option A (raise the ceiling to 1116, ruling quoted here; option B was compressing seven lines of other rules), verbatim and untranslated, recorded on #19641 in [comment 5771401251](#19641 (comment)): > 同意 A(推荐) The `CEILINGS` row in `scripts/pm/check-skill-line-ratchet.mjs` moves 1109 → 1116 and carries the same quotation. No other ceiling changes. ## Governed surface The diff touches `AGENTS.md` (Tier H). The PR is pushed and left here: no seat arms auto-merge; landing waits for the maintainer's approving review. Nothing publishes from any released package, so this PR carries the `skip-changeset` label instead of a changeset. ## Verification — exit codes captured before any pipe | command | exit | |:--|:--| | `node scripts/pm/write-pace.mjs --self-test` (109 cases · 12 batteries · real child processes) | 0 | | `node scripts/pm/fleet-token.mjs --self-test` (73 cases · 8 batteries · offline) | 0 | | `node scripts/pm/issue-create.mjs --self-test` (34 cases · 5 batteries · offline) | 0 | | `bash scripts/pm/with-fleet.sh --self-test` (19 cases · fixture cache · throwaway repo) | 0 | | `label-write` · `close-cards` · `post-stamped` · `sweep-closed-cards` · `sweep-stale-finding` `--self-test` | 0 · 0 · 0 · 0 · 0 | | `pnpm lint` (through `os-verify-lock.sh`) | 0 | | `node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD` | 0 | | `check:pm-skill-id-lint` · `check:pm-governed-prose` · `check:self-test-wired` · `check:pm-skill-ratchet` (after the ruled raise) | 0 · 0 · 0 · 0 | | `check:adr-anchors` · `check:doc-authoring` · `check:corpus-claim-drift` · `check:issue-citations` · `check:cross-package-test-inputs` · `check:workflow-step-name-quoting` · `check:required-contexts` (and the rest of the AGENTS.md / lint.yml readers) | 0 | No TypeScript package is touched, so no `typecheck` applies. Four gates in the same local sweep exited non-zero for reasons outside this diff: `check:agent-test-spelling` (a finding in the untracked `.claude/settings.local.json`), and `check:dev-prereqs` / `check:i18n` / `check:i18n-coverage` (this worktree has no built `packages/spec/dist`; the last two exit 3 = nothing measured). CI checks out and builds fresh. **Declared narrowing — verification ran UNLOCKED.** `scripts/pm/os-verify-lock.sh` could not take the shared verify lock on this host: no usable `flock`. The shared verify lock is declared Linux-only (`flock` is util-linux, and a stock macOS does not ship it), so the command below was run directly, without the lock — a declared narrowing, not a silent one. No serialization guarantee held for this run, nor for any sibling agent in this container while it ran. pnpm lint ## Online acceptance — for the maintainer, with the injected `OS_FLEET_*` 1. `node scripts/pm/fleet-token.mjs --print` returns a token; `curl -sI -H "Authorization: Bearer $(node scripts/pm/fleet-token.mjs --print)" https://api.github.com/rate_limit` shows an empty `X-OAuth-Scopes` and `X-RateLimit-Limit` ≥ 5000. **Done 2026-09-22T05:10Z from this Mac**, with no `OS_FLEET_*` in the shell (inputs read from the organization variables): exit 0, minted for `objectstack-fleet[bot]` (user id 332303061), `x-ratelimit-limit: 15000`, no `x-oauth-scopes` header at all (a server-to-server token sends none). 2. ⭐ `GET /user` with that token **must fail (403)** — a server-to-server token has nobody behind it. A login in the answer means the token is still user-to-server: the migration did not take — stop. **Done**: HTTP 403 `Resource not accessible by integration` — nobody behind the token; the migration took. 3. `scripts/pm/with-fleet.sh -- node scripts/pm/issue-create.mjs --title-file t.txt --body-file b.md` on a test card, and `scripts/pm/with-fleet.sh -- gh pr create …` on a test branch: both must show the author as `objectstack-fleet[bot]`. The PR half needs the `pull_requests` permission accepted on the installation first. **Done 2026-09-22T05:2x–05:34Z**: [#19663](#19663) created by `objectstack-fleet[bot]` (issue-create through the wrapper); an empty commit committed as `objectstack-fleet[bot] <332303061+objectstack-fleet[bot]@users.noreply.github.com>`, pushed as the bot, and [#19664](#19664) opened by `objectstack-fleet[bot]` (type Bot) — its head got **10** `github-actions` check-suites within a minute. Both closed by the same identity, the branch deleted. 4. ⭐ The open question: with that token, `scripts/pm/with-fleet.sh --kind 'git push' -- git push origin HEAD` of one real commit onto `claude/issue-19354-effective-object-permission-rescue`, then `GET /repos/objectstack-ai/objectstack/commits/<new sha>/check-suites` — is the `github-actions` suite count > 0? Every push since 2026-09-21 08:52 read 0. > 0 means the block was the identity and PR #19540 is unblocked; 0 means the restriction is not only on the person — report either result as it is. (That PR and its card answer 404 to every other account, consistent with the author's content being hidden; only the maintainer can read it.) **Done, and the answer is 0.** Empty commit `57f7dfdb` committed and pushed onto that branch as `objectstack-fleet[bot]` at 05:32Z; at +60 s and again at +150 s: check-suites total 5 (`claude`, `cloudflare-workers-and-pages`, `fly-io`, `objectstack-fleet`, `vercel`), **`github-actions` 0**, workflow runs by `head_sha` 0 — while the bot's own PR #19664 got 10 at the same minute. So the restriction is on that pull request (or its author), not on who pushes: changing the pusher does not revive it. The remedy is a fresh PR from a fresh branch carrying the same commits. The bot token, like every other account, reads #19540 and #19354 as 404. 5. `node scripts/pm/write-pace.mjs --status` after the above shows the reservations under the App key for the mint and under the token's key for the writes, and the lease free. ## Suggestion — not an instrument A scan over `scripts/`, `.claude/` and `.github/` for bare write calls (`curl -X POST|PATCH|PUT|DELETE`, `fetch(` with a write verb outside `scripts/pm`, `gh api -X …`, `gh label create`) would make the AGENTS.md rule mechanical and would have caught the loop that caused this. Not added here: a new instrument takes the maintainer's word. --- _Generated by [Claude Code](https://claude.ai/code/session_local_fce2b4bd-ec0d-4e1c-8395-224cd17995e6)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 97f4f8c commit 2ad2382

11 files changed

Lines changed: 2883 additions & 53 deletions

‎.github/workflows/lint.yml‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,12 +1355,35 @@ jobs:
13551355
# the throttle would simply stop throttling — which is the shape
13561356
# `check:self-test-wired` requires this step for.
13571357
#
1358-
# Its last battery is the wiring: the five write transports each call both
1358+
# Its last battery is the wiring: the seven write transports each call both
13591359
# halves, and the roster IS the set of files under scripts/pm that issue a
1360-
# write verb, so a SIXTH write path cannot land unpaced.
1360+
# write verb, so an EIGHTH write path cannot land unpaced. Two batteries
1361+
# run REAL child processes: the lease that keeps one write in flight
1362+
# fleet-wide, and the batch gap one process announces and two others obey,
1363+
# are claims about separate processes and are measured on separate
1364+
# processes, by their timestamps.
13611365
- name: Write-pace throttle self-test
13621366
run: pnpm check:pm-write-pace
13631367

1368+
# The fleet identity and the two doors — same family, same split. The
1369+
# minter's LIVE path is a POST to the platform signed with a private key,
1370+
# so the offline half is all CI can hold: a throwaway RSA pair, a fake
1371+
# platform through every branch, and the assertion that matters most — a
1372+
# known token and a known key fed through every output path and grepped
1373+
# out of ALL of them. The card-creation door and the shell wrapper both
1374+
# pin their wiring into the throttle above; the wrapper's self-test reads a
1375+
# fixture cache in place of the platform and proves what git sees (the
1376+
# bot as committer, no token in any remote or config file, a credential
1377+
# helper reached from the environment alone).
1378+
- name: Fleet token minter self-test
1379+
run: pnpm check:pm-fleet-token
1380+
1381+
- name: Issue-create door self-test
1382+
run: pnpm check:pm-issue-create
1383+
1384+
- name: with-fleet wrapper self-test
1385+
run: pnpm check:pm-with-fleet
1386+
13641387
- name: Stamped-post helper self-test
13651388
run: pnpm check:pm-post-stamped
13661389

‎AGENTS.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,13 @@ and posts a `Release:` line. Findings are filed unassigned; assign at the moment
413413
maintainer's own work under the shared account carries no claim comment and is indistinguishable
414414
from a dispatch — an accepted blind spot; per-seat identities are not introduced.
415415

416+
**Every GitHub write leaves through `scripts/pm/`, as `objectstack-fleet[bot]`, behind the shared write
417+
gate.** `fleet-token.mjs` mints the App token from `OS_FLEET_APP_ID` / `OS_FLEET_INSTALLATION_ID` /
418+
`OS_FLEET_PRIVATE_KEY`; `write-pace.mjs` serialises and spaces every write verb fleet-wide; `issue-create`,
419+
`label-write`, `post-stamped` and `close-cards` own their writes; `with-fleet.sh -- <command…>` runs anything
420+
else behind the gate (`--read` for reads). ⛔ Never a bare `curl` / `fetch` / `gh api -X POST|PATCH|PUT|DELETE`
421+
write; never a token in a remote URL, a `git config` value on disk or a log line. Each header is the authority.
422+
416423
**State on your PR that you did not set belongs to another actor — ask, never "correct"
417424
it.** Under one shared identity every other participant's write arrives unsigned: the PM
418425
flipping your draft to ready and arming auto-merge, a bot re-labelling, the platform

‎package.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@
8888
"check:pm-closed-card-sweep": "node scripts/pm/sweep-closed-cards.mjs --self-test",
8989
"check:pm-stale-finding": "node scripts/pm/sweep-stale-finding.mjs --self-test",
9090
"check:pm-write-pace": "node scripts/pm/write-pace.mjs --self-test",
91+
"check:pm-fleet-token": "node scripts/pm/fleet-token.mjs --self-test",
92+
"check:pm-issue-create": "node scripts/pm/issue-create.mjs --self-test",
93+
"check:pm-with-fleet": "bash scripts/pm/with-fleet.sh --self-test",
9194
"check:pm-post-stamped": "node scripts/pm/post-stamped.mjs --self-test",
9295
"check:pm-label-write": "node scripts/pm/label-write.mjs --self-test",
9396
"check:pm-close-cards": "node scripts/pm/close-cards.mjs --self-test",

‎scripts/pm/check-skill-line-ratchet.mjs‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1495,7 +1495,17 @@ export const CEILINGS = new Map([
14951495
// standing letter-C ruling 5716260390 left unruled when it named the text.
14961496
// AGENTS.md is not a CROSS_FILE_MOVES destination, so no `ruledRaises` record
14971497
// applies. Landed count, headroom 0, same convention.
1498-
['AGENTS.md', 1109],
1498+
//
1499+
// 1109 → 1116 (card #19641, PR #19642): the six-line rule that every GitHub
1500+
// write leaves through scripts/pm/ as the fleet's App identity, behind the
1501+
// shared write gate — bought by CONTENT (a rule with no prior spelling
1502+
// anywhere in the file), after the paragraph was first compressed from
1503+
// sixteen lines to six. Maintainer ruling, verbatim and untranslated:
1504+
// 「同意 A(推荐)」 — the maintainer's live instruction to the desktop session
1505+
// that raised it, recorded on #19641 comment 5771401251 together with the
1506+
// option A text it accepted (raise to 1116, ruling quoted in the PR body).
1507+
// Landed count, headroom 0, same convention.
1508+
['AGENTS.md', 1116],
14991509
// #9965: root CLAUDE.md is the other repo-root instruction file — same read
15001510
// path (every seat session), same governance (Prime Directive #14). It is
15011511
// structurally growth-prone in the way the ratchet is built for: it exists to

‎scripts/pm/close-cards.mjs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ import {
141141
resolveSweepRepo,
142142
} from './check-half-states.mjs';
143143
import { classifyHttp, parseOptions as parseLabelWriteOptions, runLabelWrite } from './label-write.mjs';
144-
import { isWriteMethod, noteResponse, paceWrite } from './write-pace.mjs';
144+
import { isWriteMethod, noteResponse, paceWrite, releaseWriteLease } from './write-pace.mjs';
145145
import {
146146
EXIT_NOT_STORED as POST_STAMPED_EXIT_NOT_STORED,
147147
STAMP_TOKEN,
@@ -455,6 +455,7 @@ async function rest(path, { method = 'GET', body = null } = {}) {
455455
...(body ? { body: JSON.stringify(body) } : {}),
456456
});
457457
} catch (e) {
458+
if (paced) releaseWriteLease(); // ⏱ rule ④: no response will come, so the fleet's turn ends here
458459
return { status: 0, rateRemaining: null, json: null, detail: e?.message ?? 'fetch threw', call: `${method} ${path}` };
459460
}
460461
const rateRemaining = res.headers.get('x-ratelimit-remaining');

‎scripts/pm/fleet-git-credential.sh‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env bash
2+
# fleet-git-credential.sh — the git credential helper `with-fleet.sh` hands git
3+
# through the ENVIRONMENT, so a push can act as the fleet's App identity with
4+
# nothing on disk: no token in a remote URL, no `git config` value in any file,
5+
# and `git remote -v` unchanged.
6+
#
7+
# printf 'protocol=https\nhost=github.com\n' | GITHUB_TOKEN=… fleet-git-credential.sh get
8+
# → username=x-access-token
9+
# password=…
10+
#
11+
# git calls it as `<helper> get` with the request on stdin. It answers ONLY for
12+
# github.com (and its subdomains), ONLY for `get`, and ONLY when `GITHUB_TOKEN`
13+
# is set — every other case prints nothing, which git reads as "this helper has
14+
# no opinion" and moves on. `store` and `erase` are ignored on purpose: a token
15+
# that lives fifty-five minutes has nothing to store and nothing to erase.
16+
#
17+
# ⛔ The token is read from the environment at the moment git asks and is
18+
# written to stdout for git alone. It appears in no argument, no file and no
19+
# log line of this script.
20+
21+
set -euo pipefail
22+
23+
op="${1:-get}"
24+
[[ "$op" == get ]] || exit 0
25+
26+
host=''
27+
while IFS= read -r line; do
28+
case "$line" in
29+
host=*) host="${line#host=}" ;;
30+
esac
31+
done
32+
33+
case "$host" in
34+
github.com | *.github.com) ;;
35+
*) exit 0 ;;
36+
esac
37+
38+
[[ -n "${GITHUB_TOKEN:-}" ]] || exit 0
39+
40+
printf 'username=x-access-token\npassword=%s\n' "$GITHUB_TOKEN"

0 commit comments

Comments
 (0)