Skip to content

Commit 2afe87e

Browse files
t3dotggclaude
andauthored
feat: new branches use the shorter t3/ prefix (#16220)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent cbb731b commit 2afe87e

9 files changed

Lines changed: 59 additions & 53 deletions

File tree

‎apps/mobile/src/features/settings/components/BranchNamingSettings.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function BranchNamingSettings(props: {
4848
{props.mode === "static" ? (
4949
<View className="gap-2 px-4 py-3">
5050
<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.
5252
</Text>
5353
<AppTextInput
5454
key={props.prefix}

‎apps/server/src/orchestration-v2/ThreadLaunchService.test.ts‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,7 @@ it.effect("names the worktree itself when the client provides no branch", () =>
11041104
yield* waitUntil(() => Effect.sync(() => harness.createWorktree.mock.calls.length === 1));
11051105
assert.match(
11061106
harness.createWorktree.mock.calls[0]?.[0]?.newRefName ?? "",
1107-
/^t3code\/[0-9a-f]{8}$/u,
1107+
/^t3\/[0-9a-f]{8}$/u,
11081108
);
11091109
yield* waitUntil(() =>
11101110
threads
@@ -1115,7 +1115,7 @@ it.effect("names the worktree itself when the client provides no branch", () =>
11151115
}),
11161116
);
11171117

1118-
it.effect("renames a temporary t3code/<hash> branch off the provisioning critical path", () =>
1118+
it.effect("renames a temporary t3/<hash> branch off the provisioning critical path", () =>
11191119
Effect.gen(function* () {
11201120
const branchNameStarted = yield* Deferred.make<void>();
11211121
const allowBranchName = yield* Deferred.make<void>();
@@ -1138,19 +1138,19 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica
11381138
command: "command:launch:temp-branch",
11391139
thread: "thread:launch:temp-branch",
11401140
message: "Build the feature",
1141-
workspace: { type: "worktree", baseRef: "main", branch: "t3code/abcd1234" },
1141+
workspace: { type: "worktree", baseRef: "main", branch: "t3/abcd1234" },
11421142
}),
11431143
);
11441144
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");
11461146
yield* waitUntil(() =>
11471147
threads
11481148
.getThreadProjection(launched.threadId)
11491149
.pipe(Effect.map((projection) => projection.runs[0]?.status === "starting")),
11501150
);
11511151
assert.equal(
11521152
(yield* threads.getThreadProjection(launched.threadId)).thread.branch,
1153-
"t3code/abcd1234",
1153+
"t3/abcd1234",
11541154
);
11551155
yield* Deferred.succeed(allowBranchName, undefined);
11561156
yield* waitUntil(() =>
@@ -1160,17 +1160,17 @@ it.effect("renames a temporary t3code/<hash> branch off the provisioning critica
11601160
);
11611161
assert.deepEqual(harness.renameBranch.mock.calls[0]?.[0], {
11621162
cwd: "/repo-worktrees/temp",
1163-
oldBranch: "t3code/abcd1234",
1163+
oldBranch: "t3/abcd1234",
11641164
newBranch: "generated-branch",
11651165
});
11661166
}).pipe(Effect.provide(harness.layer));
11671167
}),
11681168
);
11691169

1170-
it.effect("provisions under t3code-<hash> when a plain t3code branch blocks t3code/*", () =>
1170+
it.effect("provisions under t3-<hash> when a plain t3 branch blocks t3/*", () =>
11711171
Effect.gen(function* () {
11721172
const harness = makeHarness({
1173-
hasCommit: (input) => Effect.succeed(input.refName === "refs/heads/t3code"),
1173+
hasCommit: (input) => Effect.succeed(input.refName === "refs/heads/t3"),
11741174
createWorktree: (input) =>
11751175
Effect.succeed({
11761176
worktree: { path: "/repo-worktrees/temp", refName: input.newRefName, headSha: "abc" },
@@ -1184,16 +1184,16 @@ it.effect("provisions under t3code-<hash> when a plain t3code branch blocks t3co
11841184
command: "command:launch:blocked-namespace",
11851185
thread: "thread:launch:blocked-namespace",
11861186
message: "Build the feature",
1187-
workspace: { type: "worktree", baseRef: "main", branch: "t3code/abcd1234" },
1187+
workspace: { type: "worktree", baseRef: "main", branch: "t3/abcd1234" },
11881188
}),
11891189
);
11901190
yield* waitUntil(() =>
11911191
threads
11921192
.getThreadProjection(launched.threadId)
11931193
.pipe(Effect.map((projection) => projection.thread.branch === "generated-branch")),
11941194
);
1195-
assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3code-abcd1234");
1196-
assert.equal(harness.renameBranch.mock.calls[0]?.[0]?.oldBranch, "t3code-abcd1234");
1195+
assert.equal(harness.createWorktree.mock.calls[0]?.[0]?.newRefName, "t3-abcd1234");
1196+
assert.equal(harness.renameBranch.mock.calls[0]?.[0]?.oldBranch, "t3-abcd1234");
11971197
}).pipe(Effect.provide(harness.layer));
11981198
}),
11991199
);
@@ -1235,11 +1235,11 @@ it.effect("keeps the temporary branch when branch generation fails", () =>
12351235
command: "command:launch:branch-fallback",
12361236
thread: "thread:launch:branch-fallback",
12371237
message: "Build the feature",
1238-
workspace: { type: "worktree", baseRef: "main", branch: "t3code/abcd1234" },
1238+
workspace: { type: "worktree", baseRef: "main", branch: "t3/abcd1234" },
12391239
}),
12401240
);
12411241
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");
12431243
yield* waitUntil(() =>
12441244
threads
12451245
.getThreadProjection(launched.threadId)
@@ -1248,7 +1248,7 @@ it.effect("keeps the temporary branch when branch generation fails", () =>
12481248
assert.equal(harness.renameBranch.mock.calls.length, 0);
12491249
assert.equal(
12501250
(yield* threads.getThreadProjection(launched.threadId)).thread.branch,
1251-
"t3code/abcd1234",
1251+
"t3/abcd1234",
12521252
);
12531253
}).pipe(Effect.provide(harness.layer));
12541254
}),
@@ -1267,8 +1267,8 @@ it.effect("renames a temporary branch on an existing worktree to a generated nam
12671267
message: "Build the feature",
12681268
workspace: {
12691269
type: "existing_worktree",
1270-
worktreePath: "/repo-worktrees/t3code-abcd1234",
1271-
branch: "t3code/abcd1234",
1270+
worktreePath: "/repo-worktrees/t3-abcd1234",
1271+
branch: "t3/abcd1234",
12721272
},
12731273
}),
12741274
);
@@ -1278,8 +1278,8 @@ it.effect("renames a temporary branch on an existing worktree to a generated nam
12781278
.pipe(Effect.map((projection) => projection.thread.branch === "generated-branch")),
12791279
);
12801280
assert.deepEqual(harness.renameBranch.mock.calls[0]?.[0], {
1281-
cwd: "/repo-worktrees/t3code-abcd1234",
1282-
oldBranch: "t3code/abcd1234",
1281+
cwd: "/repo-worktrees/t3-abcd1234",
1282+
oldBranch: "t3/abcd1234",
12831283
newBranch: "generated-branch",
12841284
});
12851285
}).pipe(Effect.provide(harness.layer));

‎apps/server/src/orchestration-v2/ThreadLaunchService.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ const make = Effect.gen(function* () {
304304
});
305305

306306
// The server owns worktree naming: without an explicit branch, provision
307-
// under a temporary `t3code/<hash>` name so the worktree never waits on
307+
// under a temporary `t3/<hash>` name so the worktree never waits on
308308
// name generation, then rename in the background below.
309309
const requestedBranch = input.workspaceStrategy.branch;
310310
let branch: string | null;

‎apps/web/src/components/settings/BranchNamingSettings.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export function BranchNamingSettings() {
7676
serverScoped
7777
settingKeys={["branchNamePrefix"]}
7878
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."
8080
resetAction={
8181
prefixMixed ||
8282
settings.branchNamePrefix !== DEFAULT_SERVER_SETTINGS.branchNamePrefix ? (

‎docs/user/project-settings.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ and other phone-only settings ignore the filter.
4242

4343
In **Settings → Source Control → Worktree branch naming**, choose a static prefix,
4444
a model-selected semantic prefix such as `feat/` or `fix/`, or custom instructions
45-
for the complete name. The static prefix defaults to `t3code/`; a trailing slash is
45+
for the complete name. The static prefix defaults to `t3/`; a trailing slash is
4646
optional, and an empty prefix adds nothing. Invalid characters in a static prefix
4747
are replaced with hyphens. Custom instructions are appended to
4848
the naming prompt and can specify issue IDs, namespaces, and casing.

‎packages/contracts/src/settings.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,10 +1085,10 @@ it("validates remote device hosts and rejects ambiguous host ids", () => {
10851085
});
10861086

10871087
describe("branch naming settings", () => {
1088-
it("defaults existing settings to the t3code static prefix", () => {
1088+
it("defaults existing settings to the t3 static prefix", () => {
10891089
expect(decodeServerSettings({})).toMatchObject({
10901090
branchNamingMode: "static",
1091-
branchNamePrefix: "t3code",
1091+
branchNamePrefix: "t3",
10921092
branchNameInstructions: "",
10931093
});
10941094
});

‎packages/contracts/src/settings.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1368,7 +1368,7 @@ export const ServerSettings = Schema.Struct({
13681368
branchNamingMode: BranchNamingMode.pipe(
13691369
Schema.withDecodingDefault(Effect.succeed("static" as const)),
13701370
),
1371-
branchNamePrefix: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed("t3code"))),
1371+
branchNamePrefix: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed("t3"))),
13721372
branchNameInstructions: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
13731373
removeAgentCreditsOnMerge: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
13741374
sourceControlWritingStyle: SourceControlWritingStyleSettings.pipe(

‎packages/shared/src/git.test.ts‎

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -190,34 +190,37 @@ describe("isTemporaryWorktreeBranch", () => {
190190
);
191191
});
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);
194197
expect(
195198
isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12`),
196-
).toBe(true);
199+
).toBe(false);
197200
});
198201

199202
it("rejects UUID-shaped refs that are not RFC 4122 v4", () => {
200203
// version nibble is not 4
201-
expect(
202-
isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-1d48-b4f2-9cf0aa54ab12`),
203-
).toBe(false);
204+
expect(isTemporaryWorktreeBranch("t3code/f4ae4e0e-f971-1d48-b4f2-9cf0aa54ab12")).toBe(false);
204205
// variant nibble is not [89ab]
205-
expect(
206-
isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-c4f2-9cf0aa54ab12`),
207-
).toBe(false);
206+
expect(isTemporaryWorktreeBranch("t3code/f4ae4e0e-f971-4d48-c4f2-9cf0aa54ab12")).toBe(false);
208207
});
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", () => {
211210
const flat = flattenTemporaryWorktreeBranchName(`${WORKTREE_BRANCH_PREFIX}/deadbeef`);
212211
expect(flat).toBe(`${WORKTREE_BRANCH_PREFIX}-deadbeef`);
213212
expect(isTemporaryWorktreeBranch(flat)).toBe(true);
214213
expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}-deadbeef-extra`)).toBe(false);
215214
expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}-feature`)).toBe(false);
216-
expect(
217-
flattenTemporaryWorktreeBranchName(
218-
`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12`,
219-
),
220-
).toBe(`${WORKTREE_BRANCH_PREFIX}-f4ae4e0e`);
215+
expect(flattenTemporaryWorktreeBranchName("t3code/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12")).toBe(
216+
`${WORKTREE_BRANCH_PREFIX}-f4ae4e0e`,
217+
);
218+
expect(flattenTemporaryWorktreeBranchName("t3code-deadbeef")).toBe(
219+
`${WORKTREE_BRANCH_PREFIX}-deadbeef`,
220+
);
221+
expect(flattenTemporaryWorktreeBranchName("t3code/deadbeef")).toBe(
222+
`${WORKTREE_BRANCH_PREFIX}-deadbeef`,
223+
);
221224
});
222225

223226
it("rejects non-temporary refName names", () => {

‎packages/shared/src/git.ts‎

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@ import * as Arr from "effect/Array";
1111
import * as Result from "effect/Result";
1212
import { detectSourceControlProviderFromRemoteUrl } from "./sourceControl.ts";
1313

14-
export const WORKTREE_BRANCH_PREFIX = "t3code";
15-
// Canonical form is `t3code/<8 hex>`. Older mobile builds generated `t3code/<uuid>`
16-
// via Crypto.randomUUID() (always RFC 4122 v4), so the matcher also accepts exactly
17-
// that shape — version nibble `4`, variant nibble `[89ab]` — to keep those threads
18-
// eligible for branch regeneration without loosening beyond what was ever generated.
19-
// `t3code-<8 hex>` is the fallback when a plain `t3code` branch blocks the namespace.
14+
export const WORKTREE_BRANCH_PREFIX = "t3";
15+
// Canonical form is `t3/<8 hex>`. `t3-<8 hex>` is the fallback when a plain `t3`
16+
// branch blocks the namespace. The matcher also accepts every legacy shape, so
17+
// existing threads stay eligible for branch regeneration: `t3code/<8 hex>` and
18+
// `t3code-<8 hex>` from before the prefix was shortened, and `t3code/<uuid>` from
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.
21+
const TEMP_WORKTREE_HEX_TOKEN = "[0-9a-f]{8}";
22+
const 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}";
2024
const 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}))$`,
2226
);
2327

2428
/**
@@ -125,15 +129,14 @@ export function buildTemporaryWorktreeBranchName(
125129
}
126130

127131
/**
128-
* Git stores refs as paths, so a plain `t3code` branch makes every `t3code/<hex>`
129-
* ref impossible. This moves a temporary name to the flat `t3code-<hex>` sibling.
132+
* Git stores refs as paths, so a plain `t3` branch makes every `t3/<hex>`
133+
* ref impossible. This moves a temporary name to the flat `t3-<hex>` sibling.
130134
*/
131135
export function flattenTemporaryWorktreeBranchName(refName: string): string {
132-
// Keep only the canonical 8-hex token so legacy UUID names stay recognizable.
133-
const token = refName
134-
.trim()
135-
.toLowerCase()
136-
.slice(WORKTREE_BRANCH_PREFIX.length + 1, WORKTREE_BRANCH_PREFIX.length + 9);
136+
// Keep only the canonical 8-hex token so legacy `t3code/` and UUID names map cleanly.
137+
const normalized = refName.trim().toLowerCase();
138+
const tokenStart = normalized.search(/[-/]/) + 1;
139+
const token = normalized.slice(tokenStart, tokenStart + 8);
137140
return `${WORKTREE_BRANCH_PREFIX}-${token}`;
138141
}
139142

0 commit comments

Comments
 (0)