Skip to content

Linux URL handler desktop entry quotes the Exec path, so xdg-open's generic fallback never launches the AppImage and OAuth callbacks open in the browser instead #16632

Description

@tunnckoCore

What happened

Desktop app on Linux (AppImage). Clicking Sign in (T3 Connect) opens the Clerk widget; choosing Google and completing the account picker ends in the browser asking to open the callback with xdg-open. Confirming does nothing useful: a second, empty browser window opens with the t3code:// URL, the in-app Clerk widget keeps spinning, and the user is never signed in. CLI login (bunx t3) works, so the account itself is fine.

Diagnosis

DesktopLinuxUrlHandler.ts writes the URL-handler desktop entry with a double-quoted Exec path (escapeDesktopEntryExecArgument wraps every value in "..."):

Exec="/home/<user>/.local/bin/t3code.AppImage" %U

The Desktop Entry spec allows this, but xdg-utils' shell fallback does not implement Exec quoting. On any desktop that xdg-open does not recognize (sway, Hyprland, i3, river, and so on) it takes the generic path, and search_desktop_file does:

command="$(get_key "${file}" "Exec" | first_word)"   # -> "/home/<user>/.local/bin/t3code.AppImage"  WITH the quotes
if command -v "$command" >/dev/null; then            # fails: no such command
    ... env "$command" "$@"
fi
# falls through to the $BROWSER fallback

command -v fails on the quoted string, the handler is skipped, and xdg-open falls back to $BROWSER / the default web browser, which is why a second browser window opens with the t3code:// URL. The running app never receives the callback, so the OAuth flow never completes. This is xdg-utils issues #151 and #279 (see Related); it has been open since 2019, so T3 cannot rely on it being fixed downstream.

Everything else in the registration chain was fine on this machine: the entry exists and points at the current AppImage, mimeinfo.cache lists it for x-scheme-handler/t3code, and xdg-mime query default x-scheme-handler/t3code returns com.t3tools.T3Code.desktop. Removing the quotes from Exec is the only change needed for xdg-open to launch it (verified below).

Desktops where xdg-open delegates to gio open or kde-open parse Exec correctly, which is why GNOME/KDE/COSMIC users are not hit and why #10354 was solved by the cache refresh alone.

Possible fix direction: only quote the Exec argument when it contains characters that need it (spaces, quotes, $, backticks, %), or point Exec at a small wrapper script at a quote-free path. Not patched locally.

Steps to reproduce

Standalone, no T3 needed (xdg-utils 1.2.1, with XDG_CURRENT_DESKTOP set to something xdg-open does not know, e.g. sway):

T=$(mktemp -d); mkdir -p "$T/share/applications" "$T/config"
printf '#!/bin/sh\necho "HANDLER: $*" >> %s/log\n' "$T" > "$T/handler.sh"
printf '#!/bin/sh\necho "BROWSER FALLBACK: $*" >> %s/log\n' "$T" > "$T/browser.sh"
chmod +x "$T"/*.sh
printf '[Desktop Entry]\nType=Application\nName=Quoted\nExec="%s" %%U\nMimeType=x-scheme-handler/q1;\n' "$T/handler.sh" > "$T/share/applications/q1.desktop"
printf '[Desktop Entry]\nType=Application\nName=Plain\nExec=%s %%U\nMimeType=x-scheme-handler/q2;\n'   "$T/handler.sh" > "$T/share/applications/q2.desktop"
printf '[Default Applications]\nx-scheme-handler/q1=q1.desktop\nx-scheme-handler/q2=q2.desktop\n' > "$T/config/mimeapps.list"
export XDG_DATA_HOME="$T/share" XDG_DATA_DIRS="$T/share" XDG_CONFIG_HOME="$T/config" BROWSER="$T/browser.sh" XDG_CURRENT_DESKTOP=sway
xdg-open q1://x; xdg-open q2://x; cat "$T/log"

Output:

BROWSER FALLBACK: q1://x
HANDLER: q2://x

In T3 Code:

  1. Install the Linux AppImage on a session whose desktop xdg-open treats as generic (sway here).
  2. Launch the app; it writes ~/.local/share/applications/com.t3tools.T3Code.desktop with the quoted Exec.
  3. Settings → Sign in → Google, finish the account picker in the browser.
  4. The browser prompts to open t3code://app/... with xdg-open. Confirm.
  5. A new browser window opens with the t3code:// URL; the app never receives it and the Clerk widget spins forever.

Version

0.0.46-nightly.20261004.2657 (tag v0.0.46-nightly.20261004.2657, commit efecd3cf)

Environment

NixOS, Linux 6.18.33 x86_64, sway (Wayland), xdg-utils 1.2.1, AppImage launched through nixpkgs appimage-run (bubblewrap FHS sandbox), default browser Helium 0.10.7.1 (Chromium based). Node v26.8.2.

Evidence

$ cat ~/.local/share/applications/com.t3tools.T3Code.desktop
[Desktop Entry]
Type=Application
Name=T3 Code (Nightly)
Exec="/home/<user>/.local/bin/t3code.AppImage" %U
Icon=/home/<user>/.local/share/icons/com.t3tools.T3Code.desktop.png
Terminal=false
NoDisplay=true
StartupNotify=false
MimeType=x-scheme-handler/t3code;

$ grep x-scheme-handler/t3code ~/.local/share/applications/mimeinfo.cache
x-scheme-handler/t3code=com.t3tools.T3Code.desktop;t3code-url-handler.desktop;

$ xdg-mime query default x-scheme-handler/t3code
com.t3tools.T3Code.desktop

$ BROWSER=/tmp/t/browser.sh xdg-open "t3code://app/triage-test"; cat /tmp/t/log
BROWSER FALLBACK CALLED: t3code://app/triage-test

# xdg-open 1.2.1, search_desktop_file():
command="$(get_key "${file}" "Exec" | first_word)"
if command -v "$command" >/dev/null; then

Related issues

Fix applied or workaround

Nothing changed on the machine by triage. Workaround for the user: an extra handler desktop entry with an unquoted Exec, set as the x-scheme-handler/t3code default in mimeapps.list. Because the app rewrites its own entry on every start, the workaround has to live in a separate file.

A second, machine-specific problem was also found and is not part of this issue: the user's default browser runs through a wrapper that sets HOME/XDG_DATA_HOME to a different directory, so the xdg-open it spawns does not see ~/.local/share/applications at all.

Filed by

Claude Fable 5.1 (Claude Code) via t3 triage, on behalf of the user.

Activity

  1. juliusmarminge commented on Oct 7, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Confirmed on current main (517188b).

    Root cause. The URL-handler entry always double-quotes the Exec path, even when the path needs no quoting:

    • apps/desktop/src/app/DesktopLinuxUrlHandler.ts:74-82: escapeDesktopEntryExecArgument escapes the reserved characters and then always wraps the value in "...".
    • apps/desktop/src/app/DesktopLinuxUrlHandler.ts:96: renderUrlHandlerDesktopEntry writes Exec=${escapeDesktopEntryExecArgument(execTarget)} %U.
    • Two places write this entry, so both produce the quoted form:
      • the pre-ready path, apps/desktop/src/app/DesktopPreReadyPlatform.ts:93-104, where execTarget is $APPIMAGE or process.execPath
      • the post-ready writeDesktopEntry, DesktopLinuxUrlHandler.ts:128-146

    The Desktop Entry spec allows the quoted form. xdg-utils' generic path in search_desktop_file does not unquote it, though: it runs command -v on the literal "…/t3code.AppImage", skips the handler, and falls back to $BROWSER (xdg-utils #151 and #279). On sway, Hyprland, i3, river and other desktops that xdg-open doesn't recognize, the t3code:// callback never reaches the app. GNOME, KDE and COSMIC go through gio/kde-open, which parse Exec correctly. That explains why #10354 was fixed by the cache refresh alone.

    The tests currently expect the quoting unconditionally: DesktopLinuxUrlHandler.test.ts:161-176 and :227.

    Suggested fix direction. Quote the Exec argument only when it contains characters the spec reserves: space, tab, newline, ", ', \, `, $, <, >, ~, |, &, ;, *, ?, #, (, ). Literal % still needs to be doubled either way. Paths like /home/<user>/.local/bin/t3code.AppImage would then be written bare and work with the xdg-utils fallback. Paths that really need quoting stay correct for spec-compliant launchers.

    Update the existing test at :227 to expect the unquoted form for a plain path, and keep the :176 case quoted. Both writers go through renderUrlHandlerDesktopEntry, so one change covers both.

    Related.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 7, 2026
  3. Serendeep commented on Oct 9, 2026

    @Serendeep
    Contributor

    Confirmed on another setup, with the suggested direction working as a manual workaround.

    Symptom. Desktop app, "Sign in to T3 Connect" opens the browser, sign-in completes in the browser, but the app never becomes signed in. t3 connect status kept reporting the environment as linked but authorization missing.

    Environment.

    • T3 Code 0.0.46-nightly.20261009.2861 (AppImage), Linux x64, CachyOS kernel 7.2.6
    • Desktop: niri (Wayland), XDG_CURRENT_DESKTOP=niri, no $BROWSER set
    • Browser: Zen (Firefox fork, native package), t3code scheme set to "use system default"
    • xdg-utils 1.2.1

    Evidence. The app-written entry ~/.local/share/applications/com.t3tools.T3Code.desktop has Exec="~/Applications/T3-Code-0.0.46-nightly.20261009.2861-x86_64.AppImage" %U, and xdg-mime query default x-scheme-handler/t3code returns it. Reproducing xdg-open's generic search_desktop_file check on the first word of that Exec line:

    $ command -v '"/home/<user>/Applications/T3-Code-0.0.46-nightly.20261009.2861-x86_64.AppImage"'
    (not found)
    

    so the generic path skips the handler and falls back to the browser. Network was healthy and the AppImage exists at that path; only the quoting is wrong for this launcher.

    Workaround that fixed it. A second, hand-written entry with an unquoted Exec (Exec=/home/<user>/.local/bin/t3code %U, a tiny script that execs the newest AppImage) was already registered for the scheme. Running

    xdg-mime default t3code.desktop x-scheme-handler/t3code
    

    and signing in again completed immediately. Note the app re-runs xdg-mime default com.t3tools.T3Code.desktop … on every launch (DesktopLinuxUrlHandler.ts setDefaultHandler), so the workaround is reverted at the next start and has to be repeated before each new sign-in.


    Diagnosed via t3 triage, written by Claude (Fable 5.1) in Claude Code on the user's machine.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions