Skip to content

feat(cli): sign in to styal Link with an emailed code in the terminal - #905

Open
incognitojam wants to merge 1 commit into
styal/link-clients-email-sign-infrom
styal/link-cli-email-sign-in
Open

incognitojam wants to merge 1 commit into
styal/link-clients-email-sign-infrom
styal/link-cli-email-sign-in

Conversation

@incognitojam

@incognitojam incognitojam commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Problem

styal link signs in through Clerk, either a loopback browser flow or Clerk's device grant, so CLI and background-server sessions also end on Clerk's schedule, and signing in over SSH needs a browser on the remote side.

Change

styal link asks for an email address and the eight-digit code from it in the terminal, so sign-in works the same locally and over SSH. The loopback browser flow and Clerk's device grant are removed.

The CLI keeps its own P-256 DPoP key and relay session in the server's secret store and signs link, unlink and tunnel-release requests with DPoP. Secrets are published with a hard link so a CLI and a background server starting together agree on one key, with an exclusive-create fallback on filesystems without hard links. Credential changes run under a cross-process file lock shared by every process using the same home; the Codex ChatGPT session lock now uses the same implementation.

On startup a background server exchanges a stored Clerk credential for a relay session without prompting. It retries while Clerk or the relay is unavailable and stops after errors that cannot succeed. The Clerk credential is kept for older processes and deleted only when Clerk or the relay rejects it. Network calls made under the lock time out after 15 seconds, and contention reports "Another styal process is updating the styal Link sign-in; try again." status shows the account email, and logout ends the session at the relay.

styal link login offers email sign-in whenever the stored sign-in cannot be used for a lasting reason (Clerk refuses the refresh, the relay refuses the exchange, the stored Clerk token cannot be read), and fails with a clear message only for temporary problems such as an unreachable relay or another process holding the lock. A failed verify or resend over a flaky connection prints the error and asks again instead of ending the sign-in. A session stored for a different relay than the configured one counts as signed out, and authorization headers are only produced for requests to the session's relay. Passing --email for a different address than the signed-in account warns and points to styal link logout.

The CLI signs proofs with the DPoP helpers in @t3tools/shared/dpopProof. User and operations docs, the agent test skill, the fork differences overview and the fork feature ledger describe email sign-in and its rollout order.

Scope and approval

Maintainer-directed replacement of Clerk for styal Link identity.

Verification

  • Drove styal link login --email … in a pseudo-terminal against the local relay: a wrong code printed "That code is not correct", the right code printed "Signed in as agent+cli-e2e@styal.build", and the relay held one t3-cli session. styal link status showed the account email; styal link logout printed "Signed out of styal Link." and the session row was gone. The secrets directory and files were mode 0700/0600.
  • 528 focused tests across server cloud, CLI and auth code, the Codex session lock, server startup and the shared DPoP signer, covering concurrent key creation, link failures (EPERM, EXDEV, ENOSYS and others), stale temp cleanup, startup retry policy against permanent and transient errors, stalled requests timing out and releasing the lock, lock contention, and a separate-process lease.
  • Typecheck passes for the server and shared packages; the fork feature ledger validates.
  • Against a deployed personal relay stage with log delivery: styal link login from source in a pseudo-terminal signed in with the fixed test code, a wrong code for another address was rejected and asked for again, status showed the account, and logout refreshed the token, deleted the environment link, called /v1/auth/sign-out and left the CLI signed out.
  • Focused tests for login recovery: a permanent adoption failure still reaches the code prompt and signs in, a failed verify or resend asks again and then succeeds, a session for another relay counts as signed out, an unreadable proof key explains how to recover, and a refresh that reports a new email does not overwrite a session another process stored.
  • Usage is measured on the relay: sign-in and Clerk exchange spans record the client id (t3-cli) and auth mode, and session rows carry the client id.
  • Not exercised: Windows or macOS, network filesystems, and a real Clerk credential exchange.

Written by an agent (Claude Code, claude-opus-5-5).

@incognitojam
incognitojam added this pull request to stack #906 October 18, 2026 17:31
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Upstream divergence

Both sides are compared with upstream at the in-order tip 6ba6a0daf4. Excludes .repos/ and pnpm-lock.yaml.

Measure Base This PR Change
Upstream files the fork changes 1,577 1,581 +4
Lines changed in them 85,608 86,817 +1,209
Upstream files the fork removes 54 57 +3
Fork-only files 652 659 +7
Fork-only lines 119,423 122,053 +2,630

Upstream files that start to differ (7)

  • apps/server/src/auth/ServerSecretStore.test.ts: +197 −0 from upstream
  • apps/server/src/cli/connect.test.ts: +286 −14 from upstream
  • apps/server/src/cloud/CliTokenManager.test.ts removed: +0 −325 from upstream
  • apps/server/src/cloud/CloudLink.lifecycle.test.ts: +64 −22 from upstream
  • apps/server/src/cloud/CloudLink.test.ts: +2 −2 from upstream
  • apps/server/src/cloud/publicConfig.ts: +9 −66 from upstream
  • apps/server/src/provider/CodexChatGptSessionLock.ts: +12 −34 from upstream
Upstream files whose difference changes (11)
File Change Now differs by
apps/server/src/cloud/CliTokenManager.ts +567 585 lines
apps/server/src/cli/connect.ts +299 455 lines
apps/server/src/cloud/cliAuthHtml.ts +145 155 lines
apps/server/src/cloud/CloudLink.ts +65 77 lines
apps/server/src/auth/ServerSecretStore.ts +46 48 lines
apps/server/src/cloud/publicConfig.test.ts +45 51 lines
docs/internals/t3-connect.md +41 71 lines
docs/user/remote-access.md +15 172 lines
apps/server/src/server.ts +12 186 lines
packages/shared/package.json +4 68 lines
.env.example +2 19 lines

@incognitojam
incognitojam force-pushed the styal/link-cli-email-sign-in branch 2 times, most recently from b49b198 to aeff160 Compare October 18, 2026 17:42
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire — 4.9 KiB — 6.8 KiB ✅
Codex Thread snapshot wire — 3.8 KiB — 4.9 KiB ✅
Codex Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Codex Live turn WebSocket decoded — 20.8 KiB — 29.3 KiB ✅
Codex Live turn messages — 1 — 8 ✅
Claude Total thread wire — 5.0 KiB — 6.8 KiB ✅
Claude Thread snapshot wire — 3.8 KiB — 4.9 KiB ✅
Claude Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Claude Live turn WebSocket decoded — 21.2 KiB — 29.3 KiB ✅
Claude Live turn messages — 2 — 8 ✅

Baseline: unavailable · PR result: eea6285 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@incognitojam
incognitojam force-pushed the styal/link-cli-email-sign-in branch from aeff160 to 49e2247 Compare October 18, 2026 17:53
@incognitojam
incognitojam force-pushed the styal/link-cli-email-sign-in branch from 49e2247 to 01c6e29 Compare October 19, 2026 13:53
@incognitojam
incognitojam force-pushed the styal/link-cli-email-sign-in branch 2 times, most recently from 013cd34 to 0b9ccd8 Compare October 19, 2026 17:00
@incognitojam
incognitojam force-pushed the styal/link-cli-email-sign-in branch 2 times, most recently from c8010ea to 8a337b5 Compare October 20, 2026 10:42
@incognitojam
incognitojam force-pushed the styal/link-cli-email-sign-in branch from 8a337b5 to 20c66df Compare October 20, 2026 11:00
@incognitojam
incognitojam force-pushed the styal/link-cli-email-sign-in branch from 20c66df to a8fff54 Compare October 20, 2026 15:29
@incognitojam
incognitojam marked this pull request as ready for review October 20, 2026 15:55
@incognitojam
incognitojam force-pushed the styal/link-cli-email-sign-in branch from a8fff54 to 1a88ba6 Compare October 21, 2026 15:57
`styal link` now asks for an email and the eight-digit code from it in the
terminal, so signing in works the same locally and over SSH. The loopback
browser flow and Clerk's device grant are gone.

The CLI keeps its own P-256 DPoP key and relay session in the server's
secret store, and signs link, unlink and tunnel-release requests with DPoP.
The key is published atomically, so a CLI and background server starting
together agree on one key. A background server moves a stored Clerk
credential to a relay session on startup without a prompt, retrying when
Clerk or the relay is unavailable, under a lock shared by every process
using the same home. The Clerk credential is kept for older processes and
deleted only when Clerk or the relay rejects it. `status` shows the
account email and `logout` ends the session at the relay.

The browser and CLI share a DPoP proof signer in @t3tools/shared/dpopProof.
Docs, the agent test skill and the fork differences overview describe
email sign-in and the order to roll it out in.
@incognitojam
incognitojam force-pushed the styal/link-cli-email-sign-in branch from 1a88ba6 to eea6285 Compare October 22, 2026 00:29

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant