| author | |
| committer | |
| log | a1762fdd7482728800f1f4fd260c632f01a30b1f |
| tree | 1c2f62787e9b03af39083b2e508f0502f3b3c5fc |
| parent | 388b43a27cc37ae067087073825633d4fc5de3b7 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>4 files changed, 38 insertions(+), 17 deletions(-)
.agents/skills/test-t3-app/SKILL.md+4-15| ... | ... | @@ -40,7 +40,7 @@ Treat the overall testing or implementation loop—not an assistant turn or one |
| 40 | 40 | - Do not stop the server merely because one verification pass completed or because you are yielding a response to the user. |
| 41 | 41 | - Before starting another environment, check whether the existing process and browser tab still serve the task. Reuse them when healthy instead of discarding useful state. |
| 42 | 42 | - On a later turn, verify that the existing process is alive and reuse its printed ports and base directory. If it exited, restart with the same base directory; create a new pairing token only when the browser session is no longer valid. |
| 43 | - Tell the user when a test environment remains available, including its non-secret web URL when useful. Never include a pairing token. | |
| 43 | - Tell the user when a test environment remains available, including its non-secret web URL when useful. Include a pairing token only when the user still needs to pair (see below). | |
| 44 | 44 | |
| 45 | 45 | ## Authenticate the browser on the first navigation |
| 46 | 46 | |
| ... | ... | @@ -50,24 +50,13 @@ Treat the overall testing or implementation loop—not an assistant turn or one |
| 50 | 50 | 4. Wait for the pairing exchange and redirect to finish before navigating elsewhere. |
| 51 | 51 | 5. Continue in the same browser context so its stored bearer session remains available. |
| 52 | 52 | |
| 53 | Treat pairing URLs as secrets. Do not copy them into final responses, screenshots, committed files, or durable logs. A pairing token is short-lived and single-use; opening the URL in another browser or opening it twice can consume it. | |
| 53 | Keep pairing URLs out of screenshots, committed files, and durable logs. When the user asked for a shared environment, the deliverable IS the full pairing URL — paste it in your reply, token and all; a bare origin is useless to them. A pairing token is short-lived and single-use; opening the URL in another browser or opening it twice can consume it, so never open a URL you handed to the user. | |
| 54 | 54 | |
| 55 | 55 | ## Recover a consumed or expired pairing token |
| 56 | 56 | |
| 57 | Create another token against the same database and web URL as the running dev server: | |
| 57 | Run `node apps/server/src/bin.ts pair` from the repository root. It discovers the running dev server (worktree `.t3` first, same precedence as the dev runner) and prints a fresh `Pair URL` against the server's current web origin, including a `--share` tailnet origin. Pass `--base-dir <base-dir>` only when the server was started with `--home-dir`, using the identical path. | |
| 58 | 58 | |
| 59 | ```bash | |
| 60 | T3CODE_PORT=<server-port> node apps/server/src/bin.ts auth pairing create \ | |
| 61 | --base-dir <base-dir> \ | |
| 62 | --dev-url <web-url> \ | |
| 63 | --base-url <web-url> \ | |
| 64 | --ttl 15m \ | |
| 65 | --label agent-ui-test | |
| 66 | ``` | |
| 67 | ||
| 68 | Use the `Pair URL` from this command once. Derive `<server-port>` and `<web-url>` from the current dev-runner output, including any automatically selected port offset. Setting `T3CODE_PORT` keeps the administrative CLI from probing for an unrelated free port. | |
| 69 | ||
| 70 | Always pass `--dev-url` for a dev-runner environment so the generated pairing URL uses the current web origin. An explicit base directory stores runtime state in `<base-dir>/userdata`; the `<base-dir>/dev` fallback is only used by an implicit dev home. A worktree-local `.t3` counts as explicit, so its state lives in `<worktree>/.t3/userdata`. Use `auth pairing list` to inspect active token metadata; it intentionally cannot reveal token secrets. | |
| 59 | Tokens from `pair` carry standard client scopes. The startup pairing URL carries admin scopes; if the user needs Settings → Connections management (`access:write`), restart the server and hand over the new startup URL instead. | |
| 71 | 60 | |
| 72 | 61 | ## Inspect or seed SQLite state |
| 73 | 62 |
AGENTS.md+2-2| ... | ... | @@ -79,8 +79,8 @@ The most common defect in this repo is a change that works on the path you teste |
| 79 | 79 | - `vp i` installs. Worktrees get this from the t3.json setup script; if module resolution looks broken, it probably did not run. |
| 80 | 80 | - `vp run dev` starts server and web. In a worktree, state defaults to that worktree's gitignored `.t3`, which deliberately outranks an ambient `T3CODE_HOME` so you cannot land on shared state by accident. An explicit `--home-dir` still wins. |
| 81 | 81 | - Ports derive from the worktree path and are stable across restarts, but read the real ones from the `[dev-runner]` line since occupied ports shift. |
| 82 | - `--share` publishes over the tailnet. Do not open the URL when you use this, just send it to the user with the pairing code included in url | |
| 83 | - The web app requires pairing. Hand over the pairing URL, not the bare origin. A URL without its token is useless to whoever you gave it to. | |
| 82 | - Sharing over the tailnet is three steps: run `vp run dev --share` in the background, wait for the `pairingUrl:` line in its output, paste that full URL (token included) in your reply. Do not wire up `tailscale serve` by hand for this, and do not open the URL yourself. | |
| 83 | - The web app requires pairing. Hand over the pairing URL, not the bare origin. A URL without its token is useless to whoever you gave it to. If the token got consumed, mint a fresh one with `node apps/server/src/bin.ts pair` — note it carries standard scopes, while the startup URL carries admin scopes (needed for Settings → Connections management). | |
| 84 | 84 | - Stop what you started, by the PID you tracked. See rule 1. |
| 85 | 85 | |
| 86 | 86 | ## Test data |
scripts/dev-runner.test.ts+24| ... | ... | @@ -227,6 +227,30 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { |
| 227 | 227 | }), |
| 228 | 228 | ); |
| 229 | 229 | |
| 230 | it.effect("strips inherited service-launcher context", () => | |
| 231 | Effect.gen(function* () { | |
| 232 | const env = yield* createDevRunnerEnv({ | |
| 233 | mode: "dev", | |
| 234 | baseEnv: { | |
| 235 | T3_SERVICE_LAUNCHER_CONTEXT: '{"childVersion":"9.9.9"}', | |
| 236 | T3_BOOT_SERVICE_UNIT: "t3code.service", | |
| 237 | }, | |
| 238 | serverOffset: 0, | |
| 239 | webOffset: 0, | |
| 240 | t3Home: undefined, | |
| 241 | browser: undefined, | |
| 242 | autoBootstrapProjectFromCwd: undefined, | |
| 243 | logWebSocketEvents: undefined, | |
| 244 | host: undefined, | |
| 245 | port: undefined, | |
| 246 | devUrl: undefined, | |
| 247 | }); | |
| 248 | ||
| 249 | assert.equal(env.T3_SERVICE_LAUNCHER_CONTEXT, undefined); | |
| 250 | assert.equal(env.T3_BOOT_SERVICE_UNIT, undefined); | |
| 251 | }), | |
| 252 | ); | |
| 253 | ||
| 230 | 254 | it.effect("does not force websocket logging on in dev mode when unset", () => |
| 231 | 255 | Effect.gen(function* () { |
| 232 | 256 | const env = yield* createDevRunnerEnv({ |
scripts/dev-runner.ts+8| ... | ... | @@ -340,6 +340,14 @@ export function createDevRunnerEnv({ |
| 340 | 340 | delete output.T3CODE_HOME; |
| 341 | 341 | } |
| 342 | 342 | |
| 343 | // A dev-runner server is never launcher-managed. When the shell that runs | |
| 344 | // this script was itself spawned by the machine's managed t3 service (an | |
| 345 | // agent working inside T3 Code), these leak through and the child server | |
| 346 | // fails startup with "The service launcher started a different t3 version" | |
| 347 | // (serviceLauncherClient.ts resolveStartup). | |
| 348 | delete output.T3_SERVICE_LAUNCHER_CONTEXT; | |
| 349 | delete output.T3_BOOT_SERVICE_UNIT; | |
| 350 | ||
| 343 | 351 | if (!isDesktopMode) { |
| 344 | 352 | output.T3CODE_PORT = String(serverPort); |
| 345 | 353 | // HOST is Vite's own bind address, and the desktop branch below is the |