Skip to content

Make dev servers shareable from other devices - #4489

Closed
t3dotgg wants to merge 10 commits into
mainfrom
t3code/simplify-dev-server-hosting
Closed

Make dev servers shareable from other devices#4489
t3dotgg wants to merge 10 commits into
mainfrom
t3code/simplify-dev-server-hosting

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Jul 24, 2026

Copy link
Copy Markdown
Member

Sharing a dev server so it can be opened from a phone or another laptop took a full session of workarounds and still ended up hand-launching processes. This fixes the causes.

Browser dev is now single-origin: Vite proxies /api, /ws, /oauth, and /.well-known to the backend, and dev/dev:web no longer set VITE_HTTP_URL/VITE_WS_URL. Those were compiled into the bundle, so any browser not on the dev machine dialed its own localhost — no amount of external path mapping could fix it. dev:desktop still sets them; its renderer talks to the backend directly.

  • dev --share (bun run dev:share) publishes the web port on the tailnet and logs a pairing URL already built against that origin. The mapping is removed on exit, and re-running clears a stale one.
  • bun run dev:pair mints a pairing URL for an already-running server, resolving port and state dir from server-runtime.json — no flags to get wrong. The previously documented auth pairing create snippet passed --base-dir, which points the CLI at a different SQLite file than the server reads.
  • Ports derive from the worktree path, so each worktree is stable across restarts. The availability probe is loopback-only; probing wildcards let a live tailscale serve push the dev server to another port mid-setup.
  • Web session cookies are port-scoped (t3_session_<port>) like desktop's. Cookies ignore ports, so servers sharing a hostname clobbered each other into a permanent Invalid session token signature loop.
  • Dev startup pairing tokens last 24h instead of 5m. User-issued links keep the 5m default.
  • Vite allowedHosts covers .ts.net; T3CODE_DEV_ALLOWED_HOSTS / T3CODE_DEV_ALLOWED_ORIGINS handle anything else.

Testing

Ran over a real tailnet: /, /pair, /.well-known/t3/environment, and /api/auth/session all 200; /ws upgrade reaches the backend; token exchange returned authenticated: true with cookie t3_session_<port>. Confirmed in SQLite that the dev startup token gets 24h while CLI-minted tokens stay at 5m, and that Ctrl+C removes the tailnet mapping.

Typecheck clean; 3,282 tests pass across t3, @t3tools/web, and scripts.

Note

Not addressed here: vp's child processes can outlive the dev-runner parent on interrupt and keep holding ports. Pre-existing, and separate from this change.

🤖 Generated with Claude Code


Note

Medium Risk
Port-scoped t3_session_<port> invalidates existing web cookie sessions; seeding overwrites dev projection tables and auth/pairing behavior changes affect local dev workflows.

Overview
Makes browser dev single-origin so a shared tailnet/LAN URL works: dev/dev:web stop setting VITE_HTTP_URL/VITE_WS_URL, Vite proxies /api, /ws, /oauth, and /.well-known via a shared prefix list, and HMR only pins to HOST when set (e.g. desktop).

Adds dev --share / dev:share (Tailscale HTTPS on the web port, pairing URL against that origin, mapping cleared on exit) and dev:pair (auth pairing url), which locates a live server from server-runtime.json (both dev/userdata, PID check) and mints a link against recorded devUrl or origin. Worktrees default dev state to gitignored .t3, ahead of ambient T3CODE_HOME; ports prefer a worktree-hashed offset with loopback-only availability checks.

Auth/dev ergonomics: session cookies are always t3_session_<port> (web sessions reset after upgrade); startup pairing tokens use a 24h TTL when devUrl is set; T3CODE_DEV_ALLOWED_HOSTS / T3CODE_DEV_ALLOWED_ORIGINS support non-localhost access. dev:seed copies recent projection data from ~/.t3 into an isolated dev DB (schema drift tolerant, event log cleared, refuses the shared home).

Reviewed by Cursor Bugbot for commit dda3515. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Make dev servers shareable from other devices via Tailscale

  • Adds --share flag to the dev runner that publishes the web port on the tailnet via shareDevServer, prints a tailnet HTTPS URL, and cleans up the mapping on exit.
  • Adds dev:share, dev:pair, and dev:seed npm scripts; dev:pair prints a fresh pairing URL from the running server's server-runtime.json.
  • Adds dev-seed CLI (scripts/dev-seed.ts) to copy recent threads and projection data from the shared ~/.t3 home into an isolated worktree .t3, with configurable thread/activity limits and schema drift tolerance.
  • Derives stable per-worktree port offsets from the git worktree path so concurrent worktrees get distinct ports without manual configuration.
  • Session cookie names are now always port-scoped (t3_session_<port>) across all server modes to prevent conflicts between concurrent dev servers.
  • Startup pairing tokens issued when a devUrl is configured now use a 24-hour TTL instead of the default short TTL.
  • In dev and dev:web modes, VITE_HTTP_URL and VITE_WS_URL are left unset; the browser resolves the backend via window.location.origin, and Vite proxies /api, /oauth, /.well-known, and /ws to the backend.
  • The Vite dev server now accepts Host headers ending in .ts.net and any hosts in T3CODE_DEV_ALLOWED_HOSTS, enabling access over Tailnet.
  • Risk: Behavioral change — t3_session (unsuffixed) is no longer produced; any client storing the old cookie name will lose its session.

Macroscope summarized dda3515.

Summary by CodeRabbit

  • New Features

    • Added optional sharing of local development servers through secure tailnet URLs.
    • Added commands to generate fresh pairing URLs for running development servers.
    • Improved support for running multiple development instances with predictable port assignment.
    • Added proxy support for authentication, WebSocket, and related development routes.
  • Bug Fixes

    • Prevented session cookies from conflicting between servers running on different ports.
    • Improved handling of stale server state and shared-server host validation.
  • Documentation

    • Expanded development setup, sharing, pairing, troubleshooting, and proxy guidance.

Browser dev is now single-origin: Vite proxies /api, /ws, /oauth, and
/.well-known to the backend, and dev/dev:web no longer bake
VITE_HTTP_URL/VITE_WS_URL into the bundle. Absolute localhost URLs in the
bundle sent any remote browser to its own machine, which no amount of
external path mapping could fix.

- `dev --share` publishes the web port on the tailnet and logs a pairing
  URL already built against that origin
- `dev:pair` mints a pairing URL for a running server, resolving port and
  state dir from server-runtime.json
- Ports derive from the worktree path, so each worktree is stable across
  restarts; the availability probe is loopback-only so `tailscale serve`
  can't push the port
- Web session cookies are port-scoped like desktop's, ending the
  cross-instance "Invalid session token signature" loop
- Dev startup pairing tokens last 24h instead of 5m; user-issued links
  keep the 5m default

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds deterministic worktree-based dev ports, tailnet sharing, live-server pairing URL recovery, single-origin browser proxying, port-scoped session cookies, and updated development documentation and tests.

Changes

Dev server sharing and pairing

Layer / File(s) Summary
Authentication and token contracts
apps/server/src/auth/*
Session cookies now include the server port, and administrative bootstrap tokens use a shared subject constant with a 24-hour dev-server TTL.
Live runtime state and pairing URL command
apps/server/src/serverRuntimeState.ts, apps/server/src/config.ts, apps/server/src/cli/auth.ts, package.json
Runtime state records the dev URL, validates process liveness, and supports generating pairing URLs from an already-running server.
Tailnet sharing and dev-runner lifecycle
scripts/dev-runner.ts, scripts/lib/dev-share.ts, scripts/dev-runner.test.ts
The runner derives worktree-specific ports, adds --share, publishes the web port through Tailscale, and cleans up the mapping on exit.
Single-origin browser proxying
apps/web/vite.config.ts, apps/server/src/http.ts, scripts/dev-runner.ts
Browser dev leaves absolute backend URLs unset, proxies API and websocket routes through Vite, and applies host and origin allowlists.
Development workflow guidance
.agents/skills/test-t3-app/SKILL.md, AGENTS.md, docs/reference/scripts.md
Documentation explains derived ports, sharing, pairing recovery, proxy constraints, and troubleshooting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DevRunner as dev-runner
  participant Tailscale
  participant Vite
  participant Browser
  participant Server
  DevRunner->>Tailscale: publish local web port with tailscale serve
  Tailscale-->>DevRunner: return tailnet host and HTTPS URL
  DevRunner->>Vite: configure shared origin and allowed origins
  Browser->>Vite: request application and proxied routes
  Vite->>Server: proxy API, OAuth, websocket, and well-known requests
  Server-->>Browser: return application responses
Loading

Possibly related PRs

  • pingdotgg/t3code#20: Related deterministic multi-instance dev-runner port derivation and development script environment wiring.

Suggested reviewers: juliusmarminge

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main change: making dev servers shareable from other devices.
Description check ✅ Passed It clearly explains what changed, why, and includes testing notes, though it doesn't follow the exact template headings/checklist.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/simplify-dev-server-hosting

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Jul 24, 2026
Comment thread apps/server/src/cli/auth.ts Outdated
Comment thread scripts/dev-runner.ts
Comment thread apps/server/src/cli/auth.ts
@macroscopeapp

macroscopeapp Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces significant new feature capability (dev server sharing via Tailscale) with auth-adjacent changes (cookie scoping, CORS). There is also an unresolved critical bug in dev-seed that could cause data loss when source and target directories are the same.

You can customize Macroscope's approvability policy. Learn more.

- `auth pairing url` resolved the state directory with the same flag
  heuristic the command exists to hide, so without --dev-url it read
  `userdata` while `bun run dev` writes to `dev`. It then minted into the
  wrong database and printed a URL the live server rejects with
  `invalid_credential`. It now searches both state directories and issues
  against whichever one the running server actually uses.
- A server killed with SIGKILL leaves `server-runtime.json` behind, and
  that was taken as proof it was still up. Verify the recorded pid is
  alive before minting.
- `--dry-run --share` replaced and then tore down the port's existing
  tailscale mapping. Dry run now returns before the share block.

deriveServerPaths takes an optional explicit stateDir for callers that
have already located a running server and must target it exactly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread apps/server/src/cli/auth.ts Outdated
`findLiveServerRuntimeState` checked the configured state path first,
which without --dev-url resolves to `userdata`. With both a userdata and
a dev server running under the same base directory, `auth pairing url`
would mint against the userdata server and print its origin — not the
dev server this command exists to pair with.

Rank candidates by the `devUrl` field instead, which only a server
fronted by a web dev server records. Falls back to any live server when
no dev server is running.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
apps/server/src/http.ts (1)

55-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Route T3CODE_DEV_ALLOWED_ORIGINS through ServerConfig instead of raw process.env.

This function already yields ServerConfig.ServerConfig for devOrigin; reading process.env.T3CODE_DEV_ALLOWED_ORIGINS directly alongside it breaks the config-injection pattern used everywhere else (e.g. devUrl, tailscaleServeEnabled), making this harder to override in tests that inject ServerConfig via layers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/server/src/http.ts` around lines 55 - 76, Update browserApiCorsLayer to
read T3CODE_DEV_ALLOWED_ORIGINS from the yielded ServerConfig.ServerConfig
instance instead of process.env. Add or use the corresponding ServerConfig
field, preserving the existing trimming, filtering, and allowedOrigins behavior
so injected configuration layers can override it in tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.agents/skills/test-t3-app/SKILL.md:
- Around line 82-85: Update the pairing-URL guidance in the skill instructions
to explicitly allow sharing the one-time URL in the response when the user
explicitly requests access, while prohibiting its inclusion in screenshots,
committed files, or durable logs. Reconcile this exception with the existing
prohibition so the intended behavior is unambiguous.

In `@apps/server/src/cli/auth.ts`:
- Around line 142-228: 添加针对 findLiveServerRuntimeState 和 pairingUrlCommand
的聚焦测试,覆盖无存活候选、从 dev 与 userdata 候选中选择正确运行时状态,以及 devUrl 存在时优先使用它、否则回退到 origin
的分支。将测试放在现有 CLI/服务器运行时状态测试约定的位置,并确保相关测试命令会执行这些用例。

In `@docs/reference/scripts.md`:
- Around line 47-50: Qualify the worktree port-stability wording in
docs/reference/scripts.md lines 47-50: replace the claim that worktrees do not
collide with siblings, state that the hash provides a stable preferred pair when
available, and emphasize using the resolved values printed on the “[dev-runner]”
line. Apply the same “stable when available, not guaranteed unique” wording to
the worktree port description in .agents/skills/test-t3-app/SKILL.md lines
21-24.

In `@scripts/lib/dev-share.ts`:
- Around line 148-173: Update shareDevServer so a failed new serve does not
silently discard an existing working mapping: either defer unshareDevServer
until the replacement serve is ready, or preserve and restore the prior mapping
when serve fails. If restoration is not possible, include that the previous
mapping was removed in the DevShareError detail.

---

Nitpick comments:
In `@apps/server/src/http.ts`:
- Around line 55-76: Update browserApiCorsLayer to read
T3CODE_DEV_ALLOWED_ORIGINS from the yielded ServerConfig.ServerConfig instance
instead of process.env. Add or use the corresponding ServerConfig field,
preserving the existing trimming, filtering, and allowedOrigins behavior so
injected configuration layers can override it in tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 66754ed0-b428-4310-a2bc-a7839a1977b1

📥 Commits

Reviewing files that changed from the base of the PR and between 38cfc25 and 36fe3b3.

📒 Files selected for processing (20)
  • .agents/skills/test-t3-app/SKILL.md
  • AGENTS.md
  • apps/server/src/auth/EnvironmentAuth.test.ts
  • apps/server/src/auth/EnvironmentAuth.ts
  • apps/server/src/auth/EnvironmentAuthPolicy.test.ts
  • apps/server/src/auth/EnvironmentAuthPolicy.ts
  • apps/server/src/auth/PairingGrantStore.ts
  • apps/server/src/auth/SessionStore.ts
  • apps/server/src/auth/utils.ts
  • apps/server/src/cli/auth.ts
  • apps/server/src/config.ts
  • apps/server/src/http.ts
  • apps/server/src/serverRuntimeState.test.ts
  • apps/server/src/serverRuntimeState.ts
  • apps/web/vite.config.ts
  • docs/reference/scripts.md
  • package.json
  • scripts/dev-runner.test.ts
  • scripts/dev-runner.ts
  • scripts/lib/dev-share.ts

Comment thread .agents/skills/test-t3-app/SKILL.md
Comment thread apps/server/src/cli/auth.ts Outdated
Comment thread docs/reference/scripts.md Outdated
Comment thread scripts/lib/dev-share.ts
- The skill told agents both to never put a pairing URL in a response and
  to hand the shared one to the user. State the rule and its single
  exception explicitly: give the URL to the person who asked for access,
  never put it anywhere durable.
- A failed `tailscale serve` left the port unshared, because the stale-
  mapping clear had already run. The clear is still required (old
  mappings carry path routes that serving "/" would not replace), so say
  so in the error instead of letting an operator assume the previous
  mapping survived.
- Worktree-derived ports were described as collision-free. They are a
  preferred offset, not a guarantee — hashes can collide and occupied
  ports shift both values. Qualified in AGENTS.md, the skill, and
  docs/reference/scripts.md.

CodeRabbit also asked for tests around the pairing-url command; those
landed in 0555604, which it had not yet seen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread scripts/lib/dev-share.ts Outdated
Comment thread scripts/lib/dev-share.ts Outdated
t3dotgg and others added 2 commits July 24, 2026 17:44
`unshareDevServer` applied Effect.ignore, so a failed removal was
indistinguishable from a successful one. `shareDevServer` then served
over routes it had not removed: stale path entries from the older
`tsdev t3` layout (/ws, /api pointing at a separate backend port) would
survive, producing a URL that loads while its API calls resolve to a
dead port. The serve-failed message also asserted the port was cleared
without knowing it.

It now reports whether the port is clear, and sharing refuses when it
is not. `tailscale serve … off` exits nonzero with "handler does not
exist" when nothing was mapped — the normal first-share case — so that
counts as cleared; anything else does not. The dev-runner finalizer
warns, with the command to run, when cleanup does not take.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A dev server started in a worktree resolved its data directory to the
shared `~/.t3`. Worse, an ambient `T3CODE_HOME` counts as an *explicit*
base dir, which flips the state directory from `<base>/dev` to
`<base>/userdata` — the database the user's installed T3 Code is
actively running against. Feature work in a throwaway branch was
sharing a database with the user's real app, and its
`server-runtime.json` overwrote the live server's entry.

Worktrees now default to their own gitignored `.t3`, outranking the
ambient env var; `--home-dir` still wins, and the main checkout keeps
`~/.t3/dev`. `auth pairing url` applies the same default, so `dev:pair`
from a worktree can no longer mint a credential into the real database.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread apps/server/src/cli/auth.ts Outdated
Comment thread apps/server/src/cli/auth.ts Outdated
An isolated dev database starts empty, which makes any list- or
thread-shaped UI impossible to look at without hand-writing fixtures.
`dev:seed` copies the newest threads and their projects out of the
shared home into the worktree's own database.

Projections only — `orchestration_events` is never copied. The projector
cursor is exclusive, so an empty event log means bootstrap streams
nothing and leaves the copied rows alone; a partial event range is the
real hazard, since the projector would replay a tail whose creating
events are missing.

Details that matter for a usable copy:
- writes all nine projection_state rows, or computeSnapshotSequence
  reports 0 for every shell snapshot
- forces sessions to stopped with no active turn, since a copied
  "running" session has no agent behind it and the reaper skips
  anything with an active turn
- zeroes pending approval/input counts, as approvals are not copied
- copies the intersection of columns, because the two databases are
  routinely on different migrations
- refuses to write to the shared home

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Jul 26, 2026
Comment thread scripts/lib/dev-seed.ts Outdated
Comment thread scripts/lib/dev-seed.ts
Comment thread scripts/dev-seed.ts
Comment thread scripts/lib/dev-seed.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 18aadef. Configure here.

Comment thread scripts/lib/dev-seed.ts
Comment thread scripts/dev-seed.ts
Four parallel cleanup reviews over the branch; the fixes that survived
verification:

- scripts/lib/dev-share.ts was a 216-line reimplementation of
  @t3tools/tailscale (the client the server's own --tailscale-serve
  uses). Now a thin wrapper adding only the dev-share semantics: the
  stale-mapping pre-clear and the cleared/not-cleared result. Picks up
  Windows tailscale.exe handling and command timeouts for free. The
  package's exit error gains a bounded stderrPreview so callers can
  recognize "handler does not exist" without a parallel spawner.
- The .git-is-a-file worktree check existed three times across two
  packages; now once in @t3tools/shared/devHome. dev-runner drops its
  duplicated T3CODE_HOME fallback so base-dir precedence lives in one
  function.
- The dev proxied-prefix list existed in apps/web's vite config and
  apps/server's http catch-all with nothing keeping them in sync; both
  now consume @t3tools/shared/devProxy. The vite proxy map is built
  from the list.
- PROJECTOR_NAMES and the projection-table delete order were pasted in
  both seeding scripts; now shared via scripts/lib/projection-tables.
- PairingGrantStore keyed the dev startup TTL by string-comparing the
  caller's subject; issueOneTimeToken takes an explicit
  purpose: "startup" instead, and the subject constant returns to
  EnvironmentAuth where its other uses live.
- dev-seed: iterate rows instead of materializing (unbounded copies
  held the whole table in memory and the spread had a ~128k-element
  ceiling); parameterized IN lists replace hand-rolled quoting.
- Smaller: cli/auth's layer stack deduped into environmentAuthLayer,
  Set-based dedupe, precomputed proxied prefixes, vite HOST parsed
  once, dead T3CODE_DEV_SHARE_URL removed, redundant tests dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread scripts/dev-seed.ts
Comment on lines +107 to +109
if (canonicalTarget === canonicalShared) {
return yield* new DevSeedTargetError({ reason: "shared-home", detail: targetBaseDir });
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Critical scripts/dev-seed.ts:107

When --from and --to resolve to the same directory, the command opens the same SQLite database as both source and target. seedDevDatabase then deletes every projection table from the target before copying, so it wipes the very data it was supposed to copy — projection data is permanently lost. The safety check at line 107 only guards targetBaseDir === sharedHome; it never checks sourceBaseDir === targetBaseDir. Add a guard that rejects when the source and target paths are identical.

       if (canonicalTarget === canonicalShared) {
         return yield* new DevSeedTargetError({ reason: "shared-home", detail: targetBaseDir });
       }
+      if (sourceBaseDir !== sharedHome) {
+        const canonicalSource = yield* fileSystem
+          .realPath(sourceBaseDir)
+          .pipe(Effect.orElseSucceed(() => sourceBaseDir));
+        if (canonicalSource === canonicalTarget) {
+          return yield* new DevSeedTargetError({ reason: "shared-home", detail: sourceBaseDir });
+        }
+      }
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @scripts/dev-seed.ts around lines 107-109:

When `--from` and `--to` resolve to the same directory, the command opens the same SQLite database as both source and target. `seedDevDatabase` then deletes every projection table from the target before copying, so it wipes the very data it was supposed to copy — projection data is permanently lost. The safety check at line 107 only guards `targetBaseDir === sharedHome`; it never checks `sourceBaseDir === targetBaseDir`. Add a guard that rejects when the source and target paths are identical.

t3dotgg and others added 2 commits July 25, 2026 21:59
- dev:seed wrote projector cursors at 1..9 while leaving the target's
  event log intact. The cursor is exclusive and a fresh log restarts at
  sequence 1, so every projector would skip a different slice of the
  user's first real events; the retained events would also replay over
  the copied projections and resurrect the target's deleted threads. All
  nine cursors now start at 0, and the event log and command receipts
  are emptied with the projections.
- The thread-selection query referenced archived_at and
  latest_user_message_at unconditionally, so a source older than
  migrations 017/023 threw before copying anything. It now builds its
  filter and ordering from the columns the source actually has, matching
  how the rest of the copy handles drift. A table missing from the
  target is likewise skipped rather than aborting the seed.
- --threads/--activities accepted negatives, which SQLite reads as "no
  limit" — a capped copy silently became a full-table one. Both now
  require a positive integer.
- resolveGitWorktreePath only looked at cwd, so running dev:pair from a
  subdirectory fell through to the shared home. It walks up to the
  repository root now.
- resolveWorktreeT3Home no longer returns undefined when .t3 is absent:
  inside a worktree that is the answer, and falling back to the shared
  home is precisely the accident the default prevents.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@t3dotgg

t3dotgg commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

Closing as superseded. The core isolated/shareable dev work landed in #4555/#4556, while pairing and test-data setup moved to the smaller #4955 and #4949 paths.

@t3dotgg t3dotgg closed this Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant