Operator runs 'shithubd storage repair-shared-perms' as root over
SSH; the bare repos are owned by shithub:shithub. git 2.35+'s
dubious-ownership protection early-exits with the misleading
'fatal: not in a git directory' before it reads core.sharedRepository.
Same env trick the SSH dispatcher uses (cmd/shithubd/ssh_dispatch
injects GIT_CONFIG_COUNT=1 + safe.directory=* for the cross-user
git-receive-pack/upload-pack invocations). The path is verified
contained-in-root above the env injection, so '*' here is safe by
construction — every iteration of this loop targets a path we
already validated.
scripts/lint-unused.sh fails the build when any non-test Go file
under internal/ or cmd/ carries 'var _ = symbol' (the dead-code
shim shape). Allows the legitimate 'var _ Type = (*X)(nil)'
interface-assertion pattern by anchoring the regex to '= [A-Za-z]'
without a type name in between.
Wired into:
- Makefile 'lint-unused' target + the 'ci' alias
- .github/workflows/ci.yml as a dedicated step (the rest of the
bash lints are still local-only; lint-unused gets first-class CI
treatment because the audit caught regrowth twice in 3 days).
Pre-fix: 8 sites carried 'var _ = symbol' shims with comments like
'silence unused-import warnings during refactors'. These were lying:
in every case the import was already used elsewhere in the file or
the symbol was unreachable. Audit 2026-05-08 flagged 3 occurrences;
audit 2026-05-10 found 8 — the pattern grew because nothing failed
CI on it.
Sites cleaned:
- internal/auth/totp/recovery.go (base32.StdEncoding)
- internal/web/middleware/pat.go (pgx.ErrNoRows)
- internal/web/handlers/orgs/teams.go (pgx.ErrNoRows + errors.New)
- internal/web/handlers/profile/profile.go (context.Background)
- internal/web/handlers/api/api.go (context.Background)
- internal/web/handlers/repo/code.go (pgtype.Int8{})
- internal/web/handlers/repo/redirect.go (usersdb.New)
Build still passes after each shim drop because the imports are
genuinely live; the shims were cargo-cult.
Pre-fix: storage.RepoFS.InitBare ran 'git init --bare' without
--shared=group, so objects/ wound up 0755 with no group-write.
shithubd-web (runs as 'shithub' user) created repos; SSH-git
dispatched git-receive-pack as the 'git' user (in the 'shithub'
group). 'git' had read-execute on objects/ but not write, so push
failed with 'unable to create temporary object directory'.
git-upload-pack worked because read was sufficient.
Fix at the source:
- InitBare now runs 'git init --bare --shared=group --initial-branch=trunk'.
Persists core.sharedRepository=group in config; produces 2775
dirs (group write + setgid) and 0664 files. Parent dir gets 2750
so the setgid propagates from byte zero.
- CloneBareShared (fork path) prepends '-c core.sharedRepository=group'
so the cloned repo carries the contract. NB: 'git clone --shared'
alone is the alternates flag, NOT the perms flag — same word, two
meanings.
- RepairSharedPerms backfills existing repos: sets the config flag,
walks the tree, chmods g+w on files and g+w+s on dirs. Idempotent.
- 'shithubd storage repair-shared-perms' subcommand walks every
<prefix>/<owner>/<name>.git under storage.repos_root and applies
the repair. One-time use after deploying this binary on shithub-
prod (the live droplet has 1 repo created pre-fix that needs it).
Tests:
- TestInitBare_SharedGroupContract: asserts core.sharedRepository
config value + group-write bit on objects/.
- TestRepairSharedPerms_FixesPreFixRepo: builds a deliberately
pre-fix repo, calls Repair, asserts post-conditions match the
contract InitBare produces from byte zero.
Closes nothing yet — operator runs the new subcommand on the
droplet after deploy. Audit script (deploy/audit/check-droplet-
drift.sh) will pick up the binary swap and reflect drift if not.