OverviewFilesCommitsIssuesDocsReleasesPeers

pgh commits / d2845b7

0.1.132: stop `pgh repo <junk>` from creating repos
`pgh repo <name>` falls through to repo-info, which LOOKS like a read but is not: repo-info resolves through getForRepo(), which mints a per-repo tracker on demand, and RefReconcilerManager.ensureFor() runs `git init --bare` when the path is missing. So reading a repo that does not exist CREATES it. 16 empty bare repos on the live fleet were produced this way — confirmed present on BOTH backends, 124-136K each, zero refs, zero commits: flags --help subcommand words list, create, new, help, comment sentinel undefined orgs/projects 7rnx, hyper-fleeky-dev, atproto-federation, favela leftovers pushtest3, sync-test-repo, pear-git-v2, wiredove Every one is a name someone typed or a variable that stringified, and each silently became a repo the forge then served, replicated and listed. The CLI now refuses to forward three classes to repo-info: - anything starting with `-` (an unknown option is not a repo called --help) - stringified sentinels: undefined, null, nan, true, false, [object Object] - names outside git-safe punctuation, matching pear-git-v3's repo-name.js Real names still pass through unchanged; verified against both, including `../etc` and `bad name` rejected while `hyper-svc` forwards. This is the CLI half. The daemon should not create on read either — repo-info materializing a tracker and a bare repo for an arbitrary name is the actual defect, and it is reachable by any caller, not just this CLI. That fix belongs in handlers/repos.js, which the concurrent agent owns, so it is flagged rather than made here. Nothing was deleted. The 16 existing repos are left alone: several are someone's to judge, and one (pear-git-v2) is NOT empty on stortron — it holds 71M and a master ref, so it is a sync gap rather than junk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
d2845b7361f2b8a4e9a98ea39bfc921ef17d94c1
by plankatron <fleeky@gmail.com> · 2026-07-30T21:16:51.000Z

Changed files (2) +33 -2

lib/commands/repo.js +32 -1
……@@ -37,6 +37,14 @@ export const usage = `pgh repo — browse repos served by the daemon
3737 a fresh corestore + P2P doorbell
3838 auto-admission (no url)`
3939
40// A repo name must start alphanumeric and stay within git-safe punctuation —
41// same shape pear-git-v3 enforces in lib/repo-name.js.
42const VALID_REPO_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/
43
44// Values that mean "a variable was stringified", never a repo someone meant.
45// `undefined` is not hypothetical: there is one on the live fleet.
46const SENTINELS = new Set(['undefined', 'null', 'nan', 'false', 'true', '[object object]'])
47
4048export async function run (client, args) {
4149 const sub = args[0]
4250
……@@ -233,6 +241,29 @@ export async function run (client, args) {
233241 return client.call('repo-revoke-writer', { name, userId })
234242 }
235243
236 // Default: treat first arg as repo name → show
244 // Default: treat the first arg as a repo NAME → show it.
245 //
246 // GUARDED, because this path has a side effect it does not look like it has:
247 // repo-info resolves through getForRepo(), which mints a per-repo tracker on
248 // demand, and RefReconcilerManager.ensureFor() runs `git init --bare` when
249 // the path is missing. So a READ materializes a repo. Typing
250 // `pgh repo --help` created a bare repo called `--help`; 16 empty repos on
251 // the live fleet were produced this way, named after flags (`--help`),
252 // subcommand words (`list`, `create`, `new`, `help`, `comment`), orgs and
253 // projects that were never repos, and one literal `undefined`.
254 //
255 // The daemon should not create on read either — that is the deeper fix, in
256 // repo-info — but the CLI has no business forwarding these in the first place.
257 if (sub.startsWith('-')) {
258 // A flag reaching here means an unknown option, not a repo called "--help".
259 throw new Error(usage)
260 }
261 if (SENTINELS.has(sub.toLowerCase())) {
262 throw new Error(`refusing to treat "${sub}" as a repo name — it looks like a ` +
263 'stringified value, not something you meant to type.\n\n' + usage)
264 }
265 if (!VALID_REPO_NAME.test(sub)) {
266 throw new Error(`invalid repo name: ${JSON.stringify(sub)}\n\n` + usage)
267 }
237268 return client.call('repo-info', { name: sub })
238269}
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "pgh",
3 "version": "0.1.131",
3 "version": "0.1.132",
44 "description": "pgh — P2P content-addressed package manager and app launcher",
55 "type": "module",
66 "main": "index.js",