Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
11e91f1
feat(mobile): capture Live Activities and agent notifications in show…
juliusmarminge Sep 23, 2026
68fb7f4
feat(mobile): manage environment and provider updates (#13302)
juliusmarminge Sep 23, 2026
e407f9b
fix(ci): shard release tests like pull request CI (#13321)
juliusmarminge Sep 23, 2026
effaab9
fix(web): show previous worktree branch on second line (#13314)
Yash-Singh1 Sep 23, 2026
d4cd7d5
fix(providers): restore compatibility ranges for every harness (#13328)
juliusmarminge Sep 24, 2026
894d334
fix(preview): use the visible browser for new agent sessions (#13064)
Bil0000 Sep 24, 2026
e4eb997
fix(server): stop replaying old agent alerts on restart (#13340)
juliusmarminge Sep 24, 2026
c0912de
fix(web): use a brain icon for the effort dropdown (#13309)
t3-code[bot] Sep 24, 2026
6b4b190
fix(desktop): SnapShot shortcut helper no longer adds a Dock icon on …
Gigioxx Sep 24, 2026
9030a60
fix(web): composer chip rings no longer clip at the editor edge (#13301)
flamboh Sep 24, 2026
21e2b7d
fix(web): switches announce their real state to screen readers (#11580)
Leos-Khai Sep 24, 2026
f1add18
fix(shared): preserve final quoted empty CSV records (#11425)
Lucenx9 Sep 24, 2026
78af372
feat(web): add an interactive 3D device workspace (#12787)
juliusmarminge Sep 24, 2026
9383f4a
chore: add scratchyone to vouched list (#13353)
t3-code[bot] Sep 24, 2026
e67abcf
feat(observability): honor the OpenTelemetry kill switch (#13355)
juliusmarminge Sep 24, 2026
6e66bd1
fix(cli): distinguish triage CLI version from the running server
cursoragent Sep 24, 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
1 change: 1 addition & 0 deletions .github/VOUCHED.td
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ github:realAhmedRoach
github:Rishet11
github:ryanrhughes
github:saphid
github:scratchyone
github:sethwebster
github:shiroyasha9
github:shivamhwp
Expand Down
30 changes: 24 additions & 6 deletions .github/triage/PLAYBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ valuable thing you can extract from this conversation.

## 2. Read the machine facts

Read the triage context file before investigating. It tells you the installed
version, the OS, whether the server process is currently running, and the exact
paths for state, logs, and the database.
Read the triage context file before investigating. It tells you the OS, whether
the server process is currently running, and the exact paths for state, logs,
and the database.

`Installed version` (older CLIs) and `Triage CLI version` (newer CLIs) name the
`t3` binary that wrote the context file. That is not the build the bug happened
on. `npx t3 triage` is whichever `t3` npm resolved, and it can be older than the
desktop app or background service.

## 3. Check for a newer playbook

Expand All @@ -30,8 +35,21 @@ instead of this one. The user may be on an old release with an old copy.

## 4. Get the source

Clone the repo at the tag matching the user's installed version, into the source
cache directory named in the context file, one subdirectory per commit hash:
Before cloning, ask which version, device, and surface the bug happened on. A
version read on this machine is the wrong tree when the bug was on another
device, a remote server, or a build the user has updated since.

If a server is up, read `serverVersion` from
`GET <origin>/.well-known/t3/environment` (unauthenticated). The context file's
`Server process` line has the origin. Newer context files may already record it
as `Local server version`. Desktop and server builds share a version, so that
value is the desktop app when its bundled server is the one answering. Treat it
as a local fact to check against the user's answer. Clone it only when they
confirm the bug happened on that server.

Clone the repo at the tag matching the version the bug happened on, into the
source cache directory named in the context file, one subdirectory per commit
hash:

git clone --depth 1 --filter=blob:none --branch <release-tag> \
https://github.com/pingdotgg/t3code <source-cache-dir>/<hash>
Expand Down Expand Up @@ -81,7 +99,7 @@ comes from this repo's `main` branch.

Search existing issues in pingdotgg/t3code (use `gh`, or the public GitHub search
API if `gh` is missing or not logged in). Then check whether the problem is already
fixed in a release newer than the user's version: compare versions, read release
fixed in a release newer than the version the bug happened on: compare versions, read release
notes and recent commits touching the relevant code.

If the user is behind and the fix likely shipped, say so plainly and give them the
Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/mobile-eas-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,19 @@ jobs:
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
run: eas env:pull preview --non-interactive
run: |
eas env:pull preview --non-interactive
# EAS Update disables dotenv loading. Keep the downloaded native config
# in its process environment so Android build and OTA fingerprints match.
node --input-type=module <<'NODE'
import { appendFileSync, readFileSync } from "node:fs";
import { parseEnv } from "node:util";
const env = parseEnv(readFileSync(".env.local", "utf8"));
const googleServicesFile = env.T3CODE_ANDROID_GOOGLE_SERVICES_FILE;
if (googleServicesFile) {
appendFileSync(process.env.GITHUB_ENV, `T3CODE_ANDROID_GOOGLE_SERVICES_FILE=${googleServicesFile}\n`);
}
NODE

- name: Deploy with fingerprint check
if: steps.expo-token.outputs.present == 'true'
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/mobile-showcase-screenshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ jobs:
echo "$vp_pnpm_bin" >> "$GITHUB_PATH"
"$vp_pnpm_bin/pnpm" --version

- name: Install AXe
# Locks the simulator and answers the notification prompt for the
# agent-activity scene.
# Homebrew refuses third-party tap formulae until they are trusted;
# older releases have no trust command and install them as is.
run: |
brew tap cameroncooke/axe
if brew commands | grep -qx trust; then brew trust --formula cameroncooke/axe/axe; fi
brew install cameroncooke/axe/axe
axe --version

- name: Capture iOS showcase
run: pnpm screenshots:mobile --platform ios --appearance "${{ inputs.appearance }}" --theme "${{ inputs.theme }}"

Expand Down
71 changes: 68 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -259,13 +259,76 @@ jobs:
- name: Typecheck
run: vp run typecheck

# Keep tests on their own runners, using the same package split and server
# shards as CI, so the release check job does not spend its budget on tests.
test:
name: Release tests
needs: [preflight]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.ref }}
sparse-checkout: |
/*
!/.repos/
sparse-checkout-cone-mode: false

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: true

- name: Ensure Electron runtime is installed
run: vp run --filter @t3tools/desktop ensure:electron

- uses: ./.github/actions/setup-apt-mirrors

- name: Install browser secret helper build libraries
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config
run: |
sudo sed -i 's|http://|https://|g' /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config build-essential

- name: Test
run: vp run --parallel --concurrency-limit 4 --filter '!t3' --filter '!@t3tools/monorepo' test

test_server:
name: Release server tests ${{ matrix.shard }}
needs: [preflight]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3]
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.ref }}
sparse-checkout: |
/*
!/.repos/
sparse-checkout-cone-mode: false

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: true

# No Electron setup here: `t3` (apps/server) has no Electron dependency
# and none of its tests touch the runtime. Only the non-server `test`
# job, which covers @t3tools/desktop, needs the download.
- name: Test
run: vp run test
run: vp run --filter t3 test --shard ${{ matrix.shard }}/${{ strategy.job-total }}

relay_public_config:
name: Resolve T3 Connect public config
Expand Down Expand Up @@ -630,13 +693,15 @@ jobs:
preflight,
relay_public_config,
quality,
test,
test_server,
desktop_mac_arm64,
desktop_linux_x64,
desktop_linux_arm64,
desktop_win_x64,
desktop_win_arm64,
]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.quality.result == 'success' && needs.desktop_mac_arm64.result == 'success' && needs.desktop_linux_x64.result == 'success' && needs.desktop_linux_arm64.result == 'success' && needs.desktop_win_x64.result == 'success' && needs.desktop_win_arm64.result == 'success' }}
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.quality.result == 'success' && needs.test.result == 'success' && needs.test_server.result == 'success' && needs.desktop_mac_arm64.result == 'success' && needs.desktop_linux_x64.result == 'success' && needs.desktop_linux_arm64.result == 'success' && needs.desktop_win_x64.result == 'success' && needs.desktop_win_arm64.result == 'success' }}
runs-on: ubuntu-24.04 # blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 15
permissions:
Expand Down
64 changes: 64 additions & 0 deletions apps/desktop/src/app/DesktopObservability.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import * as ConfigProvider from "effect/ConfigProvider";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
Expand Down Expand Up @@ -427,6 +428,36 @@ describe("DesktopObservability", () => {
);
});

it.effect("exports kill switch warnings through the configured logger", () => {
const requests: Array<ExportedRequest> = [];
return Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-observability-test-",
});
const environmentLayer = makeEnvironmentLayer(baseDir, true, {
T3CODE_OTLP_LOGS_URL: "https://collector.example.com/v1/logs",
});

yield* Effect.scoped(
Effect.void.pipe(
Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
),
);

assert.include(requests[0]?.body ?? "", "OTEL_SDK_DISABLED=1 was read as false");
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
collectorLayer(requests),
ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OTEL_SDK_DISABLED: "1" } })),
),
),
);
});

it.effect("reads every signal endpoint from Settings when the environment names none", () => {
const requests: Array<ExportedRequest> = [];
return Effect.gen(function* () {
Expand Down Expand Up @@ -491,4 +522,37 @@ describe("DesktopObservability", () => {
Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests))),
);
});

it.effect("stops every export when the OpenTelemetry SDK is disabled", () => {
const requests: Array<ExportedRequest> = [];
return Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-observability-test-",
});
const environmentLayer = makeEnvironmentLayer(baseDir);
yield* writeObservabilitySettings(environmentLayer, {
otlpTracesUrl: "https://settings.example.com/v1/traces",
otlpLogsUrl: "https://settings.example.com/v1/logs",
});

yield* Effect.scoped(
Effect.logInfo("desktop log stays local when disabled").pipe(
Effect.withSpan("desktop-disabled-test"),
Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
),
);

assert.lengthOf(requests, 0);
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
collectorLayer(requests),
ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OTEL_SDK_DISABLED: "true" } })),
),
),
);
});
});
14 changes: 13 additions & 1 deletion apps/desktop/src/app/DesktopObservability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
makeTraceSink,
otlpSerializationLayer,
} from "@t3tools/shared/observability";
import * as OtelEnvironment from "@t3tools/shared/otelEnvironment";
import {
parsePersistedServerObservabilitySettings,
type PersistedServerObservabilitySettings,
Expand Down Expand Up @@ -353,12 +354,18 @@ const readPersistedObservabilitySettings: Effect.Effect<
* resolve traces against one revision of the file and logs against another.
*/
const resolveOtlpEndpoints = Effect.gen(function* () {
const otel = yield* OtelEnvironment.load;
if (otel.disabled) {
return { traces: undefined, metrics: undefined, logs: undefined, warnings: otel.warnings };
}

const environment = yield* DesktopEnvironment.DesktopEnvironment;
const persisted = yield* readPersistedObservabilitySettings;
return {
traces: Option.getOrUndefined(environment.otlpTracesUrl) ?? persisted.otlpTracesUrl,
metrics: Option.getOrUndefined(environment.otlpMetricsUrl) ?? persisted.otlpMetricsUrl,
logs: Option.getOrUndefined(environment.otlpLogsUrl) ?? persisted.otlpLogsUrl,
warnings: otel.warnings,
};
});

Expand Down Expand Up @@ -671,7 +678,12 @@ const telemetryLayer = Layer.unwrap(
// resource,
// }).pipe(Layer.provide(serializationLayer));

return Layer.mergeAll(loggerLayer, tracerLayer);
// Logged once the loggers above are installed, so the warnings use them.
const otelWarningsLayer = Layer.effectDiscard(
Effect.forEach(endpoints.warnings, (warning) => Effect.logWarning(warning)),
);

return otelWarningsLayer.pipe(Layer.provideMerge(Layer.mergeAll(loggerLayer, tracerLayer)));
}),
);

Expand Down
43 changes: 43 additions & 0 deletions apps/desktop/src/backend/DesktopBackendConfiguration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,49 @@ describe("DesktopBackendConfiguration", () => {
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);

it.effect("resolveWsl carries the kill switch into the distro", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-backend-config-test-",
});

const previousWslEnv = process.env.WSLENV;
const previousDisabled = process.env.OTEL_SDK_DISABLED;
try {
delete process.env.WSLENV;
process.env.OTEL_SDK_DISABLED = "true";

yield* Effect.gen(function* () {
const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
const config = yield* configuration.resolveWsl({ port: 5050, distro: null });

assert.equal(config.env.OTEL_SDK_DISABLED, "true");
assert.include((config.env.WSLENV ?? "").split(":"), "OTEL_SDK_DISABLED");
}).pipe(
Effect.provide(
DesktopBackendConfiguration.layer.pipe(
Layer.provideMerge(serverExposureLayer),
Layer.provideMerge(DesktopAppSettings.layerTest()),
Layer.provideMerge(DesktopWslServerTree.layerTest()),
Layer.provideMerge(
DesktopWslEnvironment.layerTest({
isAvailable: true,
windowsToWslPath: () => Option.some("/mnt/c/repo/apps/server/src/index.ts"),
getDistroIp: () => Option.some("172.27.0.99"),
}),
),
Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
),
),
);
} finally {
restoreEnv("WSLENV", previousWslEnv);
restoreEnv("OTEL_SDK_DISABLED", previousDisabled);
}
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);

it.effect("resolveWsl preserves existing WSLENV entries when forwarding backend secrets", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop/src/backend/DesktopBackendConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ const DESKTOP_BACKEND_ENV_NAMES = [
const WSL_FORWARDED_ENV_NAMES = [
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
// Otherwise the WSL server keeps exporting to endpoints from the bootstrap.
"T3CODE_OTEL_SDK_DISABLED",
"OTEL_SDK_DISABLED",
"T3CODE_OTLP_HEADERS",
"T3CODE_OTLP_PROTOCOL",
] as const;
Expand Down
Loading
Loading