authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-10-05 15:34:37-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-05 15:34:37-07:00
log2afe87ea4bb94f4dbbd1660a556cbffefd7498a2
tree86e3f4e85d1a4b16f50c39d600f07643e638033b
parentcbb731beb36db2330c9a2632b11354315658ea21
signature Signed by PGP key B5690EEEBB952194

feat: new branches use the shorter t3/ prefix (#16220)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

9 files changed, 59 insertions(+), 53 deletions(-)

apps/mobile/src/features/settings/components/BranchNamingSettings.tsx+1-1
...@@ -48,7 +48,7 @@ export function BranchNamingSettings(props: {...@@ -48,7 +48,7 @@ export function BranchNamingSettings(props: {
48 {props.mode === "static" ? (48 {props.mode === "static" ? (
49 <View className="gap-2 px-4 py-3">49 <View className="gap-2 px-4 py-3">
50 <Text className="text-sm text-foreground-muted">50 <Text className="text-sm text-foreground-muted">
51 Use t3code or t3code/ for t3code/add-search. Leave empty for no prefix.51 Use t3 or t3/ for t3/add-search. Leave empty for no prefix.
52 </Text>52 </Text>
53 <AppTextInput53 <AppTextInput
54 key={props.prefix}54 key={props.prefix}
apps/server/src/orchestration-v2/ThreadLaunchService.test.ts+18-18
...@@ -1104,7 +1104,7 @@ it.effect("names the worktree itself when the client provides no branch", () =>...@@ -1104,7 +1104,7 @@ it.effect("names the worktree itself when the client provides no branch", () =>
1104 yield* waitUntil(() => Effect.sync(() => harness.createWorktree.mock.calls.length === 1));1104 yield* waitUntil(() => Effect.sync(() => harness.createWorktree.mock.calls.length === 1));
1105 assert.match(1105 assert.match(
1106 harness.createWorktree.mock.calls[0]?.[0]?.newRefName ?? "",1106 harness.createWorktree.mock.calls[0]?.[0]?.newRefName ?? "",
1107 /^t3code\/[0-9a-f]{8}$/u,1107 /^t3\/[0-9a-f]{8}$/u,
1108 );1108 );
1109 yield* waitUntil(() =>1109 yield* waitUntil(() =>
1110 threads1110 threads
...@@ -1115,7 +1115,7 @@ it.effect("names the worktree itself when the client provides no branch", () =>...@@ -1115,7 +1115,7 @@ it.effect("names the worktree itself when the client provides no branch", () =>
1115 }),1115 }),
1116);1116);
11171117
1118it.effect("renames a temporary t3code/<hash> branch off the provisioning critical path", () =>1118it.effect("renames a temporary t3/<hash> branch off the provisioning critical path", () =>
1119 Effect.gen(function* () {1119 Effect.gen(function* () {
1120 const branchNameStarted = yield* Deferred.make<void>();1120 const branchNameStarted = yield* Deferred.make<void>();
1121 const allowBranchName = yield* Deferred.make<void>();1121 const allowBranchName = yield* Deferred.make<void>();
...@@ -1138,11 +1138,11 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica...@@ -1138,11 +1138,11 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica
1138 command: "command:launch:temp-branch",1138 command: "command:launch:temp-branch",
1139 thread: "thread:launch:temp-branch",1139 thread: "thread:launch:temp-branch",
1140 message: "Build the feature",1140 message: "Build the feature",
1141 workspace: { type: "worktree", baseRef: "main", branch: "t3code/abcd1234" },1141 workspace: { type: "worktree", baseRef: "main", branch: "t3/abcd1234" },
1142 }),1142 }),
1143 );1143 );
1144 yield* Deferred.await(branchNameStarted);1144 yield* Deferred.await(branchNameStarted);
1145 assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3code/abcd1234");1145 assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3/abcd1234");
1146 yield* waitUntil(() =>1146 yield* waitUntil(() =>
1147 threads1147 threads
1148 .getThreadProjection(launched.threadId)1148 .getThreadProjection(launched.threadId)
...@@ -1150,7 +1150,7 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica...@@ -1150,7 +1150,7 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica
1150 );1150 );
1151 assert.equal(1151 assert.equal(
1152 (yield* threads.getThreadProjection(launched.threadId)).thread.branch,1152 (yield* threads.getThreadProjection(launched.threadId)).thread.branch,
1153 "t3code/abcd1234",1153 "t3/abcd1234",
1154 );1154 );
1155 yield* Deferred.succeed(allowBranchName, undefined);1155 yield* Deferred.succeed(allowBranchName, undefined);
1156 yield* waitUntil(() =>1156 yield* waitUntil(() =>
...@@ -1160,17 +1160,17 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica...@@ -1160,17 +1160,17 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica
1160 );1160 );
1161 assert.deepEqual(harness.renameBranch.mock.calls[0]?.[0], {1161 assert.deepEqual(harness.renameBranch.mock.calls[0]?.[0], {
1162 cwd: "/repo-worktrees/temp",1162 cwd: "/repo-worktrees/temp",
1163 oldBranch: "t3code/abcd1234",1163 oldBranch: "t3/abcd1234",
1164 newBranch: "generated-branch",1164 newBranch: "generated-branch",
1165 });1165 });
1166 }).pipe(Effect.provide(harness.layer));1166 }).pipe(Effect.provide(harness.layer));
1167 }),1167 }),
1168);1168);
11691169
1170it.effect("provisions under t3code-<hash> when a plain t3code branch blocks t3code/*", () =>1170it.effect("provisions under t3-<hash> when a plain t3 branch blocks t3/*", () =>
1171 Effect.gen(function* () {1171 Effect.gen(function* () {
1172 const harness = makeHarness({1172 const harness = makeHarness({
1173 hasCommit: (input) => Effect.succeed(input.refName === "refs/heads/t3code"),1173 hasCommit: (input) => Effect.succeed(input.refName === "refs/heads/t3"),
1174 createWorktree: (input) =>1174 createWorktree: (input) =>
1175 Effect.succeed({1175 Effect.succeed({
1176 worktree: { path: "/repo-worktrees/temp", refName: input.newRefName, headSha: "abc" },1176 worktree: { path: "/repo-worktrees/temp", refName: input.newRefName, headSha: "abc" },
...@@ -1184,7 +1184,7 @@ it.effect("provisions under t3code-<hash> when a plain t3code branch blocks t3co...@@ -1184,7 +1184,7 @@ it.effect("provisions under t3code-<hash> when a plain t3code branch blocks t3co
1184 command: "command:launch:blocked-namespace",1184 command: "command:launch:blocked-namespace",
1185 thread: "thread:launch:blocked-namespace",1185 thread: "thread:launch:blocked-namespace",
1186 message: "Build the feature",1186 message: "Build the feature",
1187 workspace: { type: "worktree", baseRef: "main", branch: "t3code/abcd1234" },1187 workspace: { type: "worktree", baseRef: "main", branch: "t3/abcd1234" },
1188 }),1188 }),
1189 );1189 );
1190 yield* waitUntil(() =>1190 yield* waitUntil(() =>
...@@ -1192,8 +1192,8 @@ it.effect("provisions under t3code-<hash> when a plain t3code branch blocks t3co...@@ -1192,8 +1192,8 @@ it.effect("provisions under t3code-<hash> when a plain t3code branch blocks t3co
1192 .getThreadProjection(launched.threadId)1192 .getThreadProjection(launched.threadId)
1193 .pipe(Effect.map((projection) => projection.thread.branch === "generated-branch")),1193 .pipe(Effect.map((projection) => projection.thread.branch === "generated-branch")),
1194 );1194 );
1195 assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3code-abcd1234");1195 assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3-abcd1234");
1196 assert.equal(harness.renameBranch.mock.calls[0]?.[0]?.oldBranch, "t3code-abcd1234");1196 assert.equal(harness.renameBranch.mock.calls[0]?.[0]?.oldBranch, "t3-abcd1234");
1197 }).pipe(Effect.provide(harness.layer));1197 }).pipe(Effect.provide(harness.layer));
1198 }),1198 }),
1199);1199);
...@@ -1235,11 +1235,11 @@ it.effect("keeps the temporary branch when branch generation fails", () =>...@@ -1235,11 +1235,11 @@ it.effect("keeps the temporary branch when branch generation fails", () =>
1235 command: "command:launch:branch-fallback",1235 command: "command:launch:branch-fallback",
1236 thread: "thread:launch:branch-fallback",1236 thread: "thread:launch:branch-fallback",
1237 message: "Build the feature",1237 message: "Build the feature",
1238 workspace: { type: "worktree", baseRef: "main", branch: "t3code/abcd1234" },1238 workspace: { type: "worktree", baseRef: "main", branch: "t3/abcd1234" },
1239 }),1239 }),
1240 );1240 );
1241 yield* waitUntil(() => Effect.sync(() => harness.generateBranchName.mock.calls.length === 1));1241 yield* waitUntil(() => Effect.sync(() => harness.generateBranchName.mock.calls.length === 1));
1242 assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3code/abcd1234");1242 assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3/abcd1234");
1243 yield* waitUntil(() =>1243 yield* waitUntil(() =>
1244 threads1244 threads
1245 .getThreadProjection(launched.threadId)1245 .getThreadProjection(launched.threadId)
...@@ -1248,7 +1248,7 @@ it.effect("keeps the temporary branch when branch generation fails", () =>...@@ -1248,7 +1248,7 @@ it.effect("keeps the temporary branch when branch generation fails", () =>
1248 assert.equal(harness.renameBranch.mock.calls.length, 0);1248 assert.equal(harness.renameBranch.mock.calls.length, 0);
1249 assert.equal(1249 assert.equal(
1250 (yield* threads.getThreadProjection(launched.threadId)).thread.branch,1250 (yield* threads.getThreadProjection(launched.threadId)).thread.branch,
1251 "t3code/abcd1234",1251 "t3/abcd1234",
1252 );1252 );
1253 }).pipe(Effect.provide(harness.layer));1253 }).pipe(Effect.provide(harness.layer));
1254 }),1254 }),
...@@ -1267,8 +1267,8 @@ it.effect("renames a temporary branch on an existing worktree to a generated nam...@@ -1267,8 +1267,8 @@ it.effect("renames a temporary branch on an existing worktree to a generated nam
1267 message: "Build the feature",1267 message: "Build the feature",
1268 workspace: {1268 workspace: {
1269 type: "existing_worktree",1269 type: "existing_worktree",
1270 worktreePath: "/repo-worktrees/t3code-abcd1234",1270 worktreePath: "/repo-worktrees/t3-abcd1234",
1271 branch: "t3code/abcd1234",1271 branch: "t3/abcd1234",
1272 },1272 },
1273 }),1273 }),
1274 );1274 );
...@@ -1278,8 +1278,8 @@ it.effect("renames a temporary branch on an existing worktree to a generated nam...@@ -1278,8 +1278,8 @@ it.effect("renames a temporary branch on an existing worktree to a generated nam
1278 .pipe(Effect.map((projection) => projection.thread.branch === "generated-branch")),1278 .pipe(Effect.map((projection) => projection.thread.branch === "generated-branch")),
1279 );1279 );
1280 assert.deepEqual(harness.renameBranch.mock.calls[0]?.[0], {1280 assert.deepEqual(harness.renameBranch.mock.calls[0]?.[0], {
1281 cwd: "/repo-worktrees/t3code-abcd1234",1281 cwd: "/repo-worktrees/t3-abcd1234",
1282 oldBranch: "t3code/abcd1234",1282 oldBranch: "t3/abcd1234",
1283 newBranch: "generated-branch",1283 newBranch: "generated-branch",
1284 });1284 });
1285 }).pipe(Effect.provide(harness.layer));1285 }).pipe(Effect.provide(harness.layer));
apps/server/src/orchestration-v2/ThreadLaunchService.ts+1-1
...@@ -304,7 +304,7 @@ const make = Effect.gen(function* () {...@@ -304,7 +304,7 @@ const make = Effect.gen(function* () {
304 });304 });
305305
306 // The server owns worktree naming: without an explicit branch, provision306 // The server owns worktree naming: without an explicit branch, provision
307 // under a temporary `t3code/<hash>` name so the worktree never waits on307 // under a temporary `t3/<hash>` name so the worktree never waits on
308 // name generation, then rename in the background below.308 // name generation, then rename in the background below.
309 const requestedBranch = input.workspaceStrategy.branch;309 const requestedBranch = input.workspaceStrategy.branch;
310 let branch: string | null;310 let branch: string | null;
apps/web/src/components/settings/BranchNamingSettings.tsx+1-1
...@@ -76,7 +76,7 @@ export function BranchNamingSettings() {...@@ -76,7 +76,7 @@ export function BranchNamingSettings() {
76 serverScoped76 serverScoped
77 settingKeys={["branchNamePrefix"]}77 settingKeys={["branchNamePrefix"]}
78 title="Branch prefix"78 title="Branch prefix"
79 description="For example, t3code or t3code/ produces t3code/add-search. Leave empty for no prefix."79 description="For example, t3 or t3/ produces t3/add-search. Leave empty for no prefix."
80 resetAction={80 resetAction={
81 prefixMixed ||81 prefixMixed ||
82 settings.branchNamePrefix !== DEFAULT_SERVER_SETTINGS.branchNamePrefix ? (82 settings.branchNamePrefix !== DEFAULT_SERVER_SETTINGS.branchNamePrefix ? (
docs/user/project-settings.md+1-1
...@@ -42,7 +42,7 @@ and other phone-only settings ignore the filter....@@ -42,7 +42,7 @@ and other phone-only settings ignore the filter.
4242
43In **Settings → Source Control → Worktree branch naming**, choose a static prefix,43In **Settings → Source Control → Worktree branch naming**, choose a static prefix,
44a model-selected semantic prefix such as `feat/` or `fix/`, or custom instructions44a model-selected semantic prefix such as `feat/` or `fix/`, or custom instructions
45for the complete name. The static prefix defaults to `t3code/`; a trailing slash is45for the complete name. The static prefix defaults to `t3/`; a trailing slash is
46optional, and an empty prefix adds nothing. Invalid characters in a static prefix46optional, and an empty prefix adds nothing. Invalid characters in a static prefix
47are replaced with hyphens. Custom instructions are appended to47are replaced with hyphens. Custom instructions are appended to
48the naming prompt and can specify issue IDs, namespaces, and casing.48the naming prompt and can specify issue IDs, namespaces, and casing.
packages/contracts/src/settings.test.ts+2-2
...@@ -1085,10 +1085,10 @@ it("validates remote device hosts and rejects ambiguous host ids", () => {...@@ -1085,10 +1085,10 @@ it("validates remote device hosts and rejects ambiguous host ids", () => {
1085});1085});
10861086
1087describe("branch naming settings", () => {1087describe("branch naming settings", () => {
1088 it("defaults existing settings to the t3code static prefix", () => {1088 it("defaults existing settings to the t3 static prefix", () => {
1089 expect(decodeServerSettings({})).toMatchObject({1089 expect(decodeServerSettings({})).toMatchObject({
1090 branchNamingMode: "static",1090 branchNamingMode: "static",
1091 branchNamePrefix: "t3code",1091 branchNamePrefix: "t3",
1092 branchNameInstructions: "",1092 branchNameInstructions: "",
1093 });1093 });
1094 });1094 });
packages/contracts/src/settings.ts+1-1
...@@ -1368,7 +1368,7 @@ export const ServerSettings = Schema.Struct({...@@ -1368,7 +1368,7 @@ export const ServerSettings = Schema.Struct({
1368 branchNamingMode: BranchNamingMode.pipe(1368 branchNamingMode: BranchNamingMode.pipe(
1369 Schema.withDecodingDefault(Effect.succeed("static" as const)),1369 Schema.withDecodingDefault(Effect.succeed("static" as const)),
1370 ),1370 ),
1371 branchNamePrefix: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed("t3code"))),1371 branchNamePrefix: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed("t3"))),
1372 branchNameInstructions: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),1372 branchNameInstructions: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
1373 removeAgentCreditsOnMerge: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),1373 removeAgentCreditsOnMerge: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
1374 sourceControlWritingStyle: SourceControlWritingStyleSettings.pipe(1374 sourceControlWritingStyle: SourceControlWritingStyleSettings.pipe(
packages/shared/src/git.test.ts+17-14
...@@ -190,34 +190,37 @@ describe("isTemporaryWorktreeBranch", () => {...@@ -190,34 +190,37 @@ describe("isTemporaryWorktreeBranch", () => {
190 );190 );
191 });191 });
192192
193 it("matches legacy UUID-shaped temporary worktree refs from older mobile builds", () => {193 it("matches legacy t3code temporary worktree refs", () => {
194 expect(isTemporaryWorktreeBranch("t3code/deadbeef")).toBe(true);
195 expect(isTemporaryWorktreeBranch("t3code-deadbeef")).toBe(true);
196 expect(isTemporaryWorktreeBranch("t3code/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12")).toBe(true);
194 expect(197 expect(
195 isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12`),198 isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12`),
196 ).toBe(true);199 ).toBe(false);
197 });200 });
198201
199 it("rejects UUID-shaped refs that are not RFC 4122 v4", () => {202 it("rejects UUID-shaped refs that are not RFC 4122 v4", () => {
200 // version nibble is not 4203 // version nibble is not 4
201 expect(204 expect(isTemporaryWorktreeBranch("t3code/f4ae4e0e-f971-1d48-b4f2-9cf0aa54ab12")).toBe(false);
202 isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-1d48-b4f2-9cf0aa54ab12`),
203 ).toBe(false);
204 // variant nibble is not [89ab]205 // variant nibble is not [89ab]
205 expect(206 expect(isTemporaryWorktreeBranch("t3code/f4ae4e0e-f971-4d48-c4f2-9cf0aa54ab12")).toBe(false);
206 isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-c4f2-9cf0aa54ab12`),
207 ).toBe(false);
208 });207 });
209208
210 it("matches the flat fallback used when a plain t3code branch exists", () => {209 it("matches the flat fallback used when a plain t3 branch exists", () => {
211 const flat = flattenTemporaryWorktreeBranchName(`${WORKTREE_BRANCH_PREFIX}/deadbeef`);210 const flat = flattenTemporaryWorktreeBranchName(`${WORKTREE_BRANCH_PREFIX}/deadbeef`);
212 expect(flat).toBe(`${WORKTREE_BRANCH_PREFIX}-deadbeef`);211 expect(flat).toBe(`${WORKTREE_BRANCH_PREFIX}-deadbeef`);
213 expect(isTemporaryWorktreeBranch(flat)).toBe(true);212 expect(isTemporaryWorktreeBranch(flat)).toBe(true);
214 expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}-deadbeef-extra`)).toBe(false);213 expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}-deadbeef-extra`)).toBe(false);
215 expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}-feature`)).toBe(false);214 expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}-feature`)).toBe(false);
216 expect(215 expect(flattenTemporaryWorktreeBranchName("t3code/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12")).toBe(
217 flattenTemporaryWorktreeBranchName(216 `${WORKTREE_BRANCH_PREFIX}-f4ae4e0e`,
218 `${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12`,217 );
219 ),218 expect(flattenTemporaryWorktreeBranchName("t3code-deadbeef")).toBe(
220 ).toBe(`${WORKTREE_BRANCH_PREFIX}-f4ae4e0e`);219 `${WORKTREE_BRANCH_PREFIX}-deadbeef`,
220 );
221 expect(flattenTemporaryWorktreeBranchName("t3code/deadbeef")).toBe(
222 `${WORKTREE_BRANCH_PREFIX}-deadbeef`,
223 );
221 });224 });
222225
223 it("rejects non-temporary refName names", () => {226 it("rejects non-temporary refName names", () => {
packages/shared/src/git.ts+17-14
...@@ -11,14 +11,18 @@ import * as Arr from "effect/Array";...@@ -11,14 +11,18 @@ import * as Arr from "effect/Array";
11import * as Result from "effect/Result";11import * as Result from "effect/Result";
12import { detectSourceControlProviderFromRemoteUrl } from "./sourceControl.ts";12import { detectSourceControlProviderFromRemoteUrl } from "./sourceControl.ts";
1313
14export const WORKTREE_BRANCH_PREFIX = "t3code";14export const WORKTREE_BRANCH_PREFIX = "t3";
15// Canonical form is `t3code/<8 hex>`. Older mobile builds generated `t3code/<uuid>`15// Canonical form is `t3/<8 hex>`. `t3-<8 hex>` is the fallback when a plain `t3`
16// via Crypto.randomUUID() (always RFC 4122 v4), so the matcher also accepts exactly16// branch blocks the namespace. The matcher also accepts every legacy shape, so
17// that shape — version nibble `4`, variant nibble `[89ab]` — to keep those threads17// existing threads stay eligible for branch regeneration: `t3code/<8 hex>` and
18// eligible for branch regeneration without loosening beyond what was ever generated.18// `t3code-<8 hex>` from before the prefix was shortened, and `t3code/<uuid>` from
19// `t3code-<8 hex>` is the fallback when a plain `t3code` branch blocks the namespace.19// older mobile builds that used Crypto.randomUUID() (always RFC 4122 v4, so version
20// nibble `4` and variant nibble `[89ab]`). Nothing looser than what was generated.
21const TEMP_WORKTREE_HEX_TOKEN = "[0-9a-f]{8}";
22const TEMP_WORKTREE_UUID_V4_TOKEN =
23 "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
20const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(24const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(
21 `^${WORKTREE_BRANCH_PREFIX}(?:-[0-9a-f]{8}$|\\/(?:[0-9a-f]{8}|[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$)`,25 `^(?:${WORKTREE_BRANCH_PREFIX}[-/]${TEMP_WORKTREE_HEX_TOKEN}|t3code(?:[-/]${TEMP_WORKTREE_HEX_TOKEN}|\\/${TEMP_WORKTREE_UUID_V4_TOKEN}))$`,
22);26);
2327
24/**28/**
...@@ -125,15 +129,14 @@ export function buildTemporaryWorktreeBranchName(...@@ -125,15 +129,14 @@ export function buildTemporaryWorktreeBranchName(
125}129}
126130
127/**131/**
128 * Git stores refs as paths, so a plain `t3code` branch makes every `t3code/<hex>`132 * Git stores refs as paths, so a plain `t3` branch makes every `t3/<hex>`
129 * ref impossible. This moves a temporary name to the flat `t3code-<hex>` sibling.133 * ref impossible. This moves a temporary name to the flat `t3-<hex>` sibling.
130 */134 */
131export function flattenTemporaryWorktreeBranchName(refName: string): string {135export function flattenTemporaryWorktreeBranchName(refName: string): string {
132 // Keep only the canonical 8-hex token so legacy UUID names stay recognizable.136 // Keep only the canonical 8-hex token so legacy `t3code/` and UUID names map cleanly.
133 const token = refName137 const normalized = refName.trim().toLowerCase();
134 .trim()138 const tokenStart = normalized.search(/[-/]/) + 1;
135 .toLowerCase()139 const token = normalized.slice(tokenStart, tokenStart + 8);
136 .slice(WORKTREE_BRANCH_PREFIX.length + 1, WORKTREE_BRANCH_PREFIX.length + 9);
137 return `${WORKTREE_BRANCH_PREFIX}-${token}`;140 return `${WORKTREE_BRANCH_PREFIX}-${token}`;
138}141}
139142