Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# fork-owned — see .fork/customizations.yaml#t3-connect-official-config
#
# Official T3 Connect public configuration, identical to what upstream bakes
# into every released artifact (read from the published npm CLI, t3@0.0.30).
# These are public identifiers, not secrets — see docs/cloud/t3-connect-clerk.md.
# Tracked on purpose (force-added past .gitignore's .env* rule) so every clone

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocker: tracking repo-root .env fights upstream’s contract for this path. .gitignore ignores it on purpose, docs treat it as optional local config, and infra/relay/scripts/deploy.ts (reconcileRootEnvPublicConfig) mutates it — rewriting T3CODE_RELAY_URL and appending T3CODE_MOBILE_OTLP_* / T3CODE_RELAY_CLIENT_OTLP_* keys (including ingest tokens). A force-tracked “exactly four keys” pin means a normal relay deploy dirties git, fails the guard, and risks committing stage URLs/tokens.

code judo: put these four values in a fork-owned file (e.g. .fork/t3-connect.public.env) and add a small fenced lowest-precedence read in scripts/lib/public-config.ts. Keep .env / .env.local as the override/scratchpad layer deploy already owns. Avoiding a Tier-4 loader hunk is not worth co-opting the mutable scratchpad.

# and worktree of this fork builds at T3 Connect parity with official releases.
#
# Personal overrides belong in .env.local, which stays gitignored and wins —
# by REPLACING a value, not blanking it: the loader treats empty as unset and
# falls through to this file. To build with T3 Connect off, temporarily delete
# this file in your checkout (do not commit the deletion).
# Never add server-side secrets (CLERK_SECRET_KEY, API tokens) to this file.
T3CODE_CLERK_PUBLISHABLE_KEY=pk_live_Y2xlcmsudDMuY29kZXMk
T3CODE_CLERK_JWT_TEMPLATE=t3-relay
T3CODE_CLERK_CLI_OAUTH_CLIENT_ID=hzxSgY2cH10sDU2r
T3CODE_RELAY_URL=https://relay.t3.codes
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# fork:begin t3-connect-official-config — see .fork/customizations.yaml#t3-connect-official-config
# FORK NOTE: unlike upstream, this repository TRACKS a repo-root .env carrying
# the official T3 Connect public values, so builds are configured by default.
# Do not `cp .env.example .env` — that clobbers the tracked file. Put personal
# overrides in .env.local instead (gitignored, higher precedence). Overrides
# REPLACE values; a blank value falls through to the tracked .env. To build
# with T3 Connect off entirely, temporarily delete the tracked .env locally.
# fork:end t3-connect-official-config
# Optional: T3 Connect source builds
# Leave these unset to disable optional T3 Connect features in local source builds.
# Release builds inject their public values at build time. Do not add server-side
Expand Down
98 changes: 85 additions & 13 deletions .fork/customizations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -929,22 +929,36 @@
the app is ready. Fast machines win that race; CI runners lose it
deterministically, and a cold local boot is the same dice roll (the
v0.1.2 launch isolation gate caught it: the app exited before its server
ever started). Fork builds bake no Clerk publishable key, so the bridge
is guaranteed dead weight — DesktopClerk skips it outright when
desktopClerkFrontendApiHostname is undefined, logging a warning, and
main.ts registers both schemes' privileges synchronously at module load
(guaranteed to precede "ready"; the sole registrar on the skip path;
observed against the bundled Electron 41 that pre-ready re-registration
by a keyed build's bridge replaces the list harmlessly). Keyed builds
keep upstream's behavior exactly: bridge created, initialization and
cleanup failures both loud — the cleanup path's Effect.orDie is
deliberately untouched, because a real Clerk defect in a keyed build
must not hide behind a warning. The singleton-lock behavior in
DesktopClerk.configure is bridge-independent and preserved on both
paths.
ever started). A build with no baked Clerk publishable key has a bridge
that is guaranteed dead weight — historically every fork build; since
t3-connect-official-config, only a checkout without the tracked .env or
one that blanks the key via .env.local. DesktopClerk skips it outright
when desktopClerkFrontendApiHostname is undefined, logging a warning,
and main.ts registers both schemes' privileges synchronously at module
load (guaranteed to precede "ready") and is the sole registrar on every
path: createDesktopClerkBridge no-ops the registrar for the duration of
the createClerkBridge call and restores it after, because the bridge
would re-register the identical privilege set and that call throws
post-"ready". The suppression is load-bearing, not hygiene — packaged
fork builds are keyed since t3-connect-official-config, and the v0.1.7
dry run's launch isolation gate caught the keyed build losing the ready
race and dying on the bridge's own registration (an earlier observation
that pre-ready re-registration is harmless was true but answered the
wrong case). Keyed builds otherwise keep upstream's behavior: bridge
created, initialization and cleanup failures both loud — the cleanup
path's Effect.orDie is deliberately untouched, because a real Clerk
defect in a keyed build must not hide behind a warning. Keyed bridge
behavior stays unit-covered by upstream's DesktopClerk.test.ts, whose
fenced hunk bakes a key in before import; the registrar suppression by
DesktopClerkForkRegistrarSuppression.test.ts; the keyless skip by
DesktopClerkForkSkip.test.ts; and the packaged boot itself by
fork-release.yml's launch isolation gate on every build, dry runs
included. The singleton-lock behavior in DesktopClerk.configure is
bridge-independent and preserved on both paths.
tier: 4
files:
- apps/desktop/src/app/DesktopClerkForkSkip.test.ts
- apps/desktop/src/app/DesktopClerkForkRegistrarSuppression.test.ts
shadows: []
watch:
- apps/desktop/src/main.ts
Expand Down Expand Up @@ -1216,3 +1230,61 @@
- apps/web/src/components/ComposerPromptEditor.tsx
verify:
- apps/web/src/__fork_guards__/forkComposerShell.test.ts

- id: t3-connect-official-config
intent: >
Fork builds carry the same T3 Connect public configuration as official
releases, so the cloud UI, the `t3 connect` CLI group, and the managed
tunnel all work against upstream's hosted relay out of the box. Upstream
injects four public values at release time from CI variables this fork
does not have (release.yml's relay_public_config job); the fork instead
tracks them in a repository-root .env, which scripts/lib/public-config.ts
already reads for every dev run and build — with the right precedence
built in, since process env and .env.local both outrank it, so a personal
or self-hosted-relay override stays possible and stays gitignored. An
override REPLACES a value; it cannot blank one — the loader resolves
through firstNonEmpty, which treats empty as unset and falls through to
the tracked file. Building with T3 Connect off means temporarily
deleting the tracked .env in that checkout, never committing it. The
file is force-added past .gitignore's .env* rule on purpose: tracked
files are exempt from ignore rules, and upstream never ships a .env, so
a sync can never conflict with it. The values are public identifiers,
not secrets (docs/cloud/t3-connect-clerk.md) — the same ones baked into
every official artifact; they were read from the published npm CLI
(t3@0.0.30). The .env must never grow server-side secrets or tokens; the
guard pins its key set to exactly these four so secret creep fails CI.
Two neighbouring customizations make keyed fork builds safe rather than
accidental: fork-clerk-launch-resilience's keyless skip only engages
when no publishable key is baked and keyed builds keep upstream's Clerk
bridge behavior exactly, and fork-app-identity deliberately keeps the
shared t3code:// scheme, which is the desktop OAuth callback Clerk's
redirect allowlist expects. Relay access itself remains gated by the
user's own Clerk account, which is what authorizes use of the hosted
service.
tier: 1
files:
- .env

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

important: files: [.env] encodes the wrong ownership boundary. Manifest this as a fork-owned path under .fork/, with watch: [scripts/lib/public-config.ts] only for the small fenced read — not for “we force-added a gitignored upstream scratchpad.” Intent prose should also name the reconcileRootEnvPublicConfig collision so the next sync reviewer does not re-learn it.

shadows: []
watch:
# Upstream's loader: the one place the repo-root .env is read and
# projected into VITE_*/EXPO_PUBLIC_* aliases. If upstream renames the
# canonical variables or stops reading .env, this customization dies
# silently — the guard exercises the loader against the real repo root
# so that failure turns CI red instead.
- scripts/lib/public-config.ts
# Carries a fenced note telling developers NOT to `cp .env.example .env`
# in this fork — that documented upstream onboarding move would clobber
# the tracked file. An upstream rewrite of the example must re-seat it.
- .env.example
# Two fenced stubs blank the baked CLI OAuth client id: upstream models
# "unconfigured clone" by not stubbing the variable, which stopped being
# the ambient default once the tracked .env keyed every build.
- apps/web/src/cloud/connectCliAuth.test.ts
# reconcileRootEnv writes relay public config — OTLP tracing tokens
# included — into the repo-root .env, which this customization made
# committable. Unfenced (the fork never runs relay deploys), but any
# upstream rework of where that write lands must be re-checked against
# the tracked file. See the decisions note.
- infra/relay/scripts/deploy.ts
verify:
- apps/web/src/__fork_guards__/t3ConnectOfficialConfig.test.ts
101 changes: 101 additions & 0 deletions .fork/notes/FORK-CUSTOMIZATION-DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,104 @@ Related deep-dives that predate this file and stay where they are:
editor stays unclamped with `overflow-y: hidden` so Geist ink cannot inflate a scroll
container. The guard's `not.toContain("data-composer-prompt-scrollable")` is a regression fence
against bringing the toggle back, not an unexplained ban.

## t3-connect-official-config

- The goal was stated as "parity with official releases on top of the fork's changes", which ruled
out the two heavier options up front: self-hosting the relay (`infra/relay` — own Clerk app,
Cloudflare, PlanetScale; a second production system to operate for zero parity gain) and asking
every clone to hand-author an untracked `.env` (works once, silently absent in the next worktree
or clone, and this fork's contributions largely come from fresh agent worktrees).
- The four values were extracted from the published npm CLI tarball (`t3@0.0.30`), where upstream's
release build inlines them (`VITE_CLERK_CLI_OAUTH_CLIENT_ID` etc. in the bundled web assets).
`app.t3.codes` would have shown the same values but is unreachable from the agent environment's
network policy. If upstream ever rotates them, the fix is re-extracting from the current release
and updating `.env` and the guard's `OFFICIAL_VALUES` together.
- Committing a file that `.gitignore` matches is deliberate, not an oversight: ignore rules bind
only untracked files, so one `git add -f` makes it a normal tracked file forever, upstream can
never conflict with a path it has never shipped, and `.env.local` (still ignored, higher
precedence in `scripts/lib/public-config.ts`) remains the escape hatch for anyone pointing a
checkout at a staging or self-hosted relay. The alternative — carrying the values as code-level
fallbacks — would have meant a Tier-4 fence in an upstream loader that syncs would fight over.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This decision frames force-adding .env as avoiding a Tier-4 loader fence, but it never accounts for deploy.ts rewriting that same file. Please revisit: a tiny fenced read of a .fork/* public-env file is the smaller structural cost. Document why .env was rejected once that collision is in scope.

- No client code changes were needed, by construction: `fork-clerk-launch-resilience` only skips
the desktop Clerk bridge when the build is keyless, and `fork-app-identity` explicitly kept the
shared `t3code://` scheme, which is the redirect Clerk's desktop OAuth allowlist expects. The
packaged fork app signing in shares upstream's scheme contention caveat already recorded there.
- What this does not grant: relay access is authorized by the signed-in user's Clerk account
(waitlist/allowlist on upstream's instance), not by the baked values. A fork build with these
values but no approved account gets exactly what an official build gets — the sign-in flow.
- Review follow-ups (PR #35). Accepted residual risk, named rather than implied: tracking `.env`
removes the structural impossibility of committing it, and `.env` is the file most likely to
receive a `CLERK_SECRET_KEY` by habit. The guard's exact-key-set assertion is the in-repo fence,
but it is CI-only and post-push; GitHub push protection / secret scanning on the fork is the
control actually positioned to catch it pre-push, and should be confirmed enabled in repository
settings (not verifiable from a checkout). Second recorded gap: the guard's value assertion
compares the tracked `.env` against a constant maintained in the same commits, so it catches
copy-drift and secret creep, not upstream rotating the values — if rotation happens, both copies
agree, CI stays green, and the failure surfaces as a dead relay handshake at runtime. A live
probe was considered and declined; guards do not do network.
- Identity consequences of parity, stated out loud: dev runs (`vp run dev`, throwaway `test-t3-app`
worktrees) are now keyed against upstream's production Clerk instance and relay by default, where
T3 Connect was previously compiled out; and with `T3CODE_HOSTED_APP_URL` unset the `t3 connect`
CLI flow round-trips through upstream's hosted app, so the consent screen names upstream's
application. Both are exactly what "parity with official releases" means, and both are
overridable per-checkout via `.env.local` — with a real limit the second review caught:
`.env.local` can replace a value, never blank one. `firstNonEmpty` in the loader treats an empty
string as absent and falls through to the tracked `.env`, and the resolved projection is spread
last, so `T3CODE_CLERK_PUBLISHABLE_KEY=` in `.env.local` (or the process env) leaves the build
keyed. Turning T3 Connect off in a checkout means temporarily deleting the tracked `.env` — the
guard suite will complain locally until it is restored, which is the visible reminder not to
commit the deletion. A fenced loader opt-out was considered and declined: it would put a fork
hunk in upstream's build bootstrap to serve a local-experiment case that deletion already covers.
- Keyed desktop launch path: baking a key moves packaged fork builds off
fork-clerk-launch-resilience's skip path and onto upstream's bridge path — deliberately, since
that is the path every official desktop release ships. This entry originally argued a keyed
fork build was "no worse-positioned in the ready race than an official build" because main.ts's
pre-ready registration was strictly earlier than the bridge's own. True, and useless: the
bridge's own post-ready call still throws regardless of what was registered first, official
builds ship that dice roll to end users, and upstream runs no launch gate in CI to see it. The
fork's first keyed dry-run gate saw it immediately and failed the build; the incident and fix
live under `## fork-clerk-launch-resilience` below. Unit coverage of the keyed bridge already
existed in upstream's DesktopClerk.test.ts, whose fenced hunk bakes a key in before import; the
launch race itself is only observable in a packaged boot, which fork-release.yml's launch
isolation gate exercises — run it with dry_run=true to prove a keyed build before tagging a
release.
- Second review's sharpest catch: `infra/relay/scripts/deploy.ts`'s `reconcileRootEnv` writes
relay public config — including `T3CODE_MOBILE_OTLP_TRACES_TOKEN` and
`T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN`, the latter `::add-mask::`-ed in upstream's own release
workflow — into the repo-root `.env`, which this customization made committable. In-repo
automation of the exact failure the key-set guard fences. Accepted un-fenced because this fork
never runs relay deploys (self-hosting was rejected above) and a fenced redirect to `.env.local`
would also have to carry fenced edits to upstream's `deploy.test.ts`, which asserts the `.env`
target. If the fork ever self-hosts, redirecting that write is the first change to make. The
guard's failure message now names both legitimate writers (deploy output, optional OTLP values)
and points them at `.env.local`, so tripping it reads as "wrong file", not "you leaked a secret".
- The `.env.example` fork note is now asserted by the guard suite (`toContain` on the fence
marker), closing the fourth finding: `watch:` reports drift after a sync merges, but only a
failing test stops `cp .env.example .env` from shipping upstream's placeholders over the live
values via a routine `git commit -a`.

## fork-clerk-launch-resilience

- The keyed-build story failed the moment it was actually tested. After t3-connect-official-config
keyed every fork build, the manifest was rewritten to say keyed builds keep upstream's bridge
behavior exactly, leaning on "pre-ready re-registration observed harmless on Electron 41". The
first dry-run launch gate on a keyed build (PR #35, run 30508846869) exited before the server
started: the macOS runner's layer construction trailed "ready", `createClerkBridge` called
`registerSchemesAsPrivileged` post-ready, and `DesktopClerkBridgeInitializationError` was
fatally loud — the v0.1.2 failure reproduced on the keyed path. "Observed harmless" had only
ever described boots that won the race; CI was the population of boots that lose it.
- The fix (7eedbbb6) makes main.ts the sole registrar on every path: `createDesktopClerkBridge`
no-ops `protocol.registerSchemesAsPrivileged` for exactly the duration of `createClerkBridge`
and restores it in a `finally`. Chosen over gating on `app.isReady()` — an isReady gate keeps
fast and slow boots on different code paths, and the losing path is the one CI never exercises —
and over catching the initialization error, which would swallow unknown causes along with the
one known one. Safe only because main.ts registers the identical privilege set the bridge would
have; the launch-resilience guard pins that set item by item, and a future @clerk/electron that
registers a different set surfaces as a failing renderer in the launch gate, not silently.
Covered by DesktopClerkForkRegistrarSuppression.test.ts (registration lands on the no-op, the
registrar is restored after).
- Two sessions independently authored this same fix within the hour, differing only in fence
placement and test naming — the shipped one is 7eedbbb6; the duplicate was dropped unpushed.
Recorded because the convergence is itself evidence the no-op-around-bridge-creation shape is
the minimal fix, not one agent's taste.
6 changes: 3 additions & 3 deletions .github/workflows/fork-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ jobs:
so copy anything you need out of the prompt stash before upgrading.
Details: the [v0.1.6 release notes](https://github.com/NoahHendrickson/t3code/releases/tag/v0.1.6).

Cloud features (T3 Connect remote access and sign-in) are not
configured in fork builds and will be unavailable. Everything local
works normally.
T3 Connect (remote access and sign-in) is configured with the same
public values as official T3 Code releases and uses upstream's
hosted relay. Everything local works without signing in, as always.
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Loading
Loading