Repository navigation
Conversation
Kickstart answered "Internal server error" for every refusal GitHub could give it, which is the least useful answer possible for the first flow a new installation runs: the operator cannot tell whether the repository, the installation, or Facility is at fault. Two causes, one symptom. Octokit reports HTTP failures on `status`, while the API error handler reads `statusCode`, so a 404, 403 or 409 from GitHub fell through to the generic 500 branch. And kickstart's own refusals were bare `Error`s, including "already contains the Facility 0.12 kickstart files" — a conflict the caller can act on, delivered as a server fault. Map both. An unreadable base ref, an empty repository, a refused or throttled installation, a missing or suspended connection, and an already-kickstarted repository now answer 4xx with a stable code and a message naming the repository, the ref and the remedy. Statuses with no advice attached stay unmapped, so genuinely unknown failures are still logged and masked.
adrian-lorenzo
left a comment
There was a problem hiding this comment.
Thanks for tackling this.
Please distinguish 403 rate limits from permission failures using the existing helper. Also avoid treating every 409 as an empty repository—a later ref conflict gets that message too. Add regression coverage for both.
…m an empty repo Two statuses were carrying two conditions each, so the message named the wrong remedy half the time. GitHub answers 403 both when an installation may not do something and when it has spent its budget. Calling every 403 a permission fault sends an operator to the App settings while GitHub is only asking them to wait. `githubRateLimitRetryAt` already makes that decision from the headers for the mirror and trigger paths, so kickstart now asks it first and reports the reset instant instead of advice about permissions. It answers for every 429 as well, so that branch is gone. GitHub also answers 409 for a repository with no commits and for a ref that stopped being a fast-forward. Only the first is fixed by pushing. `applyKickstart` now separates resolving the base commit from writing the refs, and the segment that failed picks the message: a 409 while resolving the base is an empty repository, a 409 once the branch is being written is another writer having moved it. The failure mapper is no longer wrapped around `readRepoFiles`, which swallows every per-path failure and cannot surface one. It is wrapped around installation-token minting instead, which can. Three regressions, all red without the change: a 403 carrying `x-ratelimit-remaining: 0` reports the reset rather than a permission fault, a plain 403 still reports the permission fault, and a non fast-forward ref update reports a moving branch rather than a repository without commits.
|
Addressed at eee45b1. Both were one status carrying two conditions, and my message named the wrong remedy half the time. Kickstart now asks |
Closes #211.
The report, and what actually happens
#211 reaches kickstart with working GitHub authentication, selects a repository, and gets
Internal server error. There is nothing else to go on — not whether the repository, the installation, or Facility is at fault. That is the first flow a new installation runs, so it is the worst possible place for an opaque answer.There are two causes behind the one symptom.
1. Octokit reports
status; the error handler readsstatusCode.@octokit/request-errorsetsstatus, notstatusCode. So every refusal GitHub can give — 404, 403, 409, 429 — falls into the 500 branch and is masked. The codebase already knows this in one place;kickstartReporeads(error as { status?: number }).statuswhen handling a 422 fromcreateBranch, and nowhere else.For the reported case the failing call is
getDefaultBranchSha()→repos.getBranch, which answers 404 on a repository with no commits.facility-testreads like a freshly created repository, which fits:readRepoFilesswallows everygetContentfailure, so preview renders happily, and the first call that actually needs a base commit is the one that dies.2. Kickstart's own refusals were bare
Errors, which have nostatusCodeat all — including"Repository already contains the Facility 0.12 kickstart files". That is a conflict the caller can act on, delivered as a server fault.The change
Map both, in the one boundary that owns the GitHub call.
kickstart_repository_unreachablekickstart_repository_emptykickstart_repository_forbiddenkickstart_github_rate_limitedgithub_installation_missinggithub_installation_unavailablekickstart_already_appliedEach message names the repository, the ref, and the remedy. Statuses with no advice attached are passed through untouched, so a genuine upstream fault is still logged and masked by
sendError— the fix narrows what gets a 500, it does not stop masking.Test
New
services/api/test/kickstart-failures.test.ts, six cases against a fake Octokit that throwsstatus-carrying errors the way@octokit/request-errordoes — no daemon, no network, runs in the default suite. Five fail on unmodifiedmain; the sixth is the regression guard that an unmapped status stays masked, and it passes both ways by design.Found but not fixed
readRepoFilesswallows everygetContenterror, so kickstart preview succeeds on a repository that apply cannot use. #211's reporter gets all the way through "preview the assets" before the failure appears. Making preview resolve the base ref would surface the same diagnosable error one step earlier, which reads like the right behaviour and matches #280's "invalid contracts fail before any partial setup command runs". I left it out to keep this to one change; happy to add it here or open it separately, whichever you prefer.Distinguishing "empty repository" from "branch does not exist" would need one extra
repos.getcall on the error path. I did not add it — the 404 message names both possibilities rather than inventing a diagnosis.Verification
Windows 11, Node 26, compose Postgres.
pnpm --filter @facility/api typecheck— cleanpnpm lint— cleanvitest run test/kickstart-failures.test.ts test/github-client.test.ts— 8 passedservices/apisuite: 261 passed, 13 failed — the same 13 that fail on unmodifiedmainin this environment (the POSIX-shell fakes inworkspace-vercel-bootstrap,agent-engines,facility-012.e2e,project-environment,turn-dispatcher, i.e. the PreToolUse hooks are POSIX-path-only: .env and migration protection silently do nothing on Windows #227/test(cli): suite is POSIX-only — gh stubs never engage on Windows, so eight tests call the real GitHub API #241 Windows family). Verified by stashing the change and re-running.I could not reproduce against a live GitHub App, so the status-to-condition mapping is from the REST documentation and Octokit's error shape rather than from observed responses. The fake reproduces the shape; a real empty-repository run would confirm the 404 path end to end.