{"record":{"id":"c38f89aca45e0bbd","repo":"can1357/oh-my-pi","slug":"not-a-git-repository","errorCode":null,"errorMessage":"Not a git repository","messagePattern":"Not a git repository","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/cli/git-tui/state.ts","lineNumber":336,"sourceCode":"\tget clean(): boolean {\n\t\treturn this.unstaged.length === 0 && this.staged.length === 0;\n\t}\n\n\t/** Re-read fast repository state; expensive numstats load separately. */\n\tasync refresh(): Promise<boolean> {\n\t\tif (this.pinnedSha) {\n\t\t\tif (this.#fingerprint === this.pinnedSha) return false;\n\t\t\tthis.#fingerprint = this.pinnedSha;\n\t\t\tthis.#headFilesLoad = null;\n\t\t\tthis.headCommit = await this.#loadHeadMetadata(this.pinnedSha);\n\t\t\treturn true;\n\t\t}\n\t\tconst [statusText, branchName, headSha] = await Promise.all([\n\t\t\tthis.#repo.statusPorcelain({ nulTerminated: true, untracked: \"all\" }).catch(() => null),\n\t\t\tthis.#repo.currentBranch(),\n\t\t\tthis.#repo.headSha(),\n\t\t]);\n\t\tif (statusText === null) throw new Error(\"Not a git repository\");\n\t\tconst fingerprint = `${headSha ?? \"\"}\\u0000${statusText}`;\n\t\tif (fingerprint === this.#fingerprint) {\n\t\t\tthis.branch = branchName ?? null;\n\t\t\treturn false;\n\t\t}\n\t\tthis.#fingerprint = fingerprint;\n\t\tthis.#statusStatsLoad = null;\n\t\tthis.branch = branchName ?? null;\n\t\tthis.#setChanges(statusText);\n\t\tif ((headSha ?? null) !== this.headCommit?.sha) {\n\t\t\tthis.#headFilesLoad = null;\n\t\t\tthis.headCommit = headSha ? await this.#loadHeadMetadata(headSha) : null;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/** Populate changed-line counts after the file list is already interactive. */\n\tasync loadChangeStats(): Promise<boolean> {","sourceCodeStart":318,"sourceCodeEnd":354,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/cli/git-tui/state.ts#L318-L354","documentation":"GitTuiState.refresh polls repository status via statusPorcelain; if that call fails (caught to null) it treats the directory as no longer being a valid git repository and throws. This guards the state machine against operating on a repo that disappeared mid-session (e.g. .git removed) — the TUI data can no longer be trusted.","triggerScenarios":"Calling refresh (directly or via create/loadStagedContents) on a state whose repo's statusPorcelain fails — .git directory deleted while the TUI is open, repo corrupted, or git binary failing.","commonSituations":"Deleting or moving the .git directory while the git TUI is open, checking out into a broken worktree, or disk/filesystem errors making git status fail.","solutions":["Close and reopen the git TUI from a valid repository.","Restore the .git directory (undo deletion) or re-clone the repository.","Run `git status` manually to confirm the repository is healthy."],"exampleFix":"// before\nstate.refresh(); // throws if repo vanished\n// after\ntry { await state.refresh(); } catch { closeGitTui(); }","handlingStrategy":"try-catch","validationCode":"import { $ } from \"bun\";\nconst ok = await $`git -C ${repoRoot} status --porcelain`.quiet().nothrow();\nif (ok.exitCode !== 0) teardownGitTui(); // repo is gone/corrupt","typeGuard":null,"tryCatchPattern":"try {\n  await state.refresh();\n} catch (e) {\n  if (e instanceof Error && e.message === \"Not a git repository\") {\n    ui.hide(gitOverlay); // repo vanished mid-session\n    return;\n  }\n  throw e;\n}","preventionTips":["Don't delete/move .git while the git TUI is open.","Wrap periodic refresh calls so failures close the overlay gracefully.","Verify repository health with `git status` before long-running TUI sessions."],"tags":["git","state","repository-integrity"],"backgroundTag":"not-a-git-repository","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}