Skip to content

fix(desktop): Register and route t3code:// links - #4908

Open
benthecarman wants to merge 2 commits into
pingdotgg:mainfrom
benthecarman:fix-desktop-deep-links
Open

fix(desktop): Register and route t3code:// links#4908
benthecarman wants to merge 2 commits into
pingdotgg:mainfrom
benthecarman:fix-desktop-deep-links

Conversation

@benthecarman

@benthecarman benthecarman commented Jul 30, 2026

Copy link
Copy Markdown

What Changed

Bug fix: t3code:// links now actually reach the app. protocols moves from the mac block to the top level of the desktop build config, so the Linux desktop entry gains MimeType=x-scheme-handler/t3code;x-scheme-handler/t3code-dev; alongside the existing macOS CFBundleURLTypes, and a new DesktopDeepLinks service claims the scheme at runtime and routes an incoming link to the renderer.

Why

The scheme was declared for macOS only and nothing consumed an incoming URL, so clicking a t3code:// link did nothing on Linux or Windows and dropped its payload everywhere. Two independent gaps:

  • Never registered. electron-builder builds the Linux desktop entry's MimeType from config.protocols + linux.protocols (LinuxTargetHelper.js:266) and the macOS Info.plist from config.protocols + mac.protocols (electronMac.js:143). With the declaration under mac, Linux got no x-scheme-handler and xdg-mime query default x-scheme-handler/t3code resolved to nothing. NSIS reads protocols from neither, so Windows needs a runtime registration — setAsDefaultProtocolClient was wrapped in ElectronApp.ts but had no callers.
  • Never handled. There was no open-url listener for macOS, and the second-instance listener only revealed the window, discarding the argv that Linux and Windows deliver links through.

Design notes for review:

  • Untrusted input. A link can come from any web page, so parsing accepts only our own scheme and the app host and forwards a path — never a full URL. A protocol-relative path (t3code://app//evil.example) is rejected because a router reads it as another origin.
  • Cold start. A link handed over on the command line arrives long before a window exists, so it's held until bootstrap opens a renderer. dispatchDeepLink reports whether the send landed instead of silently dropping it; dispatchMenuAction and it now share one sendToMainWindow helper.
  • Client-side routing. The path goes to the renderer over a new desktop:deep-link channel rather than loadURL, so navigating doesn't reload the window and discard session state.
  • Ordering. Registration runs after clerk.configure, which owns the single-instance lock a second launch needs in order to hand its link to the running process.

Verification

Built a Linux artifact and confirmed the packaged desktop entry in both the AppImage and the .deb:

MimeType=x-scheme-handler/t3code;x-scheme-handler/t3code-dev;

13 new tests in DesktopDeepLinks.test.ts cover parsing (including the protocol-relative and foreign-host rejections), argv extraction, per-platform registration, and the cold-start hold-and-flush. Full desktop suite 417/417, build-desktop-artifact 31/31, vp run -r typecheck clean across all 15 packages. The macOS open-url and Windows registration paths are covered by tests but unverified on real hardware — I only have Linux here.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (n/a — no visible UI change; the only renderer edit subscribes to the new channel)
  • I included a video for animation/interaction changes (n/a — no motion or interaction changes)

Note

Medium Risk
Touches untrusted URL input, protocol registration, and navigation without full-window reload; parsing and logging are constrained to safe paths, but OS handler registration and second-instance ordering add platform risk.

Overview
t3code:// links now open the desktop app and navigate the renderer instead of being ignored on Linux/Windows or never consumed on macOS.

A new DesktopDeepLinks service runs after Clerk’s single-instance lock: it registers the URL scheme on Linux/Windows (setAsDefaultProtocolClient), listens for open-url (macOS) and second-instance (argv on Linux/Windows), and parses untrusted URLs into in-app paths only (t3code://app/.../path?query#hash), rejecting foreign hosts and protocol-relative paths.

Cold-start and delivery: one pending link slot (latest wins); the renderer pulls via takePendingDeepLink on mount and subscribes to onDeepLink pushes. DesktopWindow.dispatchDeepLink sends over IPC without deferring while loading (unlike menu actions); failed delivery keeps the link pending.

Web: useDesktopDeepLinks in the root route navigates via TanStack Router href so pairing/sign-in screens still receive links.

Build: protocols moves to the top-level electron-builder config with production scheme only (t3code); dev uses t3code-dev at runtime. Windows still registers at runtime (NSIS ignores build-time protocols).

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

Note

Register and route t3code:// deep links in the desktop app

  • Adds a new DesktopDeepLinks service (DesktopDeepLinks.ts) that registers the t3code URL scheme at startup, listens for open-url (macOS) and second-instance (Linux/Windows) events, and holds a single pending link if no renderer is ready.
  • Adds parseDeepLinkTarget / findDeepLinkTarget utilities (deepLinkTarget.ts) to safely parse and discover deep-link targets from URLs and argv.
  • Extends DesktopWindow (DesktopWindow.ts) with dispatchDeepLink, which pushes a link to the loaded renderer over a new DEEP_LINK_CHANNEL IPC channel and reveals the window.
  • Adds a takePendingDeepLink IPC method so the renderer can pull a cold-start link on mount, and a useDesktopDeepLinks hook (useDesktopDeepLinks.ts) that handles both push and pull navigation.
  • Updates the electron-builder config (build-desktop-artifact.ts) to declare t3code as a top-level protocol for all platforms, replacing the prior mac-only block.
  • Behavioral Change: deep links are not deferred during initial renderer load — links arriving before did-finish-load are held as pending and must be pulled via IPC rather than pushed.

Macroscope summarized dc752af.

Clicking a t3code:// link did nothing outside macOS. The build config
declared the scheme only under mac, so the Linux desktop entry carried
no x-scheme-handler for xdg to resolve, and nothing consumed an
incoming URL on any platform: setAsDefaultProtocolClient had no
callers, no open-url handler existed, and the second-instance handler
dropped the argv that Linux and Windows deliver links through.

Declaring protocols at the top level covers the macOS Info.plist and
the Linux desktop entry from one source. NSIS reads it from neither, so
Windows relies on the runtime registration that DesktopDeepLinks now
performs. That service also captures links from argv, second-instance,
and open-url, and holds a cold-start link until bootstrap has opened a
renderer able to receive it.

A link is untrusted input from any web page, so only our own scheme and
host survive parsing and the renderer only ever receives a path. A
protocol-relative path is rejected because a router would read it as
another origin. Delivery goes through the renderer so navigation stays
client-side rather than reloading the window and discarding session
state.

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

coderabbitai Bot commented Jul 30, 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: 80cc140b-98e4-43c8-b512-a9d3888dbb66

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 Jul 30, 2026

@macroscopeapp macroscopeapp 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.

Reviewed the new DesktopDeepLinks service and the touched call sites against the Effect service conventions. Structure (namespace subpath imports, inline Context.Service interface, exported make/layer, environment-acquired dependencies, Effect.runPromiseWith confined to Electron native callbacks as in DesktopClerk) all look correct. One finding on log/telemetry payload safety.

Posted via Macroscope — Effect Service Conventions

return;
}

yield* logDeepLinkInfo("deep link received", { target: target.value });

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.

The logged target is untrusted external input and, per parseDeepLinkTarget, includes search and hash — so any token or credential carried in a t3code:// link's query string or fragment lands verbatim in the desktop logs. Consider logging only bounded, sanitized diagnostics (path plus presence/length indicators). The same value is also copied onto the span in DesktopWindow.dispatchDeepLink (Effect.annotateCurrentSpan({ target })), which is worth narrowing alongside it.

Suggested change
yield* logDeepLinkInfo("deep link received", { target: target.value });
yield* logDeepLinkInfo("deep link received", {
path: target.value.split(/[?#]/, 1)[0],
hasQuery: target.value.includes("?"),
hasFragment: target.value.includes("#"),
});

Posted via Macroscope — Effect Service Conventions

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in dc752af. Agreed — `parseDeepLinkTarget` keeps `search` and `hash` on purpose, so a one-time token in either would have gone to the logs verbatim.

Added `describeDeepLinkTarget` (apps/desktop/src/app/deepLinkTarget.ts), which reduces a target to `{ path, hasQuery, hasFragment }`, and both the log call and the `dispatchDeepLink` span annotation now use it — thanks for flagging the span, it would have leaked the same value into telemetry. Covered by a test that asserts neither a query token nor an `#id_token=` fragment survives.

I put the helper in a new leaf module rather than `DesktopDeepLinks.ts` because `DesktopWindow` needs it for the span and `DesktopDeepLinks` already imports `DesktopWindow`; the service module re-exports it so the public surface is unchanged.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment thread scripts/build-desktop-artifact.ts
};
}, [navigate, pathname]);

useEffect(() => {

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 components/AppSidebarLayout.tsx:185

The onDeepLink listener is registered inside AppSidebarLayout, which RootRouteView does not render on /pair, /connect, /connect/*, or unauthenticated gate states. When the desktop app receives a valid deep link while one of those screens is showing, the IPC message is dispatched but no onDeepLink listener exists, so the navigation target is silently lost and the app does not navigate. Register this listener in an always-mounted root component (or retain/retry links until the renderer acknowledges them).

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/AppSidebarLayout.tsx around line 185:

The `onDeepLink` listener is registered inside `AppSidebarLayout`, which `RootRouteView` does not render on `/pair`, `/connect`, `/connect/*`, or unauthenticated gate states. When the desktop app receives a valid deep link while one of those screens is showing, the IPC message is dispatched but no `onDeepLink` listener exists, so the navigation target is silently lost and the app does not navigate. Register this listener in an always-mounted root component (or retain/retry links until the renderer acknowledges them).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Confirmed and fixed in dc752af. `RootRouteView` returns early for `/pair`, `/connect`, `/connect/*` and for any non-authenticated/hosted-static gate state, so the listener was missing on exactly the screens a pairing link would land on.

The subscription moved into `useDesktopDeepLinks` (apps/web/src/hooks/useDesktopDeepLinks.ts), called from `RootRouteView` above those early returns, so it is mounted on every branch. It also pulls once on mount, so a link that arrived before the renderer existed is not lost either.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment thread apps/desktop/src/app/DesktopApp.ts Outdated
const desktopWindow = yield* DesktopWindow.DesktopWindow;

const scheme = getDesktopScheme(environment.isDevelopment);
const pendingTargetRef = yield* Ref.make(Option.none<string>());

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 app/DesktopDeepLinks.ts:88

captureTarget stores pending links in a single Option ref, so when multiple deep links arrive during cold startup (before any renderer exists), each Ref.set overwrites the previous pending target. The first user-requested navigation is silently lost — only the last link survives to be delivered. Consider using a queue instead of a single slot so every captured target is delivered.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/app/DesktopDeepLinks.ts around line 88:

`captureTarget` stores pending links in a single `Option` ref, so when multiple deep links arrive during cold startup (before any renderer exists), each `Ref.set` overwrites the previous pending target. The first user-requested navigation is silently lost — only the last link survives to be delivered. Consider using a queue instead of a single slot so every captured target is delivered.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Deliberate, and I have documented it rather than switching to a queue — happy to change it if you disagree.

These are navigation requests, and a queue converges on the same place: delivering A then B leaves the renderer on B, which is where delivering only B leaves it. The difference is that the queue first renders A, so the user watches a route they no longer asked for flash past. Coalescing to the newest link is the behaviour I would want as a user, and it matches how the OS treats repeated activations.

I did fix a related correctness bug you led me to, though: the clear-after-delivery step now only clears the slot if it still holds the target that was delivered, so a link arriving while a send is in flight is no longer discarded. Comment added at DesktopDeepLinks.ts:43 explaining the single slot.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment thread apps/desktop/src/window/DesktopWindow.ts
Comment thread apps/desktop/src/app/DesktopApp.ts Outdated
Comment thread apps/desktop/src/window/DesktopWindow.ts Outdated
Comment thread apps/web/src/components/AppSidebarLayout.tsx Outdated
@macroscopeapp

macroscopeapp Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

8 blocking correctness issues found. This PR introduces new deep link (URL protocol) handling - a security-sensitive feature processing external input. Multiple unresolved review comments identify a critical security vulnerability (URL bypass allowing navigation to malicious origins) and several high-severity bugs causing links to be lost or duplicated.

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

Review found the cold-start path never completed. Bootstrap only
requests the backend start; the renderer is created later from
handleBackendReady, so delivering right after bootstrap always found no
renderer and left the link pending forever. sendToMainWindow also
reported a deferred send as delivered, which let the sender discard a
link that a closing window would never receive.

The renderer now claims a pending link itself when it mounts, which is
the first moment one can be received, and a push only counts once it
reaches a loaded renderer. The listener moves to the always-rendered
root view because the sidebar layout is absent on the pairing, connect,
and unauthenticated screens, where a pushed link was dropped. Routing
passes href rather than to so a link's query and fragment survive.

Packaging declares only the production scheme. A packaged build resolves
t3code from isDevelopment being false, so advertising t3code-dev claimed
links it would then reject and outranked a dev run's registration.

Logs and spans now record a link's route shape instead of its value,
since the query or fragment of an externally supplied link can carry a
one-time token.

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

Copy link
Copy Markdown
Author

Thanks both — five of the six findings were real, and two of them (cold-start delivery, deferred-send accounting) meant deep links still would not have worked on a normal launch. Pushed dc752af with per-finding replies inline.

The shape of the fix changed as a result: delivery is now pull-on-mount rather than push-from-startup. The renderer claims a pending link through a new takePendingDeepLink IPC call when it mounts, which is the first moment a link can actually be received, so nothing depends on backend readiness or on a window having finished loading. Live links are still pushed while the app runs, and a push only counts as delivered once it reaches a loaded renderer.

Summary:

Finding Outcome
Cold-start delivery never completes Fixed — renderer pulls on mount; post-bootstrap call removed
Deferred IPC counted as delivered Fixed — sent/deferred/unavailable; deep links do not defer
Listener not mounted on /pair, /connect, gates Fixed — moved to useDesktopDeepLinks in RootRouteView
Both schemes declared, one accepted Fixed — packaging declares t3code only
Query/hash lost by navigate({ to }) Fixed — navigate({ href })
Single pending slot instead of a queue Kept, documented — coalescing is correct for navigation intent; fixed a related clear-after-delivery race

Verification: 420 desktop tests (5 new for the pull model and the log sanitiser), 684 web component tests, 210 contracts tests, vp run -r typecheck clean across 15 packages. The Linux artifact still ships MimeType=x-scheme-handler/t3code;x-scheme-handler/t3code-dev;.

@macroscopeapp macroscopeapp 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.

Effect service conventions review of the deep-link service and its call sites. The service definition (Context.Service tag with inline interface, make, layer), namespace imports, dependency acquisition from the environment, and the runPromiseWith usage at the Electron event boundary (matching DesktopClerk.configure) all look consistent with the repo's conventions. The earlier finding about the raw link target reaching logs and spans has been addressed via describeDeepLinkTarget. One minor deviation noted inline.

Posted via Macroscope — Effect Service Conventions

import * as DesktopEnvironment from "./DesktopEnvironment.ts";
import { makeComponentLogger } from "./DesktopObservability.ts";

export { describeDeepLinkTarget, findDeepLinkTarget, parseDeepLinkTarget };

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.

These pure helpers now live in deepLinkTarget.ts (which is where DesktopWindow.ts imports describeDeepLinkTarget from), so this re-export leaves two import paths for the same functions and exists only so DesktopDeepLinks.test.ts can keep calling DesktopDeepLinks.parseDeepLinkTarget. Consider dropping the shim and importing the helpers from the canonical module in the test instead.

Suggested change
export { describeDeepLinkTarget, findDeepLinkTarget, parseDeepLinkTarget };

Posted via Macroscope — Effect Service Conventions

@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.

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 dc752af. Configure here.

return () => {
unmounted = true;
unsubscribe?.();
};

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.

StrictMode drops pending deep link

Medium Severity

In development, StrictMode causes the useDesktopDeepLinks effect to run twice. The initial effect run calls takePendingDeepLink, clearing the pending deep link, but its navigation is skipped because the unmounted flag is set during cleanup. The subsequent effect run finds no pending link, causing launch-time deep links to be dropped.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dc752af. Configure here.

const delivery = yield* sendToMainWindow(DEEP_LINK_CHANNEL, target, {
deferWhileLoading: false,
});
return delivery === "sent";

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.

Deep link stuck while loading

Medium Severity

When a link arrives while the main window’s main frame is still loading, dispatchDeepLink returns without sending and leaves the link pending, but the renderer only calls takePendingDeepLink once on mount and otherwise waits for onDeepLink. If the hook already ran with nothing pending, there is no retry after load finishes and the route never opens.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dc752af. Configure here.

// registers the scheme on Windows.
if (environment.platform !== "darwin") {
yield* environment.isPackaged
? electronApp.setAsDefaultProtocolClient(scheme)

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.

🟡 Medium app/DesktopDeepLinks.ts:99

configure discards the boolean returned by electronApp.setAsDefaultProtocolClient, so a failed protocol registration is silently treated as success. On Windows, when the scheme registration fails (Electron returns false rather than throwing), t3code:// links remain unhandled with no startup error or diagnostic. Consider checking the return value and logging or failing the effect when it is false.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/app/DesktopDeepLinks.ts around line 99:

`configure` discards the boolean returned by `electronApp.setAsDefaultProtocolClient`, so a failed protocol registration is silently treated as success. On Windows, when the scheme registration fails (Electron returns `false` rather than throwing), `t3code://` links remain unhandled with no startup error or diagnostic. Consider checking the return value and logging or failing the effect when it is `false`.

}

const target = `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`;
if (!target.startsWith("/") || target.startsWith("//")) {

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 app/deepLinkTarget.ts:30

parseDeepLinkTarget only rejects targets starting with // but not /\, so a URL like t3code://app/\evil.example/phish yields the target /\evil.example/phish, passes the check, and is forwarded to the renderer. When the browser resolves a path starting with /\ against an HTTP(S) origin, it treats it like //, navigating to evil.example. This lets an untrusted deep link redirect the renderer to an attacker-controlled origin. Reject targets starting with \\ (or containing backslashes in the authority-like prefix), or resolve the target against the renderer origin and verify the resulting origin.

Suggested change
if (!target.startsWith("/") || target.startsWith("//")) {
if (!target.startsWith("/") || target.startsWith("//") || target.startsWith("/\\")) {
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/app/deepLinkTarget.ts around line 30:

`parseDeepLinkTarget` only rejects targets starting with `//` but not `/\`, so a URL like `t3code://app/\evil.example/phish` yields the target `/\evil.example/phish`, passes the check, and is forwarded to the renderer. When the browser resolves a path starting with `/\` against an HTTP(S) origin, it treats it like `//`, navigating to `evil.example`. This lets an untrusted deep link redirect the renderer to an attacker-controlled origin. Reject targets starting with `\\` (or containing backslashes in the authority-like prefix), or resolve the target against the renderer origin and verify the resulting origin.

// Tries the renderer immediately for links that arrive while the app runs.
// Anything that cannot be handed over right now stays pending for the next
// renderer to pull, so nothing is dropped on the way.
const deliverPending = Effect.gen(function* () {

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.

🟡 Medium app/DesktopDeepLinks.ts:55

deliverPending sends the pending link to the renderer and only clears the slot afterward, so during the in-flight dispatchDeepLink the renderer can call takePending and receive the same value — the deep link is dispatched twice, duplicating one-time-token navigations. The slot should be claimed atomically before delivery and restored only if delivery fails.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/app/DesktopDeepLinks.ts around line 55:

`deliverPending` sends the pending link to the renderer and only clears the slot afterward, so during the in-flight `dispatchDeepLink` the renderer can call `takePending` and receive the same value — the deep link is dispatched twice, duplicating one-time-token navigations. The slot should be claimed atomically before delivery and restored only if delivery fails.

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.

1 participant