Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
88ab83e
fix(web): reconnecting notice no longer shifts the new thread compose…
t3dotgg Oct 29, 2026
20497e4
feat(marketing): homepage looks like the app (#26960)
t3dotgg Oct 29, 2026
37fbefd
chore(release): prepare v0.1.5
t3dotgg Oct 29, 2026
960d576
fix(cli): restore Windows desktop launcher console input (#24870)
t3-code-bot[bot] Oct 29, 2026
7a4f5b6
feat(settings): add and list 1Password secret providers behind T3CODE…
yordis Oct 29, 2026
d152784
fix(mobile): use monospace in the Android terminal preview (#23401)
only21mil Oct 29, 2026
ecea6ca
fix(web): edit from here uses a pencil icon (#21878)
voltcrash Oct 29, 2026
b4da896
fix(desktop): clear Cache Storage when deleting browser profile data …
only21mil Oct 29, 2026
3d5baaf
fix(web): respect reduced motion in thread loading controls (#22100)
saphid Oct 29, 2026
49d0142
fix(provider-acp): redact API keys with underscores in ACP stderr log…
only21mil Oct 29, 2026
67afdd5
fix(provider-core): redact compound credential keys in provider error…
only21mil Oct 29, 2026
456892d
fix(web): show rounded diff counts as 1m instead of 1000k (#23723)
only21mil Oct 29, 2026
5e35f6e
fix(desktop): a timed-out login-shell probe no longer skips the serve…
tiliakoos Oct 29, 2026
55e7b5f
fix(server): keep the previous preview browser version on a shared ho…
only21mil Oct 29, 2026
ee81ea4
fix(settings): servers check secret provider writes and keep unknown …
juliusmarminge Oct 29, 2026
bfee446
fix(web): keep a closed split terminal's tab in view (#22106)
only21mil Oct 29, 2026
69310db
fix(web): the ~ breadcrumb opens the home folder (#22412)
Onkarj012 Oct 29, 2026
505d022
fix(gitlab): accept merge request URLs from self-hosted hosts (#23848)
only21mil Oct 29, 2026
2c37797
fix(server): commits no longer land on a branch switched in mid-actio…
only21mil Oct 29, 2026
3b066da
fix(server): image attachments report their size when the T3 home is …
MarcoDotIO Oct 29, 2026
ebc34da
fix(ssh): skip directories in config includes (#25187)
maria-rcks Oct 29, 2026
80dfe47
fix(server): Authorized clients names Edge, Firefox and Opera on phon…
MarcoDotIO Oct 29, 2026
1d7af65
fix(mobile): fork rows show a readable label instead of a thread ID (…
JeremiahDJordan Oct 29, 2026
1d63134
fix(markdown): formulas ending in a comma render (#22883)
NikitaMGrimm Oct 29, 2026
2049052
fix(scripts): dev runner expands ~ in --home-dir and T3CODE_HOME (#25…
MarcoDotIO Oct 29, 2026
1dd8c67
fix(desktop): keep development protocol requests on the configured se…
only21mil Oct 29, 2026
e0f972e
fix(mobile): apply terminal toolbar modifiers to arrow keys (#22161)
only21mil Oct 29, 2026
40f6f5b
fix(server): clean old logs and artifacts under a symlinked T3 home (…
only21mil Oct 29, 2026
b02b436
fix(server): preserve URL arguments in Android device actions (#25205)
maria-rcks Oct 29, 2026
c839797
fix(server): keep terminal history after cancelled sequences (#25193)
maria-rcks Oct 29, 2026
5aa81a3
fix(auth): accept a typed pairing code regardless of letter case (#22…
only21mil Oct 29, 2026
a03e630
fix(web): find in thread opens from the thread title menu, so phones …
mkantautas Oct 29, 2026
e692729
fix(desktop): resolve executable commands when checking CLI shadowing…
only21mil Oct 29, 2026
a8e4188
fix(server): SQLite temp files no longer block the event loop (#24194)
only21mil Oct 29, 2026
849d2b5
fix(server): a branch named like a tag no longer reads as heads/<name…
MarcoDotIO Oct 29, 2026
0ed5540
fix(server): cap generated worktree folder names (#25212)
maria-rcks Oct 29, 2026
c6f2b7d
perf(server): checkpoint capture scans the index listing by record in…
josephv123 Oct 29, 2026
aaba9de
fix(server): PR discovery sweeps skip hidden subagent threads (#26983)
juliusmarminge Oct 29, 2026
8084e00
fix(shared): stop parsing CLI flags after -- (#25204)
maria-rcks Oct 29, 2026
293669d
fix(server): remove abandoned v1 database copies on startup (#25301)
maria-rcks Oct 29, 2026
6ca95bd
fix(web): Enter no longer skips an unanswered question (#23626)
mkantautas Oct 29, 2026
d152335
fix(web): keep wide characters in terminal links (#24013)
only21mil Oct 29, 2026
a49a3f7
fix(server): session stops no longer trigger storage sweeps unless a …
juliusmarminge Oct 29, 2026
443edd5
fix(provider-core): subagent threads no longer inherit the parent's p…
only21mil Oct 29, 2026
1b3e330
fix(device): report failed Android permission changes (#22358)
only21mil Oct 29, 2026
0d51375
fix(server): thread lists load beyond SQLite variable limit (#25696)
juliusmarminge Oct 29, 2026
1e3e3a8
fix(server): allow large git status metadata (#25207)
maria-rcks Oct 29, 2026
781cc55
perf(mobile): flatten large file trees without copying descendants (#…
luke2x Oct 29, 2026
80ae9b5
fix(web): show theme conflicts even when another import fails (#23223)
only21mil Oct 29, 2026
061dc79
fix(usage): reject cyclic model mappings before saving (#23963)
only21mil Oct 29, 2026
bae3c19
fix(settings): offer Use existing CLI in managed Codex settings (#23160)
only21mil Oct 29, 2026
1e95da7
fix(git): honor project provider settings (#23672)
parmartejass Oct 29, 2026
efb940d
fix(desktop): force-close stalled Linux MIME cache helpers (#25210)
maria-rcks Oct 29, 2026
bdf84df
fix(cli): auth pairing create rejects a --base-url without http(s)://…
MarcoDotIO Oct 29, 2026
4afdaba
fix(server): signed asset and upload URLs stay out of HTTP traces (#2…
only21mil Oct 29, 2026
1cc9d41
fix(server): clone errors no longer show a token from the URL's query…
only21mil Oct 29, 2026
c9d3f7c
fix(claude): pass every --add-dir launch directory to Claude (#23281)
only21mil Oct 29, 2026
4b7a347
fix(composer): retry expired catalogs after failed discovery (#23174)
only21mil Oct 29, 2026
7860ee9
fix(auth): preserve pairing codes after scope rejection (#25222)
maria-rcks Oct 29, 2026
04f3538
fix(mobile): discard unchanged queued edits after starting (#25232)
maria-rcks Oct 29, 2026
b7c7a78
fix(server): t3 project commands wait for a busy server's reply (#24090)
only21mil Oct 29, 2026
9d38d68
fix(web): wait for fresh diff pages in review guides (#23225)
only21mil Oct 29, 2026
99ff58a
fix(server): Antigravity stops retrying session startup while signed …
only21mil Oct 29, 2026
de386ad
fix(web): create tasks on a permitted connected environment (#23244)
only21mil Oct 29, 2026
fa2a2dc
fix(mobile): recognize this thread's main checkout (#23764)
only21mil Oct 29, 2026
06ded0b
fix(web): keep usage hubs that share a host (#23767)
only21mil Oct 29, 2026
f8646e0
fix(github): read text and mixed-case pull request templates (#25025)
maria-rcks Oct 29, 2026
de2b1d3
fix(web): keep HTML prototype selection rings visible (#23745)
stevan-borus Oct 29, 2026
12bd9d6
fix(mobile): list pull request repositories once per environment (#25…
maria-rcks Oct 29, 2026
4e274ae
fix(shared): Open in Zed prefers zeditor over the ZFS zed on Linux (#…
pujitha24 Oct 29, 2026
ef51594
fix(server): native Codex subagents no longer show the parent's reaso…
koushikxd Oct 29, 2026
3bd64cf
fix(server): mark unread works after an automatic compaction (#25286)
flamboh Oct 29, 2026
a1621a8
fix(server): show MCP App forms from failed tool results (#25225)
maria-rcks Oct 29, 2026
ad5ba43
fix(server): restore Linux session display for editor launches (#25228)
maria-rcks Oct 29, 2026
9e60e94
fix(usage): keep host identity across container recreation (#25220)
maria-rcks Oct 29, 2026
d796063
fix(opencode): authenticate with the resolved server password (#25230)
maria-rcks Oct 29, 2026
f06dd4b
feat(web): allow resizing the file explorer (#25737)
maria-rcks Oct 29, 2026
f4147dd
fix(server): update shared Codex installs from their owning home (#25…
maria-rcks Oct 29, 2026
8bcc991
fix(cli): keep published runtimes in place during installation (#24092)
only21mil Oct 29, 2026
e836ab1
fix(web): right panel launcher letters work on non-Latin keyboard lay…
MarMarLabs Oct 29, 2026
5b4f2aa
fix(server): report a deleted or replaced ownership lock (#21886)
ScottN-PV Oct 29, 2026
cb5fac9
fix(server): mark unread no longer brings back a dismissed wake (#24080)
fixfon Oct 29, 2026
fa4f3a2
fix(mobile): stop refreshes in hidden PR Guide panes (#25486)
saphid Oct 29, 2026
b6fbd18
fix(desktop): installing the t3 command no longer reports its own lau…
Gourav224 Oct 29, 2026
1fe8ddd
test(mobile): load the branch occupancy test without native sources (…
maria-rcks Oct 29, 2026
08a463e
fix(settings): project edits keep overrides the client does not know …
only21mil Oct 29, 2026
4092c4a
fix(client): retry the permissions check after a reconnect (#27008)
juliusmarminge Oct 29, 2026
da04ebc
fix(mobile): tab bar spans the screen on iOS before 26 (#27089)
juliusmarminge Oct 29, 2026
624e70e
perf(server): thread recovery reads are 10x faster (#27099)
juliusmarminge Oct 29, 2026
3898c32
perf(server): source control discovery no longer stalls up to 10s on …
juliusmarminge Oct 29, 2026
7f39a86
perf: huge checkpoints ship up to 75x smaller to clients (#27110)
juliusmarminge Oct 29, 2026
8bf4822
perf(server): sidebar shell loads 3x faster and startup thread counts…
juliusmarminge Oct 29, 2026
22c37a8
perf(server): preparing a run is 15x faster and uses 200x less memory…
juliusmarminge Oct 29, 2026
2f4da5d
feat(server): traces name what grew the heap, for ~0.3µs per span (#2…
juliusmarminge Oct 29, 2026
52f4ec6
perf(gitcafe): PR sync makes 2x fewer GitCafe requests for unchanged …
juliusmarminge Oct 29, 2026
facaa08
perf(shared): SQLite reads stop queueing behind each other, up to 5x …
juliusmarminge Oct 29, 2026
7c29f2d
perf(server): git status runs in parallel and repo detection spawns 3…
juliusmarminge Oct 29, 2026
65b0ab8
fix(client): dead connection routes are probed 30x less often (#27101)
juliusmarminge Oct 29, 2026
dd14848
perf(server): idle servers run 100x fewer background queries and 4.8M…
juliusmarminge Oct 29, 2026
d0963f3
Merge upstream/main dd14848153 into coder-only
henrychu04 Oct 30, 2026
be71d65
Unexport GITLAB_MEDIA_MAX_BYTES for knip
henrychu04 Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "t3",
"version": "0.1.4",
"version": "0.1.5",
"license": "MIT",
"type": "module",
"scripts": {
Expand Down
27 changes: 27 additions & 0 deletions apps/server/src/assets/AssetAccess.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,33 @@ describe("AssetAccess", () => {
}).pipe(Effect.provide(layerTest)),
);

it.effect.skipIf(!symlinksSupported)(
"reports image attachment dimensions when the attachments directory is a symlink",
() =>
Effect.gen(function* () {
const config = yield* ServerConfig.ServerConfig;
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-attachments-link-" });
const realAttachmentsDir = path.join(root, "real");
const linkedAttachmentsDir = path.join(root, "linked");
yield* fs.makeDirectory(realAttachmentsDir);
yield* fs.symlink(realAttachmentsDir, linkedAttachmentsDir);
const attachmentId = "thread-1-00000000-0000-4000-8000-000000000041";
yield* fs.writeFile(path.join(realAttachmentsDir, `${attachmentId}.png`), screenshotPng);

const result = yield* issueAssetUrl({
resource: { _tag: "attachment", attachmentId },
}).pipe(
Effect.provideService(ServerConfig.ServerConfig, {
...config,
attachmentsDir: linkedAttachmentsDir,
}),
);
expect(result.imageDimensions).toEqual({ width: 390, height: 844 });
}).pipe(Effect.provide(layerTest)),
);

it.effect("serves document attachments inline when a viewer requests it", () =>
Effect.gen(function* () {
const config = yield* ServerConfig.ServerConfig;
Expand Down
5 changes: 4 additions & 1 deletion apps/server/src/assets/AssetAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,10 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i
? inlinePreviewMimeTypeForExtension(extension)
: undefined;
if (!isGenericFile) {
imageDimensions = yield* readImageDimensionsFromHeader(attachmentPath);
imageDimensions = yield* fileSystem.realPath(attachmentPath).pipe(
Effect.flatMap(readImageDimensionsFromHeader),
Effect.orElseSucceed(() => null),
);
}
claims = {
version: 1,
Expand Down
62 changes: 51 additions & 11 deletions apps/server/src/git/GitManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2588,7 +2588,45 @@ it.layer(layerGitManagerTest)("GitManager", (it) => {
}),
);

it.effect("applies a V2-native thread's project settings to generated commit text", () =>
it.effect("uses an explicit project's provider settings for generated commit text", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
yield* initRepo(repoDir);
NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\nproject\n");
const projectId = ProjectId.make("project:git-action-settings");
const disabledInstanceId = DEFAULT_SERVER_SETTINGS.textGenerationModelSelection.instanceId;
let generatedModelSelection:
| TextGeneration.CommitMessageGenerationInput["modelSelection"]
| undefined;

const { manager } = yield* makeManager({
serverSettings: {
projectSettingsOverrides: {
[projectId]: {
providerInstances: { [disabledInstanceId]: { enabled: false } },
},
},
},
textGeneration: {
generateCommitMessage: (input) => {
generatedModelSelection = input.modelSelection;
return Effect.succeed({ subject: "Use the project writer", body: "" });
},
},
});

yield* manager.runStackedAction({
actionId: "test-project-action-settings",
cwd: repoDir,
action: "commit",
projectId,
});

expect(generatedModelSelection?.instanceId).toBe(ProviderInstanceId.make("claudeAgent"));
}),
);

it.effect("prefers a V2-native thread's project settings over an explicit project", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
yield* initRepo(repoDir);
Expand Down Expand Up @@ -2675,6 +2713,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => {
cwd: repoDir,
action: "commit",
threadId,
projectId: ProjectId.make("project:other"),
});

expect(generatedModelSelection).toEqual(projectModel);
Expand Down Expand Up @@ -3900,7 +3939,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => {
return { repoDir, manager, providerCalls, firstCommit, pullHead };
});

it.effect.each(["switch", "create-and-switch", "feature-branch"] as const)(
it.effect.each(["switch", "create-and-switch", "feature-branch", "commit"] as const)(
"local PR checkout excludes a concurrent %s from a nested project with an alternate root",
(operation) =>
Effect.gen(function* () {
Expand Down Expand Up @@ -3950,7 +3989,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => {
...manager,
runStackedAction: (input, options) =>
Effect.sync(() => {
events.push("feature-branch");
events.push(input.featureBranch ? "feature-branch" : "commit");
NodeFS.writeFileSync(
NodePath.join(repoDir, "second-client.txt"),
"second client\n",
Expand Down Expand Up @@ -3987,9 +4026,9 @@ it.layer(layerGitManagerTest)("GitManager", (it) => {
})
: workflows[1]!.runStackedAction({
cwd: nestedCwd,
actionId: "second-client-feature-branch",
actionId: `second-client-${operation}`,
action: "commit",
featureBranch: true,
featureBranch: operation === "feature-branch",
commitMessage: "Second client commit",
}),
{ startImmediately: true },
Expand All @@ -4002,15 +4041,16 @@ it.layer(layerGitManagerTest)("GitManager", (it) => {
expect(events).toEqual([operation]);
expect(prepared).toMatchObject({ branch: "feature/pr-safe", isOnPullRequestHead: true });
expect((yield* runGit(repoDir, ["rev-parse", "main"])).stdout.trim()).toBe(mainCommit);
expect((yield* runGit(repoDir, ["rev-parse", "feature/pr-safe"])).stdout.trim()).toBe(
pullHead,
);
const pullBranch = operation === "commit" ? "feature/pr-safe^" : "feature/pr-safe";
expect((yield* runGit(repoDir, ["rev-parse", pullBranch])).stdout.trim()).toBe(pullHead);
expect((yield* runGit(repoDir, ["branch", "--show-current"])).stdout.trim()).toBe(
operation === "switch"
? "main"
: "branch" in switched
? switched.branch.name
: "second-client",
: operation === "commit"
? "feature/pr-safe"
: "branch" in switched
? switched.branch.name
: "second-client",
);
if ("commit" in switched) {
expect(switched.commit.status).toBe("created");
Expand Down
24 changes: 15 additions & 9 deletions apps/server/src/git/GitManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -672,19 +672,25 @@ export const make = Effect.gen(function* () {
const projectSettingsFor = Effect.fnUntraced(function* (input: {
readonly cwd: string;
readonly threadId?: ThreadId | undefined;
readonly projectId?: ProjectId | undefined;
}) {
const settings = yield* serverSettingsService.getSettings;
if (!hasProjectSettingsOverrides(settings))
return { settings, projectId: null as ProjectId | null };
const projectId: ProjectId | null = yield* input.threadId !== undefined
? threads.getThreadShell(input.threadId).pipe(
Effect.map((thread) => thread?.projectId ?? null),
Effect.orElseSucceed(() => null),
)
: projects.findActiveByWorkspaceRoot(input.cwd).pipe(
Effect.map((project) => Option.getOrNull(project)?.projectId ?? null),
Effect.orElseSucceed(() => null),
);
const threadProjectId =
input.threadId === undefined
? null
: yield* threads.getThreadShell(input.threadId).pipe(
Effect.map((thread) => thread?.projectId ?? null),
Effect.orElseSucceed(() => null),
);
const projectId =
threadProjectId ??
input.projectId ??
(yield* projects.findActiveByWorkspaceRoot(input.cwd).pipe(
Effect.map((project) => Option.getOrNull(project)?.projectId ?? null),
Effect.orElseSucceed(() => null),
));
return { settings: resolveProjectSettings(settings, projectId).settings, projectId };
});
const readThreadContext = Effect.fnUntraced(
Expand Down
10 changes: 4 additions & 6 deletions apps/server/src/git/GitWorkflowService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,12 +396,10 @@ export const make = Effect.gen(function* () {
runStackedAction: Effect.fn("GitWorkflowService.runStackedAction")(function (input, options) {
return ensureGit("GitWorkflowService.runStackedAction", input.cwd).pipe(
Effect.flatMap((handle) =>
input.featureBranch
? withCheckoutLease(
handle.repository.rootPath,
gitManager.runStackedAction(input, options),
)
: gitManager.runStackedAction(input, options),
withCheckoutLease(
handle.repository.rootPath,
gitManager.runStackedAction(input, options),
),
),
);
}),
Expand Down
37 changes: 37 additions & 0 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,21 @@ function makeClaudeTestTurnInput(input: {
}

describe("ClaudeAdapterV2 runtime query policy", () => {
it.each([
"-- --thinking-display omitted",
"--thinking-display summarized -- --thinking-display omitted",
])("does not treat operands after -- as SDK flags: %s", (launchArgs) => {
const options = ClaudeAdapterV2.makeClaudeQueryOptions({
modelSelection: CLAUDE_TEST_MODEL_SELECTION,
nativeThreadId: "thinking-thread",
resume: false,
cwd: "/workspace",
settings: { ...DEFAULT_CLAUDE_SETTINGS, launchArgs },
});
assert.deepEqual(options.thinking, { type: "adaptive", display: "summarized" });
assert.equal(options.extraArgs?.["thinking-display"], "summarized");
});

it.each([
["--permission-mode acceptEdits", "acceptEdits"],
["--dangerously-skip-permissions", "bypassPermissions"],
Expand All @@ -218,6 +233,28 @@ describe("ClaudeAdapterV2 runtime query policy", () => {
assert.isUndefined(options.extraArgs?.["dangerously-skip-permissions"]);
});

it("passes every --add-dir launch directory to Claude", () => {
const options = ClaudeAdapterV2.makeClaudeQueryOptions({
modelSelection: CLAUDE_TEST_MODEL_SELECTION,
nativeThreadId: "native-add-dir",
resume: false,
cwd: "/workspace",
attachmentsDir: "/attachments",
settings: {
...AUTO_COMPACT_CLAUDE_SETTINGS,
launchArgs: "--add-dir ../apps ../lib --verbose --add-dir=/shared",
},
});
assert.deepEqual(options.additionalDirectories, [
"/workspace",
"/attachments",
"../apps",
"../lib",
"/shared",
]);
assert.isUndefined(options.extraArgs?.["add-dir"]);
});

it("gives delegated children Claude's subagent prompt cache TTL", () => {
const lineage = (relationshipToParent: "fork" | "subagent" | null) => ({
lineage: {
Expand Down
4 changes: 4 additions & 0 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ import {
type ClaudeSkillTarget,
} from "../../provider/Drivers/ClaudeSkillDispatch.ts";
import {
claudeLaunchArgDirectories,
discoverClaudeSkills,
skillOverrideSettingsPaths,
} from "../../provider/Drivers/ClaudeSkills.ts";
Expand Down Expand Up @@ -1371,6 +1372,8 @@ export function makeClaudeQueryOptions(input: {
}): ClaudeAgentSdkQueryOptions {
const compiledSelection = compileClaudeModelSelection(input.modelSelection);
const {
// claudeLaunchArgDirectories reads every --add-dir into additionalDirectories.
"add-dir": _launchArgAddDir,
"permission-mode": launchArgPermissionMode,
"dangerously-skip-permissions": launchArgSkipPermissions,
"append-system-prompt": launchArgAppendSystemPrompt,
Expand Down Expand Up @@ -1496,6 +1499,7 @@ export function makeClaudeQueryOptions(input: {
const additionalDirectories = [
...(input.cwd === null ? [] : [input.cwd]),
...(input.attachmentsDir === undefined ? [] : [input.attachmentsDir]),
...claudeLaunchArgDirectories(input.settings?.launchArgs),
];
const withDirectories =
additionalDirectories.length === 0 ? options : { ...options, additionalDirectories };
Expand Down
Loading