Skip to content

feat(desktop): open a thread from a t3code:// deep link - #6008

Closed
edwluo wants to merge 2 commits into
pingdotgg:mainfrom
edwluo:feat/desktop-thread-deep-link
Closed

edwluo wants to merge 2 commits into
pingdotgg:mainfrom
edwluo:feat/desktop-thread-deep-link

Conversation

@edwluo

@edwluo edwluo commented Aug 10, 2026 •

Copy link
Copy Markdown

Summary

  • Problem: t3code://threads/<environmentId>/<threadId> links are already produced by buildAgentAwarenessDeepLink and consumed by the mobile app, but opening one on desktop does nothing beyond (at most) focusing the window — the user still has to find the thread by hand.
  • Why it matters: it blocks exactly the integrations [Feature]: Open a specific desktop thread via t3code:// #4996 asks for — a menu-bar app, notification action, hardware controller or any local automation that wants to take the user straight to the thread that needs attention.
  • What changed: the main process now listens for open-url (macOS) and reads the URL out of argv (Windows/Linux, both cold start and second-instance), validates it, and hands a typed target to the renderer, which navigates through the existing /$environmentId/$threadId route.
  • What did NOT change (scope boundary): no changes to Clerk/OAuth handling, to DesktopClerk's existing second-instance reveal listener, to the deep-link producer, to mobile, or to the packaged Linux .desktop file. The external URL contract is unchanged — this only teaches desktop to consume what mobile already consumes.

On CONTRIBUTING

I read CONTRIBUTING.md before opening this, so to address its points directly:

  • Issue first: [Feature]: Open a specific desktop thread via t3code:// #4996 has been open since 30 July asking for exactly this, filed by someone else. This is an implementation of an existing request, not a drive-by feature or a scope expansion.
  • Size: 430 lines, of which ~130 are the test file and a good share of the rest is comments explaining the platform differences. The behavioural change is one new module plus one method on DesktopWindow.
  • UI evidence: there is no visual change to capture — no new surface, no motion, no transition. The observable difference is that a URL now navigates. If you would still like a screen recording of the navigation, I am happy to attach one.
  • Shrinking: if this is still too much at once, I can cut it down to macOS open-url only and drop the Windows/Linux argv paths, or split the pure parser out as its own PR. Say which you prefer rather than closing it, and I will resubmit.

Entirely fine if the answer is "not now" — no obligation implied.

Change Type / Scope

  • Bug fix [x] Feature [ ] Refactor [ ] Docs [ ] Security [ ] Chore
  • Scope: apps/desktop (main + preload), apps/web (renderer navigation), packages/contracts (bridge type)

Linked Issue/PR

What changed

Before — nothing consumes the URL. DesktopClerk registers the only second-instance listener and ignores its arguments:

yield* electronApp.on("second-instance", () => {
  void runPromise(
    Effect.gen(function* () {
      const mainWindow = yield* electronWindow.currentMainOrFirst;
      if (Option.isSome(mainWindow)) {
        yield* electronWindow.reveal(mainWindow.value);
      }
    }),
  );
});

There is no open-url listener at all, so on macOS the URL never reaches the app.

After — a new DesktopDeepLinkRouter owns link delivery, and DesktopWindow gains dispatchDeepLink:

// DesktopDeepLinkRouter.register
yield* electronApp.on<[{ preventDefault: () => void }, string]>("open-url", (event, url) => {
  event.preventDefault();
  handle(url, "open-url");
});

yield* electronApp.on<[unknown, readonly string[], string]>("second-instance", (_event, argv) => {
  const url = DesktopDeepLink.findDeepLinkInArgv(argv ?? [], schemes);
  if (url !== null) handle(url, "second-instance");
});

// Windows/Linux cold start: the launching URL is already in our own argv.
const launchUrl = DesktopDeepLink.findDeepLinkInArgv(process.argv, schemes);
if (launchUrl !== null) handle(launchUrl, "launch-argv");

Delivery reuses the dispatchMenuAction shape (existing window → otherwise ensureMain, wait for did-finish-load when the frame is still loading, then reveal), plus one addition that menu actions do not need:

if (Option.isNone(existingWindow) && !(yield* Ref.get(backendReadyRef))) {
  yield* Ref.set(pendingDeepLinkRef, Option.some(target));   // held, not dropped
  return;
}

handleBackendReady then replays it once via Ref.getAndSet(pendingDeepLinkRef, Option.none()). Without this, a link that launches the app would be discarded, which is half of what #4996 asks for.

Renderer navigates through the typed route rather than a raw hash:

void navigate({
  to: "/$environmentId/$threadId",
  params: { environmentId: target.environmentId, threadId: target.threadId },
});

Test plan

  • DesktopDeepLink.test.ts — 15 cases over the two pure functions.
  • Happy path: documented shape parses; both registered schemes accepted; scheme/host case-insensitive; segments percent-decoded.
  • Rejection: foreign schemes (https://, t3codex://); other hosts, explicitly including t3code://app/... (the renderer bundle) and t3code://oauth/callback — so this cannot swallow links that belong to other handlers.
  • Rejection: wrong segment count, blank/whitespace segments, malformed percent-encoding, non-URL input.
  • Rejection: segments that decode back into /, ? or # — a crafted link must not address a different route than its two visible segments suggest.
  • argv extraction: finds the link among unrelated args, trims shell whitespace, takes the first of several, returns null when absent.

Reproduction

Environment

  • OS: macOS 26 (Apple silicon)
  • Runtime: Node 24
  • App: built from this branch

Steps

  1. Open any thread and note its environmentId / threadId.
  2. Navigate elsewhere in the app (or quit it entirely).
  3. Run open "t3code://threads/<environmentId>/<threadId>".
  4. Before: the window is focused at most, and the thread is not opened. After: the app reveals and navigates to that thread, whether it was already running or was launched by the link.

Three existing DesktopWindow stubs (DesktopLifecycle.test.ts, DesktopBackendPool.test.ts, DesktopApplicationMenu.test.ts) each gained one line, because they satisfies the service type and it now has one more method. That is the whole reason those three files appear in the diff.

Commands run in this working tree:

apps/desktop     tsgo --noEmit        no errors
packages/contracts tsgo --noEmit      no errors
apps/web         tsgo --noEmit        no errors
apps/desktop     vp test run          59 files, 459 tests passed

Evidence

  • Unit tests for both pure functions
  • Mutation-checked: weakening isCleanSegment to accept any non-empty string turns exactly two cases red ("rejects segments that decode back into path separators", "rejects blank or whitespace-padded segments") and leaves the other 13 green — the guards are actually exercised, not incidentally satisfied.
  • Trace/log snippets
  • Screenshot/recording — no visual change; see note above
  • Perf numbers — N/A

Security Impact

  • New permissions/capabilities? No.
  • Secrets/tokens handling changed? No.
  • New/changed network calls? No.
  • Command/tool execution surface changed? No — the URL is parsed, never executed, and only two id strings are forwarded.
  • Data access scope changed? No. The parser is deliberately strict: only the app's own registered scheme, only the threads host, exactly two segments, and no segment that decodes back into a path separator. Everything else returns null and falls through to existing behaviour.

Human Verification

  • Verified: unit tests for the parsing and argv-extraction logic; typecheck.
  • NOT verified: I have not run a packaged build end-to-end on macOS/Windows/Linux, so the Electron event wiring itself (open-url firing, second-instance argv contents, the cold-start replay through handleBackendReady) is reasoned from the existing dispatchMenuAction path rather than observed. If you would like me to attach a recording from a local build before merging, say the word.

Failure Recovery

  • How to revert: revert this PR's single commit — no migrations, no persisted state, no schema change.
  • Files to restore: apps/desktop/src/{app/DesktopApp.ts,ipc/channels.ts,main.ts,preload.ts,window/DesktopWindow.ts}, apps/web/src/components/AppSidebarLayout.tsx, packages/contracts/src/ipc.ts; delete apps/desktop/src/app/DesktopDeepLink*.ts.
  • Bad symptoms to watch: a t3code:// link that used to reach another handler (OAuth) no longer doing so — the parser rejects any host other than threads, and there is a test for the oauth and app hosts specifically.

Risks and Mitigations

  • Risk: swallowing links intended for the Clerk/OAuth flow.
    • Mitigation: this adds a listener rather than replacing one; every listener still runs. Non-threads hosts return null and are ignored, with tests covering t3code://app/... and t3code://oauth/callback.
  • Risk: the held deep link being replayed more than once across a backend restart.
    • Mitigation: Ref.getAndSet(..., Option.none()) takes the value, so a later handleBackendReady has nothing to replay.
  • Risk: navigating to a thread the user cannot see.
    • Mitigation: navigation goes through the existing typed route, so the route's own loading/missing states (resolveThreadRouteRenderState) apply unchanged.

🤖 Authored with Claude Code. The parsing rules and the hold-until-ready behaviour were derived by reading the existing dispatchMenuAction / handleBackendReady paths in this repo; tests were written to fail without the change.

Note

Add t3code:// deep link routing to open threads in DesktopApp

  • DesktopDeepLinkRouter parses t3code://threads/{envId}/{threadId} URLs from macOS open-url, Windows/Linux second-instance, and cold-start process.argv.
  • DesktopWindow.dispatchDeepLink holds the parsed target until the backend is ready, then sends it over DEEP_LINK_CHANNEL to the renderer.
  • AppSidebarLayout.tsx subscribes to window.desktopBridge.onDeepLink and navigates to /$environmentId/$threadId.
  • Behavioral Change: DesktopWindow interface extended with dispatchDeepLink; test mock layers updated to include this method.

Macroscope summarized 699f356.


Note

Medium Risk
Touches Electron protocol events (open-url, second-instance, argv) and IPC into the renderer. Parsing is strict and OAuth/app hosts are rejected, but a mistake could steal other scheme handlers or navigate to the wrong thread.

Overview
Desktop now opens t3code://threads/<environmentId>/<threadId> links (the same contract mobile already uses) instead of only focusing the window.

A new router listens on macOS open-url (registered before ready) and on Windows/Linux via launch argv plus second-instance. URLs are parsed strictly (registered scheme, threads host, exactly two clean segments) so OAuth and renderer-bundle URLs are ignored. DesktopWindow holds a pending target until the backend is ready, then sends it over desktop:deep-link.

The preload buffers the typed target until the renderer subscribes; AppSidebarLayout navigates to /$environmentId/$threadId. Clerk/OAuth handling is unchanged.

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

@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 20f0ecfc-656a-485d-919c-4b4923913ba0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 10, 2026
Comment thread apps/desktop/src/app/DesktopApp.ts Outdated

// On a cold start the renderer is still loading and would miss an immediate
// send, so wait for the first load to finish before handing the link over.
if (targetWindow.webContents.isLoadingMainFrame()) {

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.

🟠 High window/DesktopWindow.ts:829

The cold-start path in deliverDeepLink waits for did-finish-load and then immediately calls webContents.send(DEEP_LINK_CHANNEL, target). The renderer only registers its DEEP_LINK_CHANNEL listener later from a React useEffect in AppSidebarLayout, after the auth gate loads. Because Electron IPC has no replay buffer, the one-shot message is lost whenever it fires before that listener is registered, so opening a deep link from a closed app silently fails to navigate. Consider replacing the did-finish-load heuristic with an explicit renderer-ready handshake (e.g., the renderer sends a ready signal before main delivers the link) or buffering the pending link on the renderer side.

Also found in 1 other location(s)

apps/web/src/components/AppSidebarLayout.tsx:194

The deep-link IPC listener is only registered in a post-render useEffect, but cold-start delivery sends DEEP_LINK_CHANNEL as soon as Electron emits did-finish-load. The preload implementation uses a plain ipcRenderer.on listener and does not queue/replay messages, so the main process can send the pending launch link before this effect runs (or before AppSidebarLayout mounts while the auth gate loads). In that case the one-shot IPC message is lost and opening a deep link from a closed app does not navigate to the thread.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/window/DesktopWindow.ts around line 829:

The cold-start path in `deliverDeepLink` waits for `did-finish-load` and then immediately calls `webContents.send(DEEP_LINK_CHANNEL, target)`. The renderer only registers its `DEEP_LINK_CHANNEL` listener later from a React `useEffect` in `AppSidebarLayout`, after the auth gate loads. Because Electron IPC has no replay buffer, the one-shot message is lost whenever it fires before that listener is registered, so opening a deep link from a closed app silently fails to navigate. Consider replacing the `did-finish-load` heuristic with an explicit renderer-ready handshake (e.g., the renderer sends a ready signal before main delivers the link) or buffering the pending link on the renderer side.

Also found in 1 other location(s):
- apps/web/src/components/AppSidebarLayout.tsx:194 -- The deep-link IPC listener is only registered in a post-render `useEffect`, but cold-start delivery sends `DEEP_LINK_CHANNEL` as soon as Electron emits `did-finish-load`. The preload implementation uses a plain `ipcRenderer.on` listener and does not queue/replay messages, so the main process can send the pending launch link before this effect runs (or before `AppSidebarLayout` mounts while the auth gate loads). In that case the one-shot IPC message is lost and opening a deep link from a closed app does not navigate to the thread.

Comment thread apps/desktop/src/window/DesktopWindow.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — New feature adding deep link navigation with complex cross-platform handling and main/renderer IPC coordination. Multiple High severity findings identify timing issues in cold-start scenarios where links may be silently dropped.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20eb2eae01

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/app/DesktopApp.ts Outdated
Comment on lines +292 to +294
// Registered before bootstrap so a link that launched the app is picked up
// and held, rather than being missed while the backend starts.
yield* deepLinkRouter.register;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Register the macOS URL handler before app readiness

On a macOS cold start, Electron can emit open-url before ready, and its API requires this listener to be installed before that point. Here deepLinkRouter.register does not run until after electronApp.whenReady has resolved, so the launch URL is lost and cannot be recovered from process.argv on macOS; opening a thread link while the app is closed therefore still fails.

Useful? React with 👍 / 👎.

Comment on lines +829 to +830
if (targetWindow.webContents.isLoadingMainFrame()) {
targetWindow.webContents.once("did-finish-load", send);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Buffer cold-start links until the renderer subscribes

When a deep link creates a still-loading window, this sends the only IPC notification directly from did-finish-load, but the renderer does not call onDeepLink until AppSidebarLayout's passive useEffect runs. Electron does not queue renderer IPC events for listeners registered later, so on cold starts where that effect has not run by did-finish-load, the target is silently dropped; use a preload-side buffer or an explicit renderer-ready handshake before consuming the pending link.

Useful? React with 👍 / 👎.

@Moinax

Moinax commented Aug 13, 2026

Copy link
Copy Markdown

Ran this branch from a packaged Linux build (CachyOS, AppImage) — the wiring holds end to end, cold-start argv path included. One number worth having before it lands, because it bounds what can be built on top.

On Linux a caller's only entry point is xdg-open, which launches a second full copy of the app whose entire job is to take the single-instance lock, emit second-instance and exit. Measured here: ~1.4s from xdg-open to the thread being on screen, roughly half of it the AppImage's squashfs mount. Fine for a link clicked in a browser — but #4996 asks for notification actions and status-bar integrations, and a menu-bar item that takes 1.4s to switch threads reads as broken.

A local listener in the main process removes that second process entirely. I added one on top of this branch:

// in DesktopDeepLinkRouter, alongside open-url / second-instance
const server = NodeNet.createServer((connection) => {
  /* accumulate, first line wins, capped */ handle(url, "socket");
});
server.listen(socketPath, () => NodeFS.chmodSync(socketPath, 0o600));

$XDG_RUNTIME_DIR/t3code-deeplink.sock, /tmp/<uid>-… when that is unset, distinct name in dev. Same URL string, same handle(), same parser — one more source on the funnel you already built, not a second contract.

~1.4s → ~10ms, same machine, same link. In practice the tab switch is indistinguishable from a click inside the app.

Shape, briefly:

  • The 0600 mode in a user-owned directory is the authentication — the same trust boundary xdg-open already assumes. Nothing is read back, and the only reachable action is the navigation handle performs for links from anywhere else.
  • Non-fatal by construction: acquireRelease unlinks a stale socket before listen and closes + unlinks on release; the whole registration is wrapped so any failure logs a warning and leaves xdg-open as the path.
  • Linux/macOS in the form I wrote it. Windows would want a named pipe, or simply not have it — open-url/argv still work.

Happy to open it as a follow-up once this merges, or to hand you the diff to fold in here — whichever you prefer. Either way the measurement is the part worth keeping: without something like it, every Linux deep link pays 1.4s.

Moinax added a commit to Moinax/vibewatch that referenced this pull request Aug 13, 2026
The deep link was off because nothing on T3's side answered it. Something does
now, so the default flips: landing on the thread is the point of the click, and
every way it can miss lands somewhere harmless — a build that does not route
`t3code://threads/<environment>/<thread>` reveals its window, which is what the
click did anyway.

`t3.deep_link = false` remains for the one case that is not harmless: a machine
with no T3 desktop app, where nothing claims the scheme and the desktop would
ask which application to open the URL with.

Upstream T3 does not route the link yet (pingdotgg/t3code#6008), so the README
says so rather than implying every build answers.
edwluo added 2 commits August 21, 2026 23:37
Implements pingdotgg#4996.

`t3code://threads/<environmentId>/<threadId>` is already produced by
buildAgentAwarenessDeepLink and consumed by mobile, but desktop never acted
on it: there was no `open-url` listener, and the `second-instance` handler
ignores its argv.

The main process now validates such links and hands a typed target to the
renderer, which navigates through the existing `/$environmentId/$threadId`
route. Delivery reuses the dispatchMenuAction path (existing window,
otherwise ensureMain; wait for did-finish-load while the frame is loading;
then reveal), and additionally holds a link that arrives before the backend
is ready so a link that *launches* the app is not dropped.

Parsing is strict and rejects anything that is not this app's own scheme
with a `threads` host and exactly two clean segments, so OAuth callbacks and
the renderer bundle URL continue to be handled where they are today.
Why:
- macOS can emit open-url before Electron is ready, and renderer IPC sent at did-finish-load can precede React subscription.
- The old backend-ready check and pending write could also race and strand a link.

What:
- Register deep links before app readiness.
- Buffer renderer-bound targets in preload until a subscriber exists.
- Queue before checking backend readiness and drain with an atomic take.

Validation:
- pnpm exec vp test run apps/desktop/src/app/DesktopDeepLink.test.ts apps/desktop/src/app/DesktopDeepLinkBuffer.test.ts apps/desktop/src/window/DesktopWindow.test.ts
- pnpm exec vp run --filter @t3tools/desktop --filter @t3tools/web --filter @t3tools/contracts typecheck

Notes:
- Addresses the three cold-start correctness findings on PR pingdotgg#6008.
@edwluo
edwluo force-pushed the feat/desktop-thread-deep-link branch from 20eb2ea to 699f356 Compare August 21, 2026 15:44

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 699f356f89

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +882 to +884
const existingWindow = yield* focusedMainWindow;
if (Option.isSome(existingWindow)) {
yield* sendDeepLink(existingWindow.value, target);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Send deep links only to the main renderer

When macOS delivers open-url while a browser preview's picture-in-picture window is focused, focusedMainWindow resolves to that separate BrowserWindow created by PreviewManager.openPictureInPicture. The deep-link IPC is then sent to the PiP window's different preload, which has no desktopBridge.onDeepLink listener, so the main renderer never navigates; resolve the registered main window instead and create/reveal it when absent.

Useful? React with 👍 / 👎.

});

const flushPendingDeepLink = Effect.fn("desktop.window.flushPendingDeepLink")(function* () {
const pending = yield* Ref.getAndSet(pendingDeepLinkRef, Option.none());

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.

🟠 High window/DesktopWindow.ts:872

flushPendingDeepLink permanently drops the pending target when ensureMain or sendDeepLink fails, because Ref.getAndSet(..., Option.none()) clears it before delivery succeeds. Keep the target pending until the window is created and the link is delivered, or restore it when either operation fails so a later activation can replay it.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/window/DesktopWindow.ts around line 872:

`flushPendingDeepLink` permanently drops the pending target when `ensureMain` or `sendDeepLink` fails, because `Ref.getAndSet(..., Option.none())` clears it before delivery succeeds. Keep the target pending until the window is created and the link is delivered, or restore it when either operation fails so a later activation can replay it.

) {
yield* Effect.annotateCurrentSpan({ environmentId: target.environmentId });
const send = () => {
if (targetWindow.isDestroyed()) return;

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.

🟠 High window/DesktopWindow.ts:856

When targetWindow is destroyed before send runs, sendDeepLink returns without delivering or re-queuing target; closing the focused window during the did-finish-load wait therefore makes dispatchDeepLink succeed while silently dropping the link. Re-queue the target and flush it when a replacement main window is created instead of returning.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/window/DesktopWindow.ts around line 856:

When `targetWindow` is destroyed before `send` runs, `sendDeepLink` returns without delivering or re-queuing `target`; closing the focused window during the `did-finish-load` wait therefore makes `dispatchDeepLink` succeed while silently dropping the link. Re-queue the target and flush it when a replacement main window is created instead of returning.

@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 1 potential issue.

Fix All in Cursor

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

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 699f356. Configure here.


// Replay a link that arrived before the backend came up. Taken (not read)
// so a later backend restart cannot deliver the same link a second time.
yield* flushPendingDeepLink();

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.

Pending deep link stuck on create failure

High Severity

handleBackendReady sets backendReadyRef and runs createMainIfBackendReady before flushPendingDeepLink. The backend pool swallows window-create failures, so a failed create skips the flush and leaves the cold-start target in pendingDeepLinkRef. A later dock activate can open the window without replaying it, and a later successful readiness can navigate to that stale target. deliverDeepLink also sends to an existing window without clearing the pending ref, which compounds the stale replay.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 699f356. Configure here.

@MaanavD

MaanavD commented Aug 26, 2026

Copy link
Copy Markdown

@Moinax @edwluo anything you need help w/ to get this merged? Would love to get it in for a stream deck integration I've been making!

@t3dotgg

t3dotgg commented Aug 28, 2026

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This adds t3code:// parsing, buffering, and routing, but its URL does not carry both environment and thread identity. #8246 defines that full contract and the renderer readiness flow, so we are closing this older parser.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

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

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Open a specific desktop thread via t3code://

4 participants