diff --git a/.github/scripts/thread-transfer-report.cjs b/.github/scripts/thread-transfer-report.cjs
deleted file mode 100644
index 94a02b7806dc..000000000000
--- a/.github/scripts/thread-transfer-report.cjs
+++ /dev/null
@@ -1,429 +0,0 @@
-const fs = require("node:fs");
-const path = require("node:path");
-
-const ARTIFACT_NAME = "thread-transfer-results";
-const RESULT_FILE = "thread-transfer-result.json";
-const COMMENT_MARKER = "";
-const PROVIDERS = ["codex", "claudeAgent"];
-const OBSERVED_KEYS = [
- "totalWireBytes",
- "threadSnapshotWireBytes",
- "threadSnapshotDecodedBytes",
- "measuredTurnWebSocketWireBytes",
- "measuredTurnWebSocketDecodedBytes",
- "measuredTurnWebSocketMessages",
-];
-const CEILING_KEYS = [
- "totalWireBytes",
- "threadSnapshotWireBytes",
- "measuredTurnWebSocketWireBytes",
- "measuredTurnWebSocketDecodedBytes",
- "measuredTurnWebSocketMessages",
-];
-const SCENARIO_KEYS = [
- "id",
- "historyTurns",
- "historyCommandToolsPerTurn",
- "historyMcpResultBytes",
- "measuredCommandTools",
- "measuredMcpResultBytes",
-];
-
-function resultShaMarker(sha) {
- return ``;
-}
-
-function assertObject(value, label) {
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
- throw new Error(`${label} must be an object`);
- }
-}
-
-function assertExactKeys(value, expected, label) {
- assertObject(value, label);
- const actual = Object.keys(value).sort();
- const wanted = [...expected].sort();
- if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
- throw new Error(`${label} has unexpected fields`);
- }
-}
-
-function assertMetric(value, label) {
- if (!Number.isSafeInteger(value) || value < 0 || value > 1_000_000_000) {
- throw new Error(`${label} must be a non-negative safe integer below 1,000,000,000`);
- }
-}
-
-function validateResult(value) {
- assertExactKeys(value, ["schemaVersion", "scenario", "providers"], "result");
- if (value.schemaVersion !== 1) {
- throw new Error("result.schemaVersion must be 1");
- }
-
- assertExactKeys(value.scenario, SCENARIO_KEYS, "result.scenario");
- if (value.scenario.id !== "thread-transfer-v1") {
- throw new Error("result.scenario.id is not supported");
- }
- for (const key of SCENARIO_KEYS.slice(1)) {
- assertMetric(value.scenario[key], `result.scenario.${key}`);
- }
-
- assertExactKeys(value.providers, PROVIDERS, "result.providers");
- for (const provider of PROVIDERS) {
- const entry = value.providers[provider];
- assertExactKeys(entry, ["observed", "ceiling"], `result.providers.${provider}`);
- assertExactKeys(entry.observed, OBSERVED_KEYS, `result.providers.${provider}.observed`);
- assertExactKeys(entry.ceiling, CEILING_KEYS, `result.providers.${provider}.ceiling`);
- for (const key of OBSERVED_KEYS) {
- assertMetric(entry.observed[key], `result.providers.${provider}.observed.${key}`);
- }
- for (const key of CEILING_KEYS) {
- assertMetric(entry.ceiling[key], `result.providers.${provider}.ceiling.${key}`);
- }
- }
-
- return value;
-}
-
-function readResult(directory) {
- if (!directory) return undefined;
- const file = path.join(directory, RESULT_FILE);
- if (!fs.existsSync(file)) return undefined;
- const stat = fs.lstatSync(file);
- if (!stat.isFile() || stat.size > 64 * 1_024) {
- throw new Error("thread transfer result must be a regular file smaller than 64 KiB");
- }
- return validateResult(JSON.parse(fs.readFileSync(file, "utf8")));
-}
-
-function formatBytes(bytes) {
- if (bytes < 1_024) return `${bytes} B`;
- if (bytes >= 1_024 * 1_024) return `${(bytes / 1_024 / 1_024).toFixed(2)} MiB`;
- return `${(bytes / 1_024).toFixed(1)} KiB`;
-}
-
-function formatValue(value, kind) {
- return kind === "messages" ? value.toLocaleString("en-US") : formatBytes(value);
-}
-
-function formatImpact(current, baseline, kind) {
- if (baseline === undefined) return "—";
- const delta = current - baseline;
- const prefix = delta > 0 ? "+" : delta < 0 ? "−" : "";
- const magnitude = formatValue(Math.abs(delta), kind);
- const percent =
- baseline === 0 ? "" : ` (${prefix}${Math.abs((delta / baseline) * 100).toFixed(1)}%)`;
- return `${prefix}${magnitude}${percent}`;
-}
-
-function sameScenario(left, right) {
- return SCENARIO_KEYS.every((key) => left[key] === right[key]);
-}
-
-const METRICS = [
- { key: "totalWireBytes", label: "Total thread wire", kind: "bytes" },
- { key: "threadSnapshotWireBytes", label: "Thread snapshot wire", kind: "bytes" },
- {
- key: "measuredTurnWebSocketWireBytes",
- label: "Live turn WebSocket wire",
- kind: "bytes",
- },
- {
- key: "measuredTurnWebSocketDecodedBytes",
- label: "Live turn WebSocket decoded",
- kind: "bytes",
- },
- { key: "measuredTurnWebSocketMessages", label: "Live turn messages", kind: "messages" },
-];
-
-function renderComment(input) {
- const current = input.current;
- const baseline = input.baseline;
- const comparable = baseline !== undefined && sameScenario(current.scenario, baseline.scenario);
- const rows = [];
- const ceilingChanges = [];
- let failed = false;
-
- for (const provider of PROVIDERS) {
- for (const metric of METRICS) {
- const observed = current.providers[provider].observed[metric.key];
- const ceiling = current.providers[provider].ceiling[metric.key];
- const baselineObserved = comparable
- ? baseline.providers[provider].observed[metric.key]
- : undefined;
- const pass = observed <= ceiling;
- failed ||= !pass;
- rows.push(
- `| ${provider === "codex" ? "Codex" : "Claude"} | ${metric.label} | ${baselineObserved === undefined ? "—" : formatValue(baselineObserved, metric.kind)} | ${formatValue(observed, metric.kind)} | ${formatImpact(observed, baselineObserved, metric.kind)} | ${formatValue(ceiling, metric.kind)} | ${pass ? "✅" : "❌"} |`,
- );
-
- if (baseline && baseline.providers[provider].ceiling[metric.key] !== ceiling) {
- ceilingChanges.push(
- `- ${provider === "codex" ? "Codex" : "Claude"} ${metric.label}: ${formatValue(baseline.providers[provider].ceiling[metric.key], metric.kind)} → ${formatValue(ceiling, metric.kind)}`,
- );
- }
- }
- }
-
- const baselineLink = input.baselineRun
- ? `[\`${input.baselineRun.sha.slice(0, 7)}\`](${input.baselineRun.url})`
- : "unavailable";
- const currentLink = `[\`${input.currentRun.sha.slice(0, 7)}\`](${input.currentRun.url})`;
- const notices = [];
- if (!baseline) {
- notices.push(
- "> ℹ️ No successful `main` baseline artifact is available yet. This run establishes the initial measurement.",
- );
- } else if (!comparable) {
- notices.push(
- "> ⚠️ The thread fixture changed, so impact percentages are not directly comparable to the `main` baseline.",
- );
- } else if (!input.baselineRun.matchesBase) {
- notices.push(
- "> ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful `main` measurement shown below.",
- );
- }
- if (ceilingChanges.length > 0) {
- notices.push(
- `> ⚠️ **This PR changes transfer ceilings:**\n>\n${ceilingChanges.map((line) => `> ${line}`).join("\n")}`,
- );
- }
-
- return [
- COMMENT_MARKER,
- resultShaMarker(input.currentRun.sha),
- "## Thread transfer impact",
- "",
- failed
- ? "❌ One or more thread transfer ceilings were exceeded."
- : "✅ Thread transfer remains within every enforced ceiling.",
- ...(notices.length > 0 ? ["", ...notices] : []),
- "",
- "| Provider | Metric | Main baseline | This PR | Impact | PR ceiling | |",
- "| --- | --- | ---: | ---: | ---: | ---: | --- |",
- ...rows,
- "",
- `Baseline: ${baselineLink} · PR result: ${currentLink} · Source CI: ${input.currentRun.conclusion}`,
- "",
- "",
- "Scenario and decoded snapshot size
",
- "",
- `${current.scenario.historyTurns} historical turns, ${current.scenario.historyCommandToolsPerTurn} command tools per turn, ${formatBytes(current.scenario.historyMcpResultBytes)} retained MCP result per historical turn, and a ${formatBytes(current.scenario.measuredMcpResultBytes)} retained result in the measured turn.`,
- "",
- ...PROVIDERS.map(
- (provider) =>
- `- ${provider === "codex" ? "Codex" : "Claude"} decoded thread snapshot: ${formatBytes(current.providers[provider].observed.threadSnapshotDecodedBytes)}`,
- ),
- "",
- " ",
- "",
- "_Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed._",
- ].join("\n");
-}
-
-async function artifactsForRun(github, owner, repo, runId) {
- return github.paginate(github.rest.actions.listWorkflowRunArtifacts, {
- owner,
- repo,
- run_id: runId,
- per_page: 100,
- });
-}
-
-function findResultArtifact(artifacts) {
- return artifacts.find((artifact) => artifact.name === ARTIFACT_NAME && !artifact.expired);
-}
-
-async function resolve({ github, context, core }) {
- const source = context.payload.workflow_run;
- const { owner, repo } = context.repo;
- if (source.event !== "pull_request") {
- core.setOutput("publish", "false");
- return;
- }
-
- let pullNumber = source.pull_requests?.[0]?.number;
- if (!pullNumber) {
- const associated = await github.paginate(
- github.rest.repos.listPullRequestsAssociatedWithCommit,
- { owner, repo, commit_sha: source.head_sha, per_page: 100 },
- );
- const matchingPulls = associated.filter(
- (pull) =>
- pull.state === "open" &&
- pull.head.sha === source.head_sha &&
- pull.head.ref === source.head_branch,
- );
- if (matchingPulls.length !== 1) {
- core.info(
- `Expected one open pull request for ${source.head_repository?.full_name ?? "unknown repository"}:${source.head_branch ?? "unknown branch"} at ${source.head_sha}; found ${matchingPulls.length}.`,
- );
- core.setOutput("publish", "false");
- return;
- }
- pullNumber = matchingPulls[0].number;
- }
- if (!pullNumber) {
- core.info("No open pull request is associated with the completed CI run.");
- core.setOutput("publish", "false");
- return;
- }
-
- const { data: pull } = await github.rest.pulls.get({ owner, repo, pull_number: pullNumber });
- if (pull.head.sha !== source.head_sha) {
- core.info(`Skipping stale CI result ${source.head_sha}; PR head is ${pull.head.sha}.`);
- core.setOutput("publish", "false");
- return;
- }
-
- const sourceArtifacts = await artifactsForRun(github, owner, repo, source.id);
- const sourceArtifact = findResultArtifact(sourceArtifacts);
- const workflowRuns = await github.paginate(github.rest.actions.listWorkflowRuns, {
- owner,
- repo,
- workflow_id: source.workflow_id,
- branch: pull.base.ref,
- event: "push",
- status: "success",
- per_page: 100,
- });
- const orderedRuns = [
- ...workflowRuns.filter((run) => run.head_sha === pull.base.sha),
- ...workflowRuns.filter((run) => run.head_sha !== pull.base.sha),
- ].slice(0, 20);
-
- let baselineRun;
- for (const run of orderedRuns) {
- const artifacts = await artifactsForRun(github, owner, repo, run.id);
- if (findResultArtifact(artifacts)) {
- baselineRun = run;
- break;
- }
- }
-
- core.setOutput("publish", "true");
- core.setOutput("pull_number", String(pullNumber));
- core.setOutput("pr_artifact", sourceArtifact ? "true" : "false");
- core.setOutput("pr_run_id", String(source.id));
- core.setOutput("pr_sha", source.head_sha);
- core.setOutput("pr_conclusion", source.conclusion ?? "unknown");
- core.setOutput("baseline_artifact", baselineRun ? "true" : "false");
- core.setOutput("baseline_run_id", baselineRun ? String(baselineRun.id) : "");
- core.setOutput("baseline_sha", baselineRun?.head_sha ?? "");
- core.setOutput(
- "baseline_matches_base",
- baselineRun?.head_sha === pull.base.sha ? "true" : "false",
- );
-}
-
-async function upsertComment(github, context, pullNumber, body, options = {}) {
- const { owner, repo } = context.repo;
- const comments = await github.paginate(github.rest.issues.listComments, {
- owner,
- repo,
- issue_number: pullNumber,
- per_page: 100,
- });
- const existing = comments.find(
- (comment) =>
- comment.user?.login === "github-actions[bot]" && comment.body?.includes(COMMENT_MARKER),
- );
- if (
- options.preserveResultSha &&
- existing?.body?.includes(resultShaMarker(options.preserveResultSha))
- ) {
- return;
- }
- if (existing) {
- await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
- } else {
- await github.rest.issues.createComment({ owner, repo, issue_number: pullNumber, body });
- }
-}
-
-async function upsertCommentForCurrentHead(
- github,
- context,
- core,
- pullNumber,
- expectedSha,
- body,
- options,
-) {
- const { owner, repo } = context.repo;
- const { data: pull } = await github.rest.pulls.get({
- owner,
- repo,
- pull_number: pullNumber,
- });
- if (pull.head.sha !== expectedSha) {
- core.info(`Skipping stale CI result ${expectedSha}; PR head is ${pull.head.sha}.`);
- return false;
- }
-
- await upsertComment(github, context, pullNumber, body, options);
- return true;
-}
-
-async function publish({ github, context, core }) {
- const pullNumber = Number(process.env.PR_NUMBER);
- if (!Number.isSafeInteger(pullNumber) || pullNumber <= 0) {
- throw new Error("PR_NUMBER is invalid");
- }
-
- const current = readResult(process.env.PR_RESULT_DIR);
- const currentRun = {
- sha: process.env.PR_SHA,
- conclusion: process.env.PR_CONCLUSION,
- url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.PR_RUN_ID}`,
- };
- if (!current) {
- await upsertCommentForCurrentHead(
- github,
- context,
- core,
- pullNumber,
- currentRun.sha,
- [
- COMMENT_MARKER,
- "## Thread transfer impact",
- "",
- `⚠️ The latest [CI run](${currentRun.url}) did not produce a thread transfer result for \`${currentRun.sha.slice(0, 7)}\`.`,
- "",
- "_This comment will update automatically after the next completed run._",
- ].join("\n"),
- { preserveResultSha: currentRun.sha },
- );
- return;
- }
-
- const baseline = readResult(process.env.BASELINE_RESULT_DIR);
- const baselineRun = baseline
- ? {
- sha: process.env.BASELINE_SHA,
- matchesBase: process.env.BASELINE_MATCHES_BASE === "true",
- url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.BASELINE_RUN_ID}`,
- }
- : undefined;
- const body = renderComment({ current, baseline, currentRun, baselineRun });
- const published = await upsertCommentForCurrentHead(
- github,
- context,
- core,
- pullNumber,
- currentRun.sha,
- body,
- );
- if (published) {
- core.info(`Updated thread transfer report on PR #${pullNumber}.`);
- }
-}
-
-module.exports = {
- publish,
- readResult,
- renderComment,
- resolve,
- upsertCommentForCurrentHead,
- validateResult,
-};
diff --git a/.github/scripts/thread-transfer-report.test.cjs b/.github/scripts/thread-transfer-report.test.cjs
deleted file mode 100644
index 4935864e46f0..000000000000
--- a/.github/scripts/thread-transfer-report.test.cjs
+++ /dev/null
@@ -1,292 +0,0 @@
-const assert = require("node:assert/strict");
-const test = require("node:test");
-
-const {
- renderComment,
- resolve,
- upsertCommentForCurrentHead,
- validateResult,
-} = require("./thread-transfer-report.cjs");
-
-function result(overrides = {}) {
- const observed = {
- totalWireBytes: 2_200_000,
- threadSnapshotWireBytes: 1_950_000,
- threadSnapshotDecodedBytes: 9_100_000,
- measuredTurnWebSocketWireBytes: 250_000,
- measuredTurnWebSocketDecodedBytes: 1_150_000,
- measuredTurnWebSocketMessages: 15,
- };
- const ceiling = {
- totalWireBytes: 2_900_000,
- threadSnapshotWireBytes: 2_600_000,
- measuredTurnWebSocketWireBytes: 320_000,
- measuredTurnWebSocketDecodedBytes: 1_550_000,
- measuredTurnWebSocketMessages: 20,
- };
- return {
- schemaVersion: 1,
- scenario: {
- id: "thread-transfer-v1",
- historyTurns: 10,
- historyCommandToolsPerTurn: 5,
- historyMcpResultBytes: 900_000,
- measuredCommandTools: 20,
- measuredMcpResultBytes: 1_100_000,
- },
- providers: {
- codex: { observed: { ...observed, ...overrides }, ceiling },
- claudeAgent: { observed, ceiling },
- },
- };
-}
-
-test("validates the fixed artifact schema", () => {
- assert.equal(validateResult(result()).schemaVersion, 1);
- assert.throws(
- () => validateResult({ ...result(), injectedMarkdown: "@everyone" }),
- /unexpected fields/,
- );
- assert.throws(
- () => validateResult(result({ totalWireBytes: "lots" })),
- /non-negative safe integer/,
- );
-});
-
-test("renders baseline, impact, ceiling, and ceiling changes", () => {
- const baseline = result();
- const current = result({ measuredTurnWebSocketWireBytes: 260_000 });
- current.providers.codex.ceiling = {
- ...current.providers.codex.ceiling,
- measuredTurnWebSocketWireBytes: 330_000,
- };
- const comment = renderComment({
- current,
- baseline,
- currentRun: {
- sha: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
- conclusion: "success",
- url: "https://github.com/pingdotgg/t3code/actions/runs/2",
- },
- baselineRun: {
- sha: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
- matchesBase: true,
- url: "https://github.com/pingdotgg/t3code/actions/runs/1",
- },
- });
-
- assert.match(comment, /Main baseline \| This PR \| Impact \| PR ceiling/);
- assert.match(comment, /\+9\.8 KiB \(\+4\.0%\)/);
- assert.match(comment, /This PR changes transfer ceilings/);
- assert.match(comment, /312\.5 KiB → 322\.3 KiB/);
- assert.match(comment, //);
- assert.match(
- comment,
- //,
- );
-});
-
-test("resolves a fallback PR with a redacted head repo and exact main baseline", async () => {
- const outputs = {};
- const listWorkflowRunArtifacts = () => {};
- const listWorkflowRuns = () => {};
- const listPullRequestsAssociatedWithCommit = () => {};
- const github = {
- paginate: async (method, input) => {
- if (method === listPullRequestsAssociatedWithCommit) {
- return [
- {
- number: 5350,
- state: "open",
- head: { sha: "head-sha", ref: "feature-branch", repo: null },
- },
- ];
- }
- if (method === listWorkflowRunArtifacts) {
- return [
- {
- name: "thread-transfer-results",
- expired: false,
- runId: input.run_id,
- },
- ];
- }
- if (method === listWorkflowRuns) {
- return [{ id: 1, head_sha: "base-sha" }];
- }
- throw new Error("unexpected pagination call");
- },
- rest: {
- actions: { listWorkflowRunArtifacts, listWorkflowRuns },
- pulls: {
- get: async () => ({
- data: {
- head: { sha: "head-sha" },
- base: { sha: "base-sha", ref: "main" },
- },
- }),
- },
- repos: { listPullRequestsAssociatedWithCommit },
- },
- };
- await resolve({
- github,
- context: {
- repo: { owner: "pingdotgg", repo: "t3code" },
- payload: {
- workflow_run: {
- id: 2,
- event: "pull_request",
- workflow_id: 3,
- head_sha: "head-sha",
- head_branch: "feature-branch",
- head_repository: { full_name: "pingdotgg/t3code" },
- conclusion: "success",
- pull_requests: [],
- },
- },
- },
- core: {
- info: () => {},
- setOutput: (key, value) => {
- outputs[key] = value;
- },
- },
- });
-
- assert.equal(outputs.publish, "true");
- assert.equal(outputs.pull_number, "5350");
- assert.equal(outputs.pr_artifact, "true");
- assert.equal(outputs.baseline_run_id, "1");
- assert.equal(outputs.baseline_matches_base, "true");
-});
-
-test("does not guess when a fallback commit belongs to multiple PRs", async () => {
- const outputs = {};
- const listPullRequestsAssociatedWithCommit = () => {};
- let fetchedPull = false;
- await resolve({
- github: {
- paginate: async (method) => {
- assert.equal(method, listPullRequestsAssociatedWithCommit);
- return [5350, 5351].map((number) => ({
- number,
- state: "open",
- head: {
- sha: "head-sha",
- ref: "feature-branch",
- repo: { full_name: "pingdotgg/t3code" },
- },
- }));
- },
- rest: {
- actions: {},
- pulls: {
- get: async () => {
- fetchedPull = true;
- },
- },
- repos: { listPullRequestsAssociatedWithCommit },
- },
- },
- context: {
- repo: { owner: "pingdotgg", repo: "t3code" },
- payload: {
- workflow_run: {
- id: 2,
- event: "pull_request",
- workflow_id: 3,
- head_sha: "head-sha",
- head_branch: "feature-branch",
- head_repository: { full_name: "pingdotgg/t3code" },
- conclusion: "success",
- pull_requests: [],
- },
- },
- },
- core: {
- info: () => {},
- setOutput: (key, value) => {
- outputs[key] = value;
- },
- },
- });
-
- assert.equal(outputs.publish, "false");
- assert.equal(fetchedPull, false);
-});
-
-test("does not publish a stale result after the PR head advances", async () => {
- let listedComments = false;
- const info = [];
- const published = await upsertCommentForCurrentHead(
- {
- paginate: async () => {
- listedComments = true;
- return [];
- },
- rest: {
- issues: {
- listComments: () => {},
- createComment: () => {
- throw new Error("must not create a stale comment");
- },
- updateComment: () => {
- throw new Error("must not update a stale comment");
- },
- },
- pulls: {
- get: async () => ({ data: { head: { sha: "new-head-sha" } } }),
- },
- },
- },
- { repo: { owner: "pingdotgg", repo: "t3code" } },
- { info: (message) => info.push(message) },
- 5350,
- "old-head-sha",
- "stale body",
- );
-
- assert.equal(published, false);
- assert.equal(listedComments, false);
- assert.deepEqual(info, ["Skipping stale CI result old-head-sha; PR head is new-head-sha."]);
-});
-
-test("preserves a successful result when a same-SHA rerun has no artifact", async () => {
- let updatedComment = false;
- const sha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
- const published = await upsertCommentForCurrentHead(
- {
- paginate: async () => [
- {
- id: 1,
- user: { login: "github-actions[bot]" },
- body: `\n`,
- },
- ],
- rest: {
- issues: {
- listComments: () => {},
- createComment: () => {
- updatedComment = true;
- },
- updateComment: () => {
- updatedComment = true;
- },
- },
- pulls: {
- get: async () => ({ data: { head: { sha } } }),
- },
- },
- },
- { repo: { owner: "pingdotgg", repo: "t3code" } },
- { info: () => {} },
- 5350,
- sha,
- "missing artifact warning",
- { preserveResultSha: sha },
- );
-
- assert.equal(published, true);
- assert.equal(updatedComment, false);
-});
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 570abd7d0505..019440d0c5b3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -75,6 +75,16 @@ jobs:
!/.repos/
sparse-checkout-cone-mode: false
+ # Blacksmith boots GitHub's Ubuntu runner image (gcc is usually present),
+ # but ACP process-tree live tests compile a small pthread fixture with `cc`
+ # and soft-skip when it is missing. Install build-essential so that path
+ # always runs in CI instead of silently no-oping.
+ - name: Install C toolchain for process-tree fixtures
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends build-essential
+ command -v cc
+
- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
diff --git a/.github/workflows/thread-transfer-report.yml b/.github/workflows/thread-transfer-report.yml
deleted file mode 100644
index 23eec72923bd..000000000000
--- a/.github/workflows/thread-transfer-report.yml
+++ /dev/null
@@ -1,75 +0,0 @@
-name: Thread Transfer Report
-
-on:
- workflow_run:
- workflows: [CI]
- types: [completed]
-
-permissions:
- actions: read
- contents: read
- pull-requests: write
-
-jobs:
- publish:
- name: Publish PR comment
- if: github.event.workflow_run.event == 'pull_request'
- runs-on: ubuntu-24.04
- concurrency:
- group: thread-transfer-report-${{ github.event.workflow_run.pull_requests[0].number || github.event.workflow_run.id }}
- cancel-in-progress: true
- steps:
- # workflow_run has a write-capable token even for fork PRs. Only load the
- # publisher from the trusted default branch and never execute PR code.
- - name: Checkout trusted publisher
- uses: actions/checkout@v6
- with:
- ref: ${{ github.event.repository.default_branch }}
- sparse-checkout: .github/scripts
-
- - name: Test trusted publisher
- run: node --test .github/scripts/thread-transfer-report.test.cjs
-
- - id: resolve
- name: Resolve PR and baseline artifacts
- uses: actions/github-script@v8
- with:
- script: |
- const reporter = require("./.github/scripts/thread-transfer-report.cjs");
- await reporter.resolve({ github, context, core });
-
- - name: Download PR result
- if: steps.resolve.outputs.publish == 'true' && steps.resolve.outputs.pr_artifact == 'true'
- uses: actions/download-artifact@v8
- with:
- name: thread-transfer-results
- path: ${{ runner.temp }}/thread-transfer/pr
- github-token: ${{ secrets.GITHUB_TOKEN }}
- run-id: ${{ steps.resolve.outputs.pr_run_id }}
-
- - name: Download main baseline
- if: steps.resolve.outputs.publish == 'true' && steps.resolve.outputs.baseline_artifact == 'true'
- uses: actions/download-artifact@v8
- with:
- name: thread-transfer-results
- path: ${{ runner.temp }}/thread-transfer/main
- github-token: ${{ secrets.GITHUB_TOKEN }}
- run-id: ${{ steps.resolve.outputs.baseline_run_id }}
-
- - name: Update thread transfer comment
- if: steps.resolve.outputs.publish == 'true'
- uses: actions/github-script@v8
- env:
- PR_NUMBER: ${{ steps.resolve.outputs.pull_number }}
- PR_SHA: ${{ steps.resolve.outputs.pr_sha }}
- PR_CONCLUSION: ${{ steps.resolve.outputs.pr_conclusion }}
- PR_RUN_ID: ${{ steps.resolve.outputs.pr_run_id }}
- PR_RESULT_DIR: ${{ runner.temp }}/thread-transfer/pr
- BASELINE_SHA: ${{ steps.resolve.outputs.baseline_sha }}
- BASELINE_MATCHES_BASE: ${{ steps.resolve.outputs.baseline_matches_base }}
- BASELINE_RUN_ID: ${{ steps.resolve.outputs.baseline_run_id }}
- BASELINE_RESULT_DIR: ${{ runner.temp }}/thread-transfer/main
- with:
- script: |
- const reporter = require("./.github/scripts/thread-transfer-report.cjs");
- await reporter.publish({ github, context, core });
diff --git a/README.md b/README.md
index 8ec101387f67..cf90fdf7ef54 100644
--- a/README.md
+++ b/README.md
@@ -79,6 +79,7 @@ Full docs live in [docs/](./docs). There's no docs site yet.
- [Permission modes](./docs/user/permission-modes.md)
- [Keyboard shortcuts](./docs/user/keybindings.md)
- [Customize a project icon](./docs/user/project-settings.md)
+- [Appearance preferences](./docs/user/appearance.md)
- [Remote access from a phone or another machine](./docs/user/remote-access.md)
- [Keeping app and server in sync](./docs/user/updating.md)
- [Source control integrations](./docs/user/source-control.md)
diff --git a/apps/desktop/src/app/DesktopEnvironment.test.ts b/apps/desktop/src/app/DesktopEnvironment.test.ts
index 218e2c3e4ba2..ee12b37b8c8b 100644
--- a/apps/desktop/src/app/DesktopEnvironment.test.ts
+++ b/apps/desktop/src/app/DesktopEnvironment.test.ts
@@ -96,6 +96,8 @@ describe("DesktopEnvironment", () => {
assert.equal(environment.logDir, "/tmp/t3/userdata/logs");
assert.equal(environment.browserArtifactsDir, "/tmp/t3/userdata/browser-artifacts");
assert.equal(environment.serverSettingsPath, "/tmp/t3/userdata/settings.json");
+ assert.equal(environment.userDataDirName, "t3code");
+ assert.equal(environment.legacyUserDataDirName, "T3 Code (Alpha)");
}),
);
diff --git a/apps/desktop/src/backend/tailscaleEndpointProvider.ts b/apps/desktop/src/backend/tailscaleEndpointProvider.ts
index 0b48adc308c3..4f53a7e6f54a 100644
--- a/apps/desktop/src/backend/tailscaleEndpointProvider.ts
+++ b/apps/desktop/src/backend/tailscaleEndpointProvider.ts
@@ -121,7 +121,7 @@ export const resolveTailscaleAdvertisedEndpoints = Effect.fn("resolveTailscaleAd
input.readMagicDnsName ??
readTailscaleStatus.pipe(
Effect.map((status) => status.magicDnsName),
- Effect.orElseSucceed(() => null),
+ Effect.orElseSucceed((): string | null => null),
);
const dnsName =
input.statusJson === undefined
diff --git a/apps/desktop/src/settings/DesktopClientSettings.test.ts b/apps/desktop/src/settings/DesktopClientSettings.test.ts
index e32a5e2d0807..850300cb3496 100644
--- a/apps/desktop/src/settings/DesktopClientSettings.test.ts
+++ b/apps/desktop/src/settings/DesktopClientSettings.test.ts
@@ -38,6 +38,7 @@ const clientSettings: ClientSettings = {
glassOpacity: 80,
planModeEnabled: false,
showSkillsInSlashMenu: false,
+ persistComposerContextStrip: true,
providerModelPreferences: {},
sidebarAutoSettleAfterDays: 3,
sidebarAutoSettleOnMerge: true,
diff --git a/apps/marketing/src/pages/index.astro b/apps/marketing/src/pages/index.astro
index e45cb7602873..274ffc3d876b 100644
--- a/apps/marketing/src/pages/index.astro
+++ b/apps/marketing/src/pages/index.astro
@@ -218,7 +218,7 @@ const mobileEndorsementRows = [