spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

mods/diff: one repository probe in flight, and a git that timed out is asked again, not read as no repositoryBrowse files
poteat authored on Sep 10, 2026 parent 30a6850 · commit a33d3441c1bc
3 changed files+44 -11compared with 30a6850
Amods/diff/hooks/names/texts/git-unanswered-text.ts
Mmods/diff/hooks/names/texts/index.ts
Mmods/diff/hooks/register.ts
@@ -1,0 +1,9 @@
1+/**
2+ * `/diff`'s answer when git did not say whether this is a repository.
3+ *
4+ * It timed out or could not start: the directory is unknown, not outside
5+ * one.
6+ */
7+export const GIT_UNANSWERED_TEXT =
8+ 'The diff panel couldn’t read git state — git didn’t answer; run /diff ' +
9+ 'again'
@@ -1,4 +1,5 @@
11 export * from './builtin-holds-pattern.js'
2+export * from './git-unanswered-text.js'
23 export * from './not-in-repository-text.js'
34 export * from './register-failed-text-of.js'
45 export * from './untracked-withheld-text.js'
@@ -21,17 +21,16 @@
2121 * Registers the diff pane: `/diff` once the built-in stands down, the
2222 * pane's drawing and refresh, its opening on Claude's first edit, the ask.
2323 *
24- * `session.start` binds the engine once, registers the command (refused
25- * because the built-in holds it, the plugin does nothing) and pins the
26- * backend holding the session's directory (probed again on `/diff` and
27- * first edit until found): what an installed backend probe finds, else git
28- * (backendOf).
24+ * `session.start` registers `/diff` (refused while the built-in holds it),
25+ * binds the host every pinned backend reads through, and pins the backend
26+ * (backendOf): one probe in flight, asked again on `/diff` until git answers.
2927 *
3028 * @param on the engine's registrar
3129 */
3230 export function register(on: On) {
3331 let host: Host | null = null
3432 let backend: Backend.Backend | null = null
33+ let probing: Promise<boolean> | null = null
3534 let sessionStartMs = 0
3635 let isPaneOpen = false
3736 let hasAutoOpened = false
@@ -76,19 +75,37 @@
7675 },
7776 })
7877
79- async function pinBackend(engine: Host): Promise<void> {
78+ function pinBackend(engine: Host): Promise<boolean> {
8079 if (backend) {
81- return
80+ return Promise.resolve(true)
8281 }
8382
83+ probing ??= probeBackend(engine).finally(() => {
84+ probing = null
85+ })
86+
87+ return probing
88+ }
89+
90+ async function probeBackend(engine: Host): Promise<boolean> {
91+ const asked = { isAnswered: true }
92+ const probeHost = backendHostOf(engine)
8493 const probed = await Backend.backendOf(
85- backendHostOf(engine),
94+ {
95+ ...probeHost,
96+ run: (argv, init) =>
97+ probeHost.run(argv, init).catch((error: unknown) => {
98+ asked.isAnswered &&=
99+ argv[0] !== 'git' || !/\baborted\b/.test(messageOf(error))
100+ throw error
101+ }),
102+ },
86103 Backend.INSTALLED_BACKEND_PROBES,
87104 )
88105 backend ??= probed
89106
90107 if (!probed || backend !== probed) {
91- return
108+ return asked.isAnswered || backend !== null
92109 }
93110
94111 const stored = PaneState.baseModeOf(
@@ -103,6 +120,8 @@
103120 baseModes: probed.baseModes,
104121 ...(mode && { requestedMode: mode }),
105122 }
123+
124+ return true
106125 }
107126
108127 function redraw(engine: Host) {
@@ -477,10 +496,14 @@
477496 return next(e)
478497 }
479498
480- await pinBackend(host)
499+ const isAnswered = (await pinBackend(host)) || (await pinBackend(host))
481500
482501 if (!backend) {
483- return { text: Names.NOT_IN_REPOSITORY_TEXT }
502+ return {
503+ text: isAnswered
504+ ? Names.NOT_IN_REPOSITORY_TEXT
505+ : Names.GIT_UNANSWERED_TEXT,
506+ }
484507 }
485508
486509 const isOpening =