fix(vcs): treat bare repositories as valid worktree sources - #307

Merged
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors
Aug 4, 2026
Merged

fix(vcs): treat bare repositories as valid worktree sources#307
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors

Conversation

@patroza

@patrozapatroza commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Problem

Every T3 turn against /var/lib/t3/src/t3code on t3vm failed with:

Could not start T3 turn: dispatch failed: Git command failed in
GitWorkflowService.fetchRemote (/var/lib/t3/src/t3code):
Failed to resolve the VCS driver for this Git command.

The repository had core.bare = true. GitVcsDriver.detectRepository gated on
rev-parse --is-inside-work-tree, which conflates "is a Git repository" with
"has a checkout". A bare repository therefore detected as no repository at all, so
VcsDriverRegistry.resolve raised VcsUnsupportedOperationError and every Git route
failed — including the two a thread actually needs, fetchRemote and createWorktree.

Nothing was logged server-side: the error reached Discord but produced zero
t3code-server journal lines, so the host looked healthy.

Why bare should work

Starting a thread never needs the source repository to have a checkout — the thread gets
its own worktree. git worktree add, git fetch, and ref plumbing all work fine
against a bare repository. Only operations that touch a checkout need one.

The lower layer already agreed: resolveRepositoryPaths tolerates a failing
--show-toplevel and models worktreeRoot: null. Only the detection gate rejected bare.

Change

  • Detect bare repositories instead of rejecting them; carry bare on
    VcsRepositoryIdentity. rootPath is the metadata directory when bare.
  • One probe. Detection now runs a single combined
    rev-parse --is-bare-repository --is-inside-work-tree, which separates no repository /
    bare / checkout in one call. Negative detection stays at one git invocation, so
    there is no added load on the status-poll path.
  • Bare-safe routes opt in with allowBare: true: createWorktree, fetchRemote,
    resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when it
    is not also switching.
  • Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
    thread preparation) keep the default and now fail with the real reason — the repository
    has no working tree — instead of a generic routing error.
  • Status polling reports a bare repository as having no workspace rather than erroring
    on every poll.

Tests

  • GitVcsDriver.test.ts — bare repo detects as bare: true; a repo whose config marks an
    existing checkout bare (the exact shape that broke t3vm) detects instead of returning
    null; non-repositories still return null; and a usable worktree is created from a bare
    repository
    , with the resulting checkout detecting as bare: false.
  • GitWorkflowService.test.tscreateWorktree and fetchRemote run against a bare
    handle; switchRef is refused before reaching the driver with an actionable message;
    localStatus degrades to "no workspace".
  • VcsDriverRegistry.test.ts — updated to the new probe, asserting one probe per detect.
  • Contract harness asserts bare: false for ordinary repositories.

Note

The immediate outage was already mitigated on t3vm by git config core.bare false — that
repository has a full checkout and 15 linked worktrees, so its config flag was simply wrong.
This change is about the routing behavior: a repository without a checkout should not take
down thread creation, and when a checkout genuinely is required the error should say so.

🤖 Generated with Claude Code


Also in this PR: unblocking a red fork/changes

fork/changes was red when this branch was cut (Fork CI failing on Check and Test),
so the ship gate could not pass on the VCS fix alone. These commits fix that breakage. They
are unrelated to the VCS change and are separated per commit for review:

  • style: format files drifted on fork/changes — whitespace only, in three files this
    branch does not otherwise touch (ThreadComposer.tsx, ProviderCommandReactor.ts,
    serverRuntimeStartup.ts). This is what CI's Check job was failing on.
  • fix(desktop): add the missing removeCommandLineSwitch test stubElectronApp
    gained removeCommandLineSwitch, but the DesktopUpdates test mock was never updated, so
    @t3tools/desktop failed typecheck.
  • refactor(server): use Effect FileSystem and Schema for the runtime descriptor
    serverRuntimeStartup.ts wrote and cleaned up the runtime descriptor with
    node:fs/promises, node:path, and hand-rolled JSON, which the Effect diagnostics reject
    (nodeBuiltinImport, preferSchemaOverJson). It now uses Effect FileSystem/Path and
    the existingServerRuntimeDescriptor schema from @t3tools/shared/serverRuntime
    the same schema the desktop client already decodes the file with, so the format now comes
    from one definition instead of two hand-written shapes. mode: 0o600 and the best-effort
    cleanup semantics are preserved, and a test pins the on-disk format against the consumer's
    decode path.

Known flake (not fixed here)

apps/desktop/src/app/DesktopPreReadyPlatform.test.ts > acquires a synchronous pre-ready layer before an asynchronous Clerk-shaped layer fails intermittently. It failed in one local
full-suite run, then passed on re-run, in isolation (3×), and in the full desktop suite
(456 tests) — and CI hit the same test on fork/changes this morning. It is pre-existing and
timing-sensitive; left alone rather than papered over.

A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
…scriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
@patroza
patroza marked this pull request as ready for review August 4, 2026 11:02
@patroza
patroza merged this pull request into fork/changesAug 4, 2026
11 checks passed
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for freeto 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

@patroza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(vcs): treat bare repositories as valid worktree sources - #307

Merged
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors
Aug 4, 2026
Merged

fix(vcs): treat bare repositories as valid worktree sources#307
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors

Conversation

@patroza

@patrozapatroza commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Problem

Every T3 turn against /var/lib/t3/src/t3code on t3vm failed with:

Could not start T3 turn: dispatch failed: Git command failed in
GitWorkflowService.fetchRemote (/var/lib/t3/src/t3code):
Failed to resolve the VCS driver for this Git command.

The repository had core.bare = true. GitVcsDriver.detectRepository gated on
rev-parse --is-inside-work-tree, which conflates "is a Git repository" with
"has a checkout". A bare repository therefore detected as no repository at all, so
VcsDriverRegistry.resolve raised VcsUnsupportedOperationError and every Git route
failed — including the two a thread actually needs, fetchRemote and createWorktree.

Nothing was logged server-side: the error reached Discord but produced zero
t3code-server journal lines, so the host looked healthy.

Why bare should work

Starting a thread never needs the source repository to have a checkout — the thread gets
its own worktree. git worktree add, git fetch, and ref plumbing all work fine
against a bare repository. Only operations that touch a checkout need one.

The lower layer already agreed: resolveRepositoryPaths tolerates a failing
--show-toplevel and models worktreeRoot: null. Only the detection gate rejected bare.

Change

  • Detect bare repositories instead of rejecting them; carry bare on
    VcsRepositoryIdentity. rootPath is the metadata directory when bare.
  • One probe. Detection now runs a single combined
    rev-parse --is-bare-repository --is-inside-work-tree, which separates no repository /
    bare / checkout in one call. Negative detection stays at one git invocation, so
    there is no added load on the status-poll path.
  • Bare-safe routes opt in with allowBare: true: createWorktree, fetchRemote,
    resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when it
    is not also switching.
  • Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
    thread preparation) keep the default and now fail with the real reason — the repository
    has no working tree — instead of a generic routing error.
  • Status polling reports a bare repository as having no workspace rather than erroring
    on every poll.

Tests

  • GitVcsDriver.test.ts — bare repo detects as bare: true; a repo whose config marks an
    existing checkout bare (the exact shape that broke t3vm) detects instead of returning
    null; non-repositories still return null; and a usable worktree is created from a bare
    repository
    , with the resulting checkout detecting as bare: false.
  • GitWorkflowService.test.tscreateWorktree and fetchRemote run against a bare
    handle; switchRef is refused before reaching the driver with an actionable message;
    localStatus degrades to "no workspace".
  • VcsDriverRegistry.test.ts — updated to the new probe, asserting one probe per detect.
  • Contract harness asserts bare: false for ordinary repositories.

Note

The immediate outage was already mitigated on t3vm by git config core.bare false — that
repository has a full checkout and 15 linked worktrees, so its config flag was simply wrong.
This change is about the routing behavior: a repository without a checkout should not take
down thread creation, and when a checkout genuinely is required the error should say so.

🤖 Generated with Claude Code


Also in this PR: unblocking a red fork/changes

fork/changes was red when this branch was cut (Fork CI failing on Check and Test),
so the ship gate could not pass on the VCS fix alone. These commits fix that breakage. They
are unrelated to the VCS change and are separated per commit for review:

  • style: format files drifted on fork/changes — whitespace only, in three files this
    branch does not otherwise touch (ThreadComposer.tsx, ProviderCommandReactor.ts,
    serverRuntimeStartup.ts). This is what CI's Check job was failing on.
  • fix(desktop): add the missing removeCommandLineSwitch test stubElectronApp
    gained removeCommandLineSwitch, but the DesktopUpdates test mock was never updated, so
    @t3tools/desktop failed typecheck.
  • refactor(server): use Effect FileSystem and Schema for the runtime descriptor
    serverRuntimeStartup.ts wrote and cleaned up the runtime descriptor with
    node:fs/promises, node:path, and hand-rolled JSON, which the Effect diagnostics reject
    (nodeBuiltinImport, preferSchemaOverJson). It now uses Effect FileSystem/Path and
    the existingServerRuntimeDescriptor schema from @t3tools/shared/serverRuntime
    the same schema the desktop client already decodes the file with, so the format now comes
    from one definition instead of two hand-written shapes. mode: 0o600 and the best-effort
    cleanup semantics are preserved, and a test pins the on-disk format against the consumer's
    decode path.

Known flake (not fixed here)

apps/desktop/src/app/DesktopPreReadyPlatform.test.ts > acquires a synchronous pre-ready layer before an asynchronous Clerk-shaped layer fails intermittently. It failed in one local
full-suite run, then passed on re-run, in isolation (3×), and in the full desktop suite
(456 tests) — and CI hit the same test on fork/changes this morning. It is pre-existing and
timing-sensitive; left alone rather than papered over.

A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
…scriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
@patroza
patroza marked this pull request as ready for review August 4, 2026 11:02
@patroza
patroza merged this pull request into fork/changesAug 4, 2026
11 checks passed
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for freeto 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

@patroza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(vcs): treat bare repositories as valid worktree sources - #307

Merged
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors
Aug 4, 2026
Merged

fix(vcs): treat bare repositories as valid worktree sources#307
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors

Conversation

@patroza

@patrozapatroza commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Problem

Every T3 turn against /var/lib/t3/src/t3code on t3vm failed with:

Could not start T3 turn: dispatch failed: Git command failed in
GitWorkflowService.fetchRemote (/var/lib/t3/src/t3code):
Failed to resolve the VCS driver for this Git command.

The repository had core.bare = true. GitVcsDriver.detectRepository gated on
rev-parse --is-inside-work-tree, which conflates "is a Git repository" with
"has a checkout". A bare repository therefore detected as no repository at all, so
VcsDriverRegistry.resolve raised VcsUnsupportedOperationError and every Git route
failed — including the two a thread actually needs, fetchRemote and createWorktree.

Nothing was logged server-side: the error reached Discord but produced zero
t3code-server journal lines, so the host looked healthy.

Why bare should work

Starting a thread never needs the source repository to have a checkout — the thread gets
its own worktree. git worktree add, git fetch, and ref plumbing all work fine
against a bare repository. Only operations that touch a checkout need one.

The lower layer already agreed: resolveRepositoryPaths tolerates a failing
--show-toplevel and models worktreeRoot: null. Only the detection gate rejected bare.

Change

  • Detect bare repositories instead of rejecting them; carry bare on
    VcsRepositoryIdentity. rootPath is the metadata directory when bare.
  • One probe. Detection now runs a single combined
    rev-parse --is-bare-repository --is-inside-work-tree, which separates no repository /
    bare / checkout in one call. Negative detection stays at one git invocation, so
    there is no added load on the status-poll path.
  • Bare-safe routes opt in with allowBare: true: createWorktree, fetchRemote,
    resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when it
    is not also switching.
  • Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
    thread preparation) keep the default and now fail with the real reason — the repository
    has no working tree — instead of a generic routing error.
  • Status polling reports a bare repository as having no workspace rather than erroring
    on every poll.

Tests

  • GitVcsDriver.test.ts — bare repo detects as bare: true; a repo whose config marks an
    existing checkout bare (the exact shape that broke t3vm) detects instead of returning
    null; non-repositories still return null; and a usable worktree is created from a bare
    repository
    , with the resulting checkout detecting as bare: false.
  • GitWorkflowService.test.tscreateWorktree and fetchRemote run against a bare
    handle; switchRef is refused before reaching the driver with an actionable message;
    localStatus degrades to "no workspace".
  • VcsDriverRegistry.test.ts — updated to the new probe, asserting one probe per detect.
  • Contract harness asserts bare: false for ordinary repositories.

Note

The immediate outage was already mitigated on t3vm by git config core.bare false — that
repository has a full checkout and 15 linked worktrees, so its config flag was simply wrong.
This change is about the routing behavior: a repository without a checkout should not take
down thread creation, and when a checkout genuinely is required the error should say so.

🤖 Generated with Claude Code


Also in this PR: unblocking a red fork/changes

fork/changes was red when this branch was cut (Fork CI failing on Check and Test),
so the ship gate could not pass on the VCS fix alone. These commits fix that breakage. They
are unrelated to the VCS change and are separated per commit for review:

  • style: format files drifted on fork/changes — whitespace only, in three files this
    branch does not otherwise touch (ThreadComposer.tsx, ProviderCommandReactor.ts,
    serverRuntimeStartup.ts). This is what CI's Check job was failing on.
  • fix(desktop): add the missing removeCommandLineSwitch test stubElectronApp
    gained removeCommandLineSwitch, but the DesktopUpdates test mock was never updated, so
    @t3tools/desktop failed typecheck.
  • refactor(server): use Effect FileSystem and Schema for the runtime descriptor
    serverRuntimeStartup.ts wrote and cleaned up the runtime descriptor with
    node:fs/promises, node:path, and hand-rolled JSON, which the Effect diagnostics reject
    (nodeBuiltinImport, preferSchemaOverJson). It now uses Effect FileSystem/Path and
    the existingServerRuntimeDescriptor schema from @t3tools/shared/serverRuntime
    the same schema the desktop client already decodes the file with, so the format now comes
    from one definition instead of two hand-written shapes. mode: 0o600 and the best-effort
    cleanup semantics are preserved, and a test pins the on-disk format against the consumer's
    decode path.

Known flake (not fixed here)

apps/desktop/src/app/DesktopPreReadyPlatform.test.ts > acquires a synchronous pre-ready layer before an asynchronous Clerk-shaped layer fails intermittently. It failed in one local
full-suite run, then passed on re-run, in isolation (3×), and in the full desktop suite
(456 tests) — and CI hit the same test on fork/changes this morning. It is pre-existing and
timing-sensitive; left alone rather than papered over.

A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
…scriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
@patroza
patroza marked this pull request as ready for review August 4, 2026 11:02
@patroza
patroza merged this pull request into fork/changesAug 4, 2026
11 checks passed
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for freeto 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

@patroza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(vcs): treat bare repositories as valid worktree sources - #307

Merged
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors
Aug 4, 2026
Merged

fix(vcs): treat bare repositories as valid worktree sources#307
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors

Conversation

@patroza

@patrozapatroza commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Problem

Every T3 turn against /var/lib/t3/src/t3code on t3vm failed with:

Could not start T3 turn: dispatch failed: Git command failed in
GitWorkflowService.fetchRemote (/var/lib/t3/src/t3code):
Failed to resolve the VCS driver for this Git command.

The repository had core.bare = true. GitVcsDriver.detectRepository gated on
rev-parse --is-inside-work-tree, which conflates "is a Git repository" with
"has a checkout". A bare repository therefore detected as no repository at all, so
VcsDriverRegistry.resolve raised VcsUnsupportedOperationError and every Git route
failed — including the two a thread actually needs, fetchRemote and createWorktree.

Nothing was logged server-side: the error reached Discord but produced zero
t3code-server journal lines, so the host looked healthy.

Why bare should work

Starting a thread never needs the source repository to have a checkout — the thread gets
its own worktree. git worktree add, git fetch, and ref plumbing all work fine
against a bare repository. Only operations that touch a checkout need one.

The lower layer already agreed: resolveRepositoryPaths tolerates a failing
--show-toplevel and models worktreeRoot: null. Only the detection gate rejected bare.

Change

  • Detect bare repositories instead of rejecting them; carry bare on
    VcsRepositoryIdentity. rootPath is the metadata directory when bare.
  • One probe. Detection now runs a single combined
    rev-parse --is-bare-repository --is-inside-work-tree, which separates no repository /
    bare / checkout in one call. Negative detection stays at one git invocation, so
    there is no added load on the status-poll path.
  • Bare-safe routes opt in with allowBare: true: createWorktree, fetchRemote,
    resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when it
    is not also switching.
  • Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
    thread preparation) keep the default and now fail with the real reason — the repository
    has no working tree — instead of a generic routing error.
  • Status polling reports a bare repository as having no workspace rather than erroring
    on every poll.

Tests

  • GitVcsDriver.test.ts — bare repo detects as bare: true; a repo whose config marks an
    existing checkout bare (the exact shape that broke t3vm) detects instead of returning
    null; non-repositories still return null; and a usable worktree is created from a bare
    repository
    , with the resulting checkout detecting as bare: false.
  • GitWorkflowService.test.tscreateWorktree and fetchRemote run against a bare
    handle; switchRef is refused before reaching the driver with an actionable message;
    localStatus degrades to "no workspace".
  • VcsDriverRegistry.test.ts — updated to the new probe, asserting one probe per detect.
  • Contract harness asserts bare: false for ordinary repositories.

Note

The immediate outage was already mitigated on t3vm by git config core.bare false — that
repository has a full checkout and 15 linked worktrees, so its config flag was simply wrong.
This change is about the routing behavior: a repository without a checkout should not take
down thread creation, and when a checkout genuinely is required the error should say so.

🤖 Generated with Claude Code


Also in this PR: unblocking a red fork/changes

fork/changes was red when this branch was cut (Fork CI failing on Check and Test),
so the ship gate could not pass on the VCS fix alone. These commits fix that breakage. They
are unrelated to the VCS change and are separated per commit for review:

  • style: format files drifted on fork/changes — whitespace only, in three files this
    branch does not otherwise touch (ThreadComposer.tsx, ProviderCommandReactor.ts,
    serverRuntimeStartup.ts). This is what CI's Check job was failing on.
  • fix(desktop): add the missing removeCommandLineSwitch test stubElectronApp
    gained removeCommandLineSwitch, but the DesktopUpdates test mock was never updated, so
    @t3tools/desktop failed typecheck.
  • refactor(server): use Effect FileSystem and Schema for the runtime descriptor
    serverRuntimeStartup.ts wrote and cleaned up the runtime descriptor with
    node:fs/promises, node:path, and hand-rolled JSON, which the Effect diagnostics reject
    (nodeBuiltinImport, preferSchemaOverJson). It now uses Effect FileSystem/Path and
    the existingServerRuntimeDescriptor schema from @t3tools/shared/serverRuntime
    the same schema the desktop client already decodes the file with, so the format now comes
    from one definition instead of two hand-written shapes. mode: 0o600 and the best-effort
    cleanup semantics are preserved, and a test pins the on-disk format against the consumer's
    decode path.

Known flake (not fixed here)

apps/desktop/src/app/DesktopPreReadyPlatform.test.ts > acquires a synchronous pre-ready layer before an asynchronous Clerk-shaped layer fails intermittently. It failed in one local
full-suite run, then passed on re-run, in isolation (3×), and in the full desktop suite
(456 tests) — and CI hit the same test on fork/changes this morning. It is pre-existing and
timing-sensitive; left alone rather than papered over.

A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
…scriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
@patroza
patroza marked this pull request as ready for review August 4, 2026 11:02
@patroza
patroza merged this pull request into fork/changesAug 4, 2026
11 checks passed
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for freeto 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

@patroza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(vcs): treat bare repositories as valid worktree sources - #307

Merged
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors
Aug 4, 2026
Merged

fix(vcs): treat bare repositories as valid worktree sources#307
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors

Conversation

@patroza

@patrozapatroza commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Problem

Every T3 turn against /var/lib/t3/src/t3code on t3vm failed with:

Could not start T3 turn: dispatch failed: Git command failed in
GitWorkflowService.fetchRemote (/var/lib/t3/src/t3code):
Failed to resolve the VCS driver for this Git command.

The repository had core.bare = true. GitVcsDriver.detectRepository gated on
rev-parse --is-inside-work-tree, which conflates "is a Git repository" with
"has a checkout". A bare repository therefore detected as no repository at all, so
VcsDriverRegistry.resolve raised VcsUnsupportedOperationError and every Git route
failed — including the two a thread actually needs, fetchRemote and createWorktree.

Nothing was logged server-side: the error reached Discord but produced zero
t3code-server journal lines, so the host looked healthy.

Why bare should work

Starting a thread never needs the source repository to have a checkout — the thread gets
its own worktree. git worktree add, git fetch, and ref plumbing all work fine
against a bare repository. Only operations that touch a checkout need one.

The lower layer already agreed: resolveRepositoryPaths tolerates a failing
--show-toplevel and models worktreeRoot: null. Only the detection gate rejected bare.

Change

  • Detect bare repositories instead of rejecting them; carry bare on
    VcsRepositoryIdentity. rootPath is the metadata directory when bare.
  • One probe. Detection now runs a single combined
    rev-parse --is-bare-repository --is-inside-work-tree, which separates no repository /
    bare / checkout in one call. Negative detection stays at one git invocation, so
    there is no added load on the status-poll path.
  • Bare-safe routes opt in with allowBare: true: createWorktree, fetchRemote,
    resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when it
    is not also switching.
  • Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
    thread preparation) keep the default and now fail with the real reason — the repository
    has no working tree — instead of a generic routing error.
  • Status polling reports a bare repository as having no workspace rather than erroring
    on every poll.

Tests

  • GitVcsDriver.test.ts — bare repo detects as bare: true; a repo whose config marks an
    existing checkout bare (the exact shape that broke t3vm) detects instead of returning
    null; non-repositories still return null; and a usable worktree is created from a bare
    repository
    , with the resulting checkout detecting as bare: false.
  • GitWorkflowService.test.tscreateWorktree and fetchRemote run against a bare
    handle; switchRef is refused before reaching the driver with an actionable message;
    localStatus degrades to "no workspace".
  • VcsDriverRegistry.test.ts — updated to the new probe, asserting one probe per detect.
  • Contract harness asserts bare: false for ordinary repositories.

Note

The immediate outage was already mitigated on t3vm by git config core.bare false — that
repository has a full checkout and 15 linked worktrees, so its config flag was simply wrong.
This change is about the routing behavior: a repository without a checkout should not take
down thread creation, and when a checkout genuinely is required the error should say so.

🤖 Generated with Claude Code


Also in this PR: unblocking a red fork/changes

fork/changes was red when this branch was cut (Fork CI failing on Check and Test),
so the ship gate could not pass on the VCS fix alone. These commits fix that breakage. They
are unrelated to the VCS change and are separated per commit for review:

  • style: format files drifted on fork/changes — whitespace only, in three files this
    branch does not otherwise touch (ThreadComposer.tsx, ProviderCommandReactor.ts,
    serverRuntimeStartup.ts). This is what CI's Check job was failing on.
  • fix(desktop): add the missing removeCommandLineSwitch test stubElectronApp
    gained removeCommandLineSwitch, but the DesktopUpdates test mock was never updated, so
    @t3tools/desktop failed typecheck.
  • refactor(server): use Effect FileSystem and Schema for the runtime descriptor
    serverRuntimeStartup.ts wrote and cleaned up the runtime descriptor with
    node:fs/promises, node:path, and hand-rolled JSON, which the Effect diagnostics reject
    (nodeBuiltinImport, preferSchemaOverJson). It now uses Effect FileSystem/Path and
    the existingServerRuntimeDescriptor schema from @t3tools/shared/serverRuntime
    the same schema the desktop client already decodes the file with, so the format now comes
    from one definition instead of two hand-written shapes. mode: 0o600 and the best-effort
    cleanup semantics are preserved, and a test pins the on-disk format against the consumer's
    decode path.

Known flake (not fixed here)

apps/desktop/src/app/DesktopPreReadyPlatform.test.ts > acquires a synchronous pre-ready layer before an asynchronous Clerk-shaped layer fails intermittently. It failed in one local
full-suite run, then passed on re-run, in isolation (3×), and in the full desktop suite
(456 tests) — and CI hit the same test on fork/changes this morning. It is pre-existing and
timing-sensitive; left alone rather than papered over.

A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
…scriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
@patroza
patroza marked this pull request as ready for review August 4, 2026 11:02
@patroza
patroza merged this pull request into fork/changesAug 4, 2026
11 checks passed
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for freeto 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

@patroza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(vcs): treat bare repositories as valid worktree sources - #307

Merged
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors
Aug 4, 2026
Merged

fix(vcs): treat bare repositories as valid worktree sources#307
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors

Conversation

@patroza

@patrozapatroza commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Problem

Every T3 turn against /var/lib/t3/src/t3code on t3vm failed with:

Could not start T3 turn: dispatch failed: Git command failed in
GitWorkflowService.fetchRemote (/var/lib/t3/src/t3code):
Failed to resolve the VCS driver for this Git command.

The repository had core.bare = true. GitVcsDriver.detectRepository gated on
rev-parse --is-inside-work-tree, which conflates "is a Git repository" with
"has a checkout". A bare repository therefore detected as no repository at all, so
VcsDriverRegistry.resolve raised VcsUnsupportedOperationError and every Git route
failed — including the two a thread actually needs, fetchRemote and createWorktree.

Nothing was logged server-side: the error reached Discord but produced zero
t3code-server journal lines, so the host looked healthy.

Why bare should work

Starting a thread never needs the source repository to have a checkout — the thread gets
its own worktree. git worktree add, git fetch, and ref plumbing all work fine
against a bare repository. Only operations that touch a checkout need one.

The lower layer already agreed: resolveRepositoryPaths tolerates a failing
--show-toplevel and models worktreeRoot: null. Only the detection gate rejected bare.

Change

  • Detect bare repositories instead of rejecting them; carry bare on
    VcsRepositoryIdentity. rootPath is the metadata directory when bare.
  • One probe. Detection now runs a single combined
    rev-parse --is-bare-repository --is-inside-work-tree, which separates no repository /
    bare / checkout in one call. Negative detection stays at one git invocation, so
    there is no added load on the status-poll path.
  • Bare-safe routes opt in with allowBare: true: createWorktree, fetchRemote,
    resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when it
    is not also switching.
  • Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
    thread preparation) keep the default and now fail with the real reason — the repository
    has no working tree — instead of a generic routing error.
  • Status polling reports a bare repository as having no workspace rather than erroring
    on every poll.

Tests

  • GitVcsDriver.test.ts — bare repo detects as bare: true; a repo whose config marks an
    existing checkout bare (the exact shape that broke t3vm) detects instead of returning
    null; non-repositories still return null; and a usable worktree is created from a bare
    repository
    , with the resulting checkout detecting as bare: false.
  • GitWorkflowService.test.tscreateWorktree and fetchRemote run against a bare
    handle; switchRef is refused before reaching the driver with an actionable message;
    localStatus degrades to "no workspace".
  • VcsDriverRegistry.test.ts — updated to the new probe, asserting one probe per detect.
  • Contract harness asserts bare: false for ordinary repositories.

Note

The immediate outage was already mitigated on t3vm by git config core.bare false — that
repository has a full checkout and 15 linked worktrees, so its config flag was simply wrong.
This change is about the routing behavior: a repository without a checkout should not take
down thread creation, and when a checkout genuinely is required the error should say so.

🤖 Generated with Claude Code


Also in this PR: unblocking a red fork/changes

fork/changes was red when this branch was cut (Fork CI failing on Check and Test),
so the ship gate could not pass on the VCS fix alone. These commits fix that breakage. They
are unrelated to the VCS change and are separated per commit for review:

  • style: format files drifted on fork/changes — whitespace only, in three files this
    branch does not otherwise touch (ThreadComposer.tsx, ProviderCommandReactor.ts,
    serverRuntimeStartup.ts). This is what CI's Check job was failing on.
  • fix(desktop): add the missing removeCommandLineSwitch test stubElectronApp
    gained removeCommandLineSwitch, but the DesktopUpdates test mock was never updated, so
    @t3tools/desktop failed typecheck.
  • refactor(server): use Effect FileSystem and Schema for the runtime descriptor
    serverRuntimeStartup.ts wrote and cleaned up the runtime descriptor with
    node:fs/promises, node:path, and hand-rolled JSON, which the Effect diagnostics reject
    (nodeBuiltinImport, preferSchemaOverJson). It now uses Effect FileSystem/Path and
    the existingServerRuntimeDescriptor schema from @t3tools/shared/serverRuntime
    the same schema the desktop client already decodes the file with, so the format now comes
    from one definition instead of two hand-written shapes. mode: 0o600 and the best-effort
    cleanup semantics are preserved, and a test pins the on-disk format against the consumer's
    decode path.

Known flake (not fixed here)

apps/desktop/src/app/DesktopPreReadyPlatform.test.ts > acquires a synchronous pre-ready layer before an asynchronous Clerk-shaped layer fails intermittently. It failed in one local
full-suite run, then passed on re-run, in isolation (3×), and in the full desktop suite
(456 tests) — and CI hit the same test on fork/changes this morning. It is pre-existing and
timing-sensitive; left alone rather than papered over.

A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
…scriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
@patroza
patroza marked this pull request as ready for review August 4, 2026 11:02
@patroza
patroza merged this pull request into fork/changesAug 4, 2026
11 checks passed
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for freeto 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

@patroza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(vcs): treat bare repositories as valid worktree sources - #307

Merged
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors
Aug 4, 2026
Merged

fix(vcs): treat bare repositories as valid worktree sources#307
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors

Conversation

@patroza

@patrozapatroza commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Problem

Every T3 turn against /var/lib/t3/src/t3code on t3vm failed with:

Could not start T3 turn: dispatch failed: Git command failed in
GitWorkflowService.fetchRemote (/var/lib/t3/src/t3code):
Failed to resolve the VCS driver for this Git command.

The repository had core.bare = true. GitVcsDriver.detectRepository gated on
rev-parse --is-inside-work-tree, which conflates "is a Git repository" with
"has a checkout". A bare repository therefore detected as no repository at all, so
VcsDriverRegistry.resolve raised VcsUnsupportedOperationError and every Git route
failed — including the two a thread actually needs, fetchRemote and createWorktree.

Nothing was logged server-side: the error reached Discord but produced zero
t3code-server journal lines, so the host looked healthy.

Why bare should work

Starting a thread never needs the source repository to have a checkout — the thread gets
its own worktree. git worktree add, git fetch, and ref plumbing all work fine
against a bare repository. Only operations that touch a checkout need one.

The lower layer already agreed: resolveRepositoryPaths tolerates a failing
--show-toplevel and models worktreeRoot: null. Only the detection gate rejected bare.

Change

  • Detect bare repositories instead of rejecting them; carry bare on
    VcsRepositoryIdentity. rootPath is the metadata directory when bare.
  • One probe. Detection now runs a single combined
    rev-parse --is-bare-repository --is-inside-work-tree, which separates no repository /
    bare / checkout in one call. Negative detection stays at one git invocation, so
    there is no added load on the status-poll path.
  • Bare-safe routes opt in with allowBare: true: createWorktree, fetchRemote,
    resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when it
    is not also switching.
  • Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
    thread preparation) keep the default and now fail with the real reason — the repository
    has no working tree — instead of a generic routing error.
  • Status polling reports a bare repository as having no workspace rather than erroring
    on every poll.

Tests

  • GitVcsDriver.test.ts — bare repo detects as bare: true; a repo whose config marks an
    existing checkout bare (the exact shape that broke t3vm) detects instead of returning
    null; non-repositories still return null; and a usable worktree is created from a bare
    repository
    , with the resulting checkout detecting as bare: false.
  • GitWorkflowService.test.tscreateWorktree and fetchRemote run against a bare
    handle; switchRef is refused before reaching the driver with an actionable message;
    localStatus degrades to "no workspace".
  • VcsDriverRegistry.test.ts — updated to the new probe, asserting one probe per detect.
  • Contract harness asserts bare: false for ordinary repositories.

Note

The immediate outage was already mitigated on t3vm by git config core.bare false — that
repository has a full checkout and 15 linked worktrees, so its config flag was simply wrong.
This change is about the routing behavior: a repository without a checkout should not take
down thread creation, and when a checkout genuinely is required the error should say so.

🤖 Generated with Claude Code


Also in this PR: unblocking a red fork/changes

fork/changes was red when this branch was cut (Fork CI failing on Check and Test),
so the ship gate could not pass on the VCS fix alone. These commits fix that breakage. They
are unrelated to the VCS change and are separated per commit for review:

  • style: format files drifted on fork/changes — whitespace only, in three files this
    branch does not otherwise touch (ThreadComposer.tsx, ProviderCommandReactor.ts,
    serverRuntimeStartup.ts). This is what CI's Check job was failing on.
  • fix(desktop): add the missing removeCommandLineSwitch test stubElectronApp
    gained removeCommandLineSwitch, but the DesktopUpdates test mock was never updated, so
    @t3tools/desktop failed typecheck.
  • refactor(server): use Effect FileSystem and Schema for the runtime descriptor
    serverRuntimeStartup.ts wrote and cleaned up the runtime descriptor with
    node:fs/promises, node:path, and hand-rolled JSON, which the Effect diagnostics reject
    (nodeBuiltinImport, preferSchemaOverJson). It now uses Effect FileSystem/Path and
    the existingServerRuntimeDescriptor schema from @t3tools/shared/serverRuntime
    the same schema the desktop client already decodes the file with, so the format now comes
    from one definition instead of two hand-written shapes. mode: 0o600 and the best-effort
    cleanup semantics are preserved, and a test pins the on-disk format against the consumer's
    decode path.

Known flake (not fixed here)

apps/desktop/src/app/DesktopPreReadyPlatform.test.ts > acquires a synchronous pre-ready layer before an asynchronous Clerk-shaped layer fails intermittently. It failed in one local
full-suite run, then passed on re-run, in isolation (3×), and in the full desktop suite
(456 tests) — and CI hit the same test on fork/changes this morning. It is pre-existing and
timing-sensitive; left alone rather than papered over.

A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
…scriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
@patroza
patroza marked this pull request as ready for review August 4, 2026 11:02
@patroza
patroza merged this pull request into fork/changesAug 4, 2026
11 checks passed
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for freeto 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

@patroza
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(vcs): treat bare repositories as valid worktree sources - #307

Merged
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors
Aug 4, 2026
Merged

fix(vcs): treat bare repositories as valid worktree sources#307
patroza merged 6 commits into
fork/changesfrom
t3code/debug-t3vm-vcs-errors

Conversation

@patroza

@patrozapatroza commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Problem

Every T3 turn against /var/lib/t3/src/t3code on t3vm failed with:

Could not start T3 turn: dispatch failed: Git command failed in
GitWorkflowService.fetchRemote (/var/lib/t3/src/t3code):
Failed to resolve the VCS driver for this Git command.

The repository had core.bare = true. GitVcsDriver.detectRepository gated on
rev-parse --is-inside-work-tree, which conflates "is a Git repository" with
"has a checkout". A bare repository therefore detected as no repository at all, so
VcsDriverRegistry.resolve raised VcsUnsupportedOperationError and every Git route
failed — including the two a thread actually needs, fetchRemote and createWorktree.

Nothing was logged server-side: the error reached Discord but produced zero
t3code-server journal lines, so the host looked healthy.

Why bare should work

Starting a thread never needs the source repository to have a checkout — the thread gets
its own worktree. git worktree add, git fetch, and ref plumbing all work fine
against a bare repository. Only operations that touch a checkout need one.

The lower layer already agreed: resolveRepositoryPaths tolerates a failing
--show-toplevel and models worktreeRoot: null. Only the detection gate rejected bare.

Change

  • Detect bare repositories instead of rejecting them; carry bare on
    VcsRepositoryIdentity. rootPath is the metadata directory when bare.
  • One probe. Detection now runs a single combined
    rev-parse --is-bare-repository --is-inside-work-tree, which separates no repository /
    bare / checkout in one call. Negative detection stays at one git invocation, so
    there is no added load on the status-poll path.
  • Bare-safe routes opt in with allowBare: true: createWorktree, fetchRemote,
    resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when it
    is not also switching.
  • Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
    thread preparation) keep the default and now fail with the real reason — the repository
    has no working tree — instead of a generic routing error.
  • Status polling reports a bare repository as having no workspace rather than erroring
    on every poll.

Tests

  • GitVcsDriver.test.ts — bare repo detects as bare: true; a repo whose config marks an
    existing checkout bare (the exact shape that broke t3vm) detects instead of returning
    null; non-repositories still return null; and a usable worktree is created from a bare
    repository
    , with the resulting checkout detecting as bare: false.
  • GitWorkflowService.test.tscreateWorktree and fetchRemote run against a bare
    handle; switchRef is refused before reaching the driver with an actionable message;
    localStatus degrades to "no workspace".
  • VcsDriverRegistry.test.ts — updated to the new probe, asserting one probe per detect.
  • Contract harness asserts bare: false for ordinary repositories.

Note

The immediate outage was already mitigated on t3vm by git config core.bare false — that
repository has a full checkout and 15 linked worktrees, so its config flag was simply wrong.
This change is about the routing behavior: a repository without a checkout should not take
down thread creation, and when a checkout genuinely is required the error should say so.

🤖 Generated with Claude Code


Also in this PR: unblocking a red fork/changes

fork/changes was red when this branch was cut (Fork CI failing on Check and Test),
so the ship gate could not pass on the VCS fix alone. These commits fix that breakage. They
are unrelated to the VCS change and are separated per commit for review:

  • style: format files drifted on fork/changes — whitespace only, in three files this
    branch does not otherwise touch (ThreadComposer.tsx, ProviderCommandReactor.ts,
    serverRuntimeStartup.ts). This is what CI's Check job was failing on.
  • fix(desktop): add the missing removeCommandLineSwitch test stubElectronApp
    gained removeCommandLineSwitch, but the DesktopUpdates test mock was never updated, so
    @t3tools/desktop failed typecheck.
  • refactor(server): use Effect FileSystem and Schema for the runtime descriptor
    serverRuntimeStartup.ts wrote and cleaned up the runtime descriptor with
    node:fs/promises, node:path, and hand-rolled JSON, which the Effect diagnostics reject
    (nodeBuiltinImport, preferSchemaOverJson). It now uses Effect FileSystem/Path and
    the existingServerRuntimeDescriptor schema from @t3tools/shared/serverRuntime
    the same schema the desktop client already decodes the file with, so the format now comes
    from one definition instead of two hand-written shapes. mode: 0o600 and the best-effort
    cleanup semantics are preserved, and a test pins the on-disk format against the consumer's
    decode path.

Known flake (not fixed here)

apps/desktop/src/app/DesktopPreReadyPlatform.test.ts > acquires a synchronous pre-ready layer before an asynchronous Clerk-shaped layer fails intermittently. It failed in one local
full-suite run, then passed on re-run, in isolation (3×), and in the full desktop suite
(456 tests) — and CI hit the same test on fork/changes this morning. It is pre-existing and
timing-sensitive; left alone rather than papered over.

A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
…scriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
@patroza
patroza marked this pull request as ready for review August 4, 2026 11:02
@patroza
patroza merged this pull request into fork/changesAug 4, 2026
11 checks passed
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 4, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
patroza added a commit that referenced this pull request Aug 5, 2026
* fix(vcs): treat bare repositories as valid worktree sources
A repository with `core.bare=true` was reported as "not a repository" at
detection, because `detectRepository` gated on `rev-parse --is-inside-work-tree`
— which conflates "is a Git repository" with "has a checkout". Every Git route
then failed with "Failed to resolve the VCS driver for this Git command",
including `fetchRemote` and `createWorktree`, so no thread could start against
that repository.
Starting a thread does not need the source repository to have a checkout: the
thread gets its own worktree, and `git worktree add`, `git fetch`, and ref
plumbing all work against a bare repository. Only operations that touch a
checkout need one.
- Detect bare repositories instead of rejecting them, and carry `bare` on the
repository identity. Detection now uses one combined
`rev-parse --is-bare-repository --is-inside-work-tree`, which separates
"no repository" / "bare" / "checkout" in a single call, so negative detection
stays at one git invocation.
- Let the bare-safe routes through: createWorktree, fetchRemote,
resolveRemoteTrackingCommit, removeWorktree, renameBranch, and createRef when
it is not also switching.
- Checkout-dependent routes (switchRef, pullCurrentBranch, stacked actions, PR
thread preparation) now fail with the actual reason — that the repository has
no working tree — instead of a routing error.
- Status polling reports a bare repository as having no workspace rather than
erroring on every poll.
* style: format files drifted on fork/changes
Whitespace only, no behavior change. These three files were already
unformatted on fork/changes and are untouched by this branch's fix, but the
agent ship gate runs a whole-repo `vp check`, so publishing is blocked until
they are formatted.
* fix(desktop): add the missing removeCommandLineSwitch test stub
`ElectronApp` gained `removeCommandLineSwitch`, but the DesktopUpdates test
mock was not updated, so `@t3tools/desktop` failed typecheck on fork/changes
before this branch. Untouched by this branch's fix, but the agent ship gate
runs the workspace typecheck, so publishing is blocked until it compiles.
* fix(vcs): supply the required worktree path in the bare-repo test input
* refactor(server): use Effect FileSystem and Schema for the runtime descriptor
serverRuntimeStartup wrote and cleaned up the runtime descriptor with
`node:fs/promises`, `node:path`, and hand-rolled `JSON.stringify`/`JSON.parse`,
which the Effect diagnostics flag (nodeBuiltinImport, preferSchemaOverJson) and
which failed the workspace typecheck.
- Write and remove the descriptor through Effect `FileSystem` (`mode: 0o600` is
preserved) and join its path with Effect `Path`.
- Encode and decode with the existing `ServerRuntimeDescriptor` schema from
`@t3tools/shared/serverRuntime` rather than untyped JSON. That schema was
already the one the desktop client decodes with, so the file now round-trips
through a single definition instead of two hand-written shapes.
- Keep prior failure semantics: a descriptor that cannot be written is a defect
(as it was under `Effect.promise`), and cleanup stays best-effort so a
missing, unreadable, or malformed file cannot fail shutdown.
- Pin the on-disk format with a test that decodes the written contents the same
way `DesktopExistingBackend` does, since that shape is a cross-process
contract.
* test(server): decode the descriptor via the schema JSON codec
---------
Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for freeto 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

@patroza