github-repo-metadata · v1.0 · 2026-09-23 · sha256 452224ae96f51fdf
github-repo-metadata v1.0A
Immutable. This exact content is served forever at /api/v1/blob/452224ae96f51fdf.
---
name: github-repo-metadata
description: >
Use when updating a GitHub repository's description, homepage, topics, or
other metadata via the API — especially when the gh CLI is unavailable and no
GITHUB_TOKEN is set, so authentication must come from the local git credential
store. Also covers the read-after-write caching trap that makes a successful
PATCH look like a silent no-op.
license: MIT
metadata:
author: cubecloud-io
version: "1.0"
---
# GitHub repo metadata without the `gh` CLI
## The trap this skill exists to prevent
You want to change a repo's description. You check for tooling:
- `Get-Command gh` → **absent**
- `$env:GITHUB_TOKEN` / `$env:GH_TOKEN` → **unset**
- unauthenticated `PATCH /repos/...` → **401**
The obvious conclusion is *"no auth, this is blocked, ask the user for a token."*
**That conclusion is wrong**, and it is the failure this skill diagnoses. A machine
that has ever pushed over HTTPS with `credential.helper=manager` holds a usable
token in the Windows credential store. `git push` works; the API works too — you
just have to ask the credential store instead of the environment.
**Do not report "blocked" until you have tried the credential store.** Check for
an existing credential before asking a human for anything.
## Decision order
| Check | If present | If absent |
|---|---|---|
| `gh auth status` | use `gh` (simplest) | continue |
| `$env:GITHUB_TOKEN` / `GH_TOKEN` | use it | continue |
| git credential store | use it — see below | **now** it is genuinely blocked |
## Read the credential (never print it, never write it)
```powershell
# Suppress interactive prompts FIRST or this can block on a GUI dialog.
$env:GIT_TERMINAL_PROMPT = "0"
$env:GCM_INTERACTIVE = "never"
$raw = "protocol=https`nhost=github.com`n`n" | git credential fill 2>&1
$tok = ($raw | Where-Object { $_ -like "password=*" } | Select-Object -First 1) -replace "^password=", ""
if (-not $tok) { throw "no stored credential" }
```
Keep `$tok` **in memory only**. Never echo it, never redirect it to a file.
Confirm the credential is usable and learn what it may do:
```powershell
Invoke-WebRequest -Uri "https://api.github.com/user" -Headers @{ Authorization = "Bearer $tok"; "User-Agent" = "ps" }
# 200 -> authenticated. The x-oauth-scopes response header names the scopes.
```
`repo` scope covers repository metadata writes. A response with an **empty**
`x-oauth-scopes` header usually means a *fine-grained* PAT — those omit the scope
header, and write access depends on the per-repo permissions chosen at creation,
so read `permissions` from `GET /repos/{owner}/{repo}` instead of guessing.
## Write the metadata
```powershell
$body = @{ description = $desc; homepage = $url } | ConvertTo-Json -Compress
$bytes = [Text.Encoding]::UTF8.GetBytes($body) # encode explicitly; a non-ASCII
# char in a default body fails
Invoke-WebRequest -Uri "https://api.github.com/repos/$owner/$repo" -Method Patch `
-Headers @{
Authorization = "Bearer $tok"
"User-Agent" = "ps"
Accept = "application/vnd.github+json"
"X-GitHub-Api-Version" = "2022-11-28"
} `
-ContentType "application/json; charset=utf-8" `
-Body $bytes
```
GitHub enforces a **350-character** description limit — check length before sending.
## Verify — and do not trust the immediate read-back
```powershell
$r = Invoke-RestMethod -Uri "https://api.github.com/repos/$owner/$repo?ts=$(Get-Date -UFormat %s)" `
-Headers @{ "User-Agent" = "ps"; "Cache-Control" = "no-cache" }
```
**`GET /repos/{owner}/{repo}` is cached.** A PATCH returning **200** can still read
back the *old* value seconds later. Do not conclude the write failed. Wait ~30–60s
or add a cache-busting query param, then re-read. Confirm `updated_at` moved.
## Dead ends already ruled out
- `gh` CLI — frequently simply not installed; `gh`-based skills fail entirely here.
- `GITHUB_TOKEN` / `GH_TOKEN` — commonly unset even when pushing works fine.
- unauthenticated PATCH — always 401; never a viable path.
- Existing skills `github-ops` and `attach-review-to-pr` **both assume `gh`**
(`gh auth status`, `gh auth login`) and so do not help on a `gh`-less machine.
## Safety
- Read the token in memory; never print, log, or persist it.
- Report only non-identifying facts: length and token class
(`gh?_` / `github_pat_` / 40-hex), plus the login from `GET /user`.
- After the run, sweep the working tree for token-shaped strings and confirm `0`:
```powershell
Get-ChildItem . -Recurse -File | Where-Object { $_.FullName -notmatch "\\\.git\\" } |
Select-String -Pattern "\bgh[pousr]_[A-Za-z0-9]{36}\b"
```
- If the credential carries `repo` scope it is a **full-write admin credential**.
That is normal for a dev box, but say so plainly when you use it.
## Passed verification (why this skill is trusted)
Applied to `JZKK720/cubecloud-skills-bundle-kit`: `PATCH` → **200**; after cache
expiry the live read-back showed the new description and homepage, and `updated_at`
had advanced. Negative control: the unauthenticated PATCH returned **401**, so the
success above is attributable to the stored credential and not to public write
access.