From ff27b4ff5a60c1694b2f3e298ec0f4c315cf1512 Mon Sep 17 00:00:00 2001 From: eimexdev Date: Mon, 14 Sep 2026 20:55:08 -0700 Subject: [PATCH 1/2] fix: support native T3 executables during setup --- src/installer.ts | 4 +-- src/setup.ts | 17 ++++++++-- tests/setup.test.ts | 81 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 90 insertions(+), 12 deletions(-) diff --git a/src/installer.ts b/src/installer.ts index 5735641..3a36cad 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -147,9 +147,7 @@ async function checkT3Support(server: LocalT3): Promise { ); supported = stdout.trim() === "true"; } else - supported = readFileSync(server.cli, "utf8").includes( - "T3CODE_CODEX_LAUNCH_ARGS", - ); + supported = readFileSync(server.cli).includes("T3CODE_CODEX_LAUNCH_ARGS"); if (!supported) throw new Error( "This T3 build could not be verified to support Codex launch arguments. Update T3 before T3-only setup.", diff --git a/src/setup.ts b/src/setup.ts index 0001d69..6141e63 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -38,6 +38,7 @@ export type LocalT3 = { origin: string; node: string; cli: string; + native?: boolean; electron?: boolean; }; const renewalWindow = 24 * 60 * 60 * 1000; @@ -105,6 +106,17 @@ export function inspectLocal(baseDir: string): LocalT3 | undefined { // process has this home's database open before running its auth CLI. if (!hasOpenFile(state.pid, join(baseDir, "userdata/state.sqlite"))) return; + } else if (args[1] === "serve") { + // Native npm distributions run `t3 serve`, with no JS entrypoint. + // Verify the live executable against its platform package before using + // that same executable to issue or revoke credentials. + const pkg = JSON.parse( + readFileSync(join(dirname(executable), "package.json"), "utf8"), + ); + if (pkg.name !== `@t3code/t3-${process.platform}-${process.arch}`) return; + const binary = process.platform === "win32" ? "t3.exe" : "t3"; + if (executable !== join(dirname(executable), binary)) return; + cli = executable; } else { cli = realpathSync(resolve(cwd, args[1])); if (!cli.endsWith(join("dist", "bin.mjs"))) return; @@ -141,6 +153,7 @@ export function inspectLocal(baseDir: string): LocalT3 | undefined { node: executable, cli, ...(electron ? { electron: true } : {}), + ...(args[1] === "serve" ? { native: true } : {}), }; } catch { return; @@ -237,7 +250,7 @@ async function issue( await exec( server.node, [ - server.cli, + ...(server.native ? [] : [server.cli]), "auth", "session", "revoke", @@ -260,7 +273,7 @@ async function issue( const { stdout } = await exec( server.node, [ - server.cli, + ...(server.native ? [] : [server.cli]), "auth", "session", "issue", diff --git a/tests/setup.test.ts b/tests/setup.test.ts index b719e95..670caa3 100644 --- a/tests/setup.test.ts +++ b/tests/setup.test.ts @@ -29,7 +29,7 @@ import { Service } from "../src/service.js"; const exec = promisify(execFile); async function fixture( t: { after: (fn: () => Promise) => void }, - launch: "direct" | "absolute-link" | "relative-link" = "direct", + launch: "direct" | "absolute-link" | "relative-link" | "native" = "direct", ) { const root = mkdtempSync(join(tmpdir(), "t3poll setup café space-")); const base = join(root, "t3"); @@ -44,7 +44,7 @@ async function fixture( const cli = join(pkg, "dist/bin.mjs"); copyFileSync(resolve("tests/fixtures/local-t3.mjs"), cli); const link = join(root, "t3-bin"); - if (launch !== "direct") { + if (launch !== "direct" && launch !== "native") { if (process.platform === "win32") symlinkSync(pkg, link, "junction"); else symlinkSync(cli, link); } @@ -54,11 +54,34 @@ async function fixture( process.platform === "win32" && launch !== "direct" ? join(command, "dist", "bin.mjs") : command; - const child = spawn(process.execPath, [entry, "serve"], { - cwd: root, - env: { ...process.env, T3CODE_HOME: base }, - stdio: ["ignore", "pipe", "pipe"], - }); + let executable = process.execPath; + if (launch === "native") { + // A copied Node binary gives us a real process with the native `t3 serve` + // argv layout, without requiring a T3 download or compiler in CI. + executable = join(pkg, process.platform === "win32" ? "t3.exe" : "t3"); + copyFileSync(process.execPath, executable); + writeFileSync( + join(pkg, "package.json"), + JSON.stringify({ + name: `@t3code/t3-${process.platform}-${process.arch}`, + }), + ); + writeFileSync(join(root, "package.json"), '{"type":"module"}'); + copyFileSync(cli, join(root, "serve")); + writeFileSync( + join(root, "auth"), + 'process.argv.splice(1, 0, "auth");\n' + readFileSync(cli, "utf8"), + ); + } + const child = spawn( + executable, + launch === "native" ? ["serve"] : [entry, "serve"], + { + cwd: root, + env: { ...process.env, T3CODE_HOME: base }, + stdio: ["ignore", "pipe", "pipe"], + }, + ); t.after(async () => { if (child.exitCode === null) { const stopped = once(child, "exit"); @@ -431,3 +454,47 @@ test("installer restores configs when credential verification fails", async (t) codexBinary, ); }); + +test("native T3 discovery, issuance, renewal, and failed-verification cleanup", async (t) => { + const f = await fixture(t, "native"); + assert.equal(discover(f.config).origin, f.origin); + // The stand-in binary loads the auth fixture from its working directory. + const cwd = process.cwd(); + process.chdir(f.root); + try { + const c = await connection(f.config); + assert.deepEqual(await new T3(c.origin, c.tokenFile).threads(), []); + assert.equal(f.issued(), 1); + const path = `${c.tokenFile}.managed.json`; + const metadata = JSON.parse(readFileSync(path, "utf8")); + metadata.expiresAt = new Date(0).toISOString(); + writeFileSync(path, JSON.stringify(metadata)); + writeFileSync(join(f.base, "reject"), ""); + await assert.rejects(renewManaged(c.tokenFile, c.origin), /preserved/); + assert.equal( + readFileSync(join(f.base, "revoked"), "utf8").trim().split("\n").length, + 1, + ); + rmSync(join(f.base, "reject")); + await renewManaged(c.tokenFile, c.origin); + assert.equal(f.issued(), 3); + assert.deepEqual(await new T3(c.origin, c.tokenFile).threads(), []); + } finally { + process.chdir(cwd); + } +}); + +test("native discovery rejects unrelated packages and mismatched homes", async (t) => { + const f = await fixture(t, "native"); + const other = join(f.root, "other"); + mkdirSync(join(other, "userdata"), { recursive: true }); + copyFileSync( + join(f.base, "userdata/server-runtime.json"), + join(other, "userdata/server-runtime.json"), + ); + assert.equal(inspectLocal(other), undefined); + assert.ok(inspectLocal(f.base)); + writeFileSync(join(f.root, "package/package.json"), '{"name":"unrelated"}'); + assert.equal(inspectLocal(f.base), undefined); + assert.equal(f.issued(), 0); +}); From 730abc02d8793f54b388078beca973eb1e9152d5 Mon Sep 17 00:00:00 2001 From: eimexdev Date: Mon, 14 Sep 2026 21:00:26 -0700 Subject: [PATCH 2/2] fix: detect T3 independently of setup CPU architecture --- src/setup.ts | 8 +++++++- tests/setup.test.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/setup.ts b/src/setup.ts index 6141e63..e901415 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -113,7 +113,13 @@ export function inspectLocal(baseDir: string): LocalT3 | undefined { const pkg = JSON.parse( readFileSync(join(dirname(executable), "package.json"), "utf8"), ); - if (pkg.name !== `@t3code/t3-${process.platform}-${process.arch}`) return; + // Setup's Node may run under emulation while T3 uses the host CPU. + if ( + !["x64", "arm64"].some( + (arch) => pkg.name === `@t3code/t3-${process.platform}-${arch}`, + ) + ) + return; const binary = process.platform === "win32" ? "t3.exe" : "t3"; if (executable !== join(dirname(executable), binary)) return; cli = executable; diff --git a/tests/setup.test.ts b/tests/setup.test.ts index 670caa3..a250c53 100644 --- a/tests/setup.test.ts +++ b/tests/setup.test.ts @@ -498,3 +498,29 @@ test("native discovery rejects unrelated packages and mismatched homes", async ( assert.equal(inspectLocal(f.base), undefined); assert.equal(f.issued(), 0); }); + +test("native discovery accepts a supported package architecture independently of setup's Node architecture", async (t) => { + const f = await fixture(t, "native"); + const packagePath = join(f.root, "package/package.json"); + // The stand-in executable stays the same; only the target package identity + // changes, reproducing a setup runtime and T3 with different architectures. + for (const arch of ["x64", "arm64"]) { + writeFileSync( + packagePath, + JSON.stringify({ + name: `@t3code/t3-${process.platform}-${arch}`, + }), + ); + assert.equal(inspectLocal(f.base)?.native, true, `${arch} package`); + } + const otherPlatform = process.platform === "darwin" ? "linux" : "darwin"; + for (const name of [ + `@t3code/t3-${process.platform}-ia32`, + `@t3code/t3-${process.platform}-x64-extra`, + `@t3code/t3-${otherPlatform}-arm64`, + ]) { + writeFileSync(packagePath, JSON.stringify({ name })); + assert.equal(inspectLocal(f.base), undefined, name); + } + assert.equal(f.issued(), 0); +});