Skip to content

refactor(server): share the no-git-repository checkout-root rewrap (#tech-debt) - #1085

Merged
selfcontained merged 1 commit into
mainfrom
agt_97def1efa46c/job-debt-collector-f0a8739c
Sep 13, 2026
Merged

selfcontained merged 1 commit into
mainfrom
agt_97def1efa46c/job-debt-collector-f0a8739c

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

What

apps/server/src/shared/git/worktree.ts and apps/server/src/shared/github/pr.ts each declared a file-private resolveRepoRoot(cwd, commandRunner). The two were identical 9-line wrappers: try { return await resolveCheckoutRoot(cwd, commandRunner) } catch { throw new <DomainError>("No git repository found for the provided working directory.", 404) }.

This PR adds one exported helper next to resolveCheckoutRoot in shared/git/git-context.ts:

resolveCheckoutRootOrThrow(cwd, commandRunner, ErrorClass)

It deletes both wrappers, plus resolveCurrentCheckoutRoot in worktree.ts. That function did nothing but call worktree.ts's resolveRepoRoot, so it only existed to forward to the deleted wrapper. All four call sites now call the helper directly:

call site error class status message
createGitWorktree GitWorktreeError 404 same string
cleanupGitWorktree GitWorktreeError 404 same string
createPr GitHubPrError 404 same string
getPrStatus GitHubPrError 404 same string

What the included sites have in common: each one runs git -C <cwd> rev-parse --show-toplevel through the injected runner. Any failure is swallowed and replaced with a new domain error that has status 404 and the message above. The sites differ only in the error class. Both classes have the same (message: string, statusCode = 400) constructor, and shared/mcp/tool-error.ts and agents/manager.ts branch on the class with instanceof, so each call site passes its own class.

Why it's tech debt

  • The same rewrap was copied in two files.
  • Both copies were file-private functions named resolveRepoRoot that returned the checkout root. That name collides with the exported git-context.ts resolveRepoRoot, which returns the common repo root (via --git-common-dir, with a timeout and fallback). worktree.ts then aliased its copy under the accurate name resolveCurrentCheckoutRoot.
  • After this PR, only the exported function is called resolveRepoRoot.

Deliberately left alone

  • git-context.ts resolveRepoRoot (exported, about 8 importers): it is a different function, and renaming it has a much wider blast radius.
  • worktree.ts resolveCommonRepoRoot: it looks like git-context.ts resolveRepoRoot, but it has no timeout and no fallback, and it throws GitWorktreeError(…, 500). Folding it in would change behavior.
  • routes/personas.ts resolveOptionalRepoRoot / resolveOptionalWorktreeRoot: these swallow the failure to null instead of rethrowing, so they have a different result type.

Shape chosen

The helper takes the error constructor, so call sites stay one-liners and the 404 stays next to the message it belongs with. The alternative was an error factory ((message) => new GitHubPrError(message, 404)), which moves the status code to each caller at the cost of a lambda per site. I also considered keeping a thin file-private wrapper per file and rejected it: that would keep 2 wrappers for what is now a 4-line call.

Validation

  • pnpm run check passes.
  • Added tests for the no-repo path in test/git-worktree.test.ts (create and cleanup) and test/github-pr.test.ts (createPr and getPrStatus). Previously none of the four sites had an assertion on this path. Each test checks the error class, message and 404.
  • Identity-preserving: the new tests pass against the original src files (HEAD's git-context/worktree/pr).
  • Non-vacuous: 8 defect probes, each reverted afterward, all turned the tests red: 404→400, changed message, plain Error instead of ErrorClass, and swapping the class to Error at each of the 4 call sites one at a time.
  • pnpm run test (server with Postgres env, web, browser-extension) and pnpm run test:e2e: all green — server 187 files passed / 1 skipped (3321 tests), web 130 files / 1924 tests, browser-extension 9 files / 60 tests; E2E 198 passed / 12 skipped (1.8m).

Next run

RunJobResult duplication between server and web: apps/server/src/jobs/service.ts:57 vs apps/web/src/hooks/use-jobs.ts:22.

🤖 Generated with Claude Code

…tech-debt)

worktree.ts and pr.ts each declared a file-private resolveRepoRoot that
wrapped resolveCheckoutRoot and rethrew the same 404 message, differing
only in the domain error class. Replace both (and worktree.ts's
pass-through resolveCurrentCheckoutRoot alias) with an exported
resolveCheckoutRootOrThrow(cwd, runner, ErrorClass) in git-context.ts,
and cover the no-repo path for all four entry points.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@selfcontained
selfcontained merged commit 6ae305b into main Sep 13, 2026
1 check passed
@selfcontained
selfcontained deleted the agt_97def1efa46c/job-debt-collector-f0a8739c branch September 13, 2026 09:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant