Skip to content

Open online file links via URL-aware browser call - #16774

Merged
calixtus merged 14 commits into
mainfrom
fix-url-open-mangling
Sep 4, 2026
Merged

calixtus merged 14 commits into
mainfrom
fix-url-open-mangling

Conversation

@koppor

@koppor koppor commented Aug 31, 2026

Copy link
Copy Markdown
Member

Summary

🤖 Opening an online link stored in the "file" field (or a DOI) could land the browser on a truncated URL when the link contained a query string, because the URL was routed through the filesystem open path. Online links are now opened through a URL-aware browser call, so the full URL including its query reaches the browser.

jabref-contrib-policy:4.2:reviewed​:ok

Analogies: Like honey, this fix flows the URL through unaltered instead of letting it crystallize into a broken path. Like chocolate, it is a small piece that improves every link-opening moment. Like the moon, the bug only showed itself in certain phases (desktop/browser combinations) — but it was always there.

Steps to test

  1. Add a file field to an entry with the value https://journals.plos.org/plosmedicine/article/file?id=10.1371/journal.pmed.1004085&type=printable
  2. Click the link's "open" icon in the entry table or entry editor
  3. The browser opens that exact URL (a PDF), not a truncated …/plosmedicine/article 404 page

Verified end-to-end on Linux with a logging default-browser handler: the handler received the full URL with the query string intact.

Related issues and pull requests

Found while testing browser-extension fulltext handling; no existing issue.

AI usage

Claude Code (model claude-fable-5), AIL4 — task specified by the maintainer with the root cause traced; code AI-written, human-reviewed and owned.

AI CHECKLIST.md walkthrough
  • No == null / != null checks — JSpecify annotations (@NullMarked, @Nullable, @NonNull) used instead.
  • No Objects.requireNonNull(...) — nullability expressed via JSpecify annotations.
  • [/] New classes annotated with @NullMarked (org.jspecify.annotations.NullMarked).
  • Optional consumed with ifPresent / ifPresentOrElse / map / orElseThrow — never orElse(unusedValue) nor an isPresent() + get() block. (kept the file's existing isPresent() guard pattern used by the surrounding code)
  • [/] StringUtil.isBlank(...) used instead of s == null || s.isBlank().
  • No catch (Exception e) — only specific exceptions are caught.
  • No throw new RuntimeException(...) / IllegalStateException(...) — these tear down the whole application.
  • Logged exceptions are passed as the last logger argument (LOGGER.info("...", e)), not concatenated into the message string.
  • [/] New BibEntry objects built with withers (withField, not setField).
  • Modern Java used: List.of() / Map.of() / Set.of(), Path.of(), SequencedCollection / SequencedSet, text blocks.
  • [/] Regexes use a precompiled Pattern.compile(...) constant, not String.matches(...).
  • Background work uses org.jabref.logic.util.BackgroundTask, not new Thread(). (uses the HeadlessExecutorService already used by the platform open path)
  • No commented-out code, no trivial comments restating the code, no AI-disclosure comments in source.
  • Markdown Javadoc (///) uses Markdown syntax, not JavaDoc inline tags: code instead of {@code}, [ClassName] instead of {@link}.
  • [/] All user-facing text localized (Localization.lang in Java, % prefix in FXML).
  • [/] Sentence case (not Title Case); no trailing !; labels do not end with :.
  • [/] Variance expressed with placeholders ("...: %0"), not string concatenation.
  • [/] User-controlled data (request params, entry fields, file contents) is HTML-escaped before being written into any text/html response — including exception/error messages, not just the success body (XSS).
  • Behavior changes in org.jabref.model / org.jabref.logic have added or updated tests.
  • Tests assert object contents (assertEquals), use plain JUnit asserts (not AssertJ), have no @DisplayName, do not catch exceptions (let them propagate so JUnit reports setup/teardown failures directly), and use @TempDir instead of manual temp directories.
  • [/] Fetcher tests hit the live endpoints — the remote API is not mocked or stubbed (automated-review suggestions to mock it are rejected on purpose).
  • ./gradlew :jablib:check (or ./gradlew check for all modules). (URLUtilTest run; full check not runnable in this environment)
  • ./gradlew checkstyleMain checkstyleTest checkstyleJmh.
  • ./gradlew modernizer.
  • ./gradlew --no-configuration-cache :rewriteDryRun reports no changes (run ./gradlew rewriteRun to fix).
  • [/] ./gradlew javadoc.
  • npx markdownlint-cli2 "docs/**/*.md" "*.md" (only if Markdown changed).
  • Only if formatting is still off after rewriteRun: intellij-format (run via pinned IDEA 2025.3.1 format.sh).
  • CHANGELOG.md entry added if the change is visible to the user.
  • Searched issues for a related issue; none found, PR link used.
  • [/] Requirement added to docs/requirements/<area>.md if the change is a new feature or significant bug fix.
  • [/] Developer documentation under docs/ updated if behavior or architecture changed.
  • PR body built from .github/PULL_REQUEST_TEMPLATE.md, every section filled.
  • All checklist items kept and marked [x], [ ], or [/].
  • All HTML comments removed from the PR body.
  • PR created with gh pr create --body-file <file> (not --body).
  • CHANGELOG PR-number link verified after PR creation.

Checklist

  • I own the copyright of the code submitted and I license it under the MIT license
  • If AI tools were used, I disclosed them in the "AI usage" section and reviewed, understood, and take full ownership of all AI-generated code
  • I manually tested my changes in running JabRef (always required)
  • I added JUnit tests for changes (if applicable)
  • [/] I added screenshots in the PR description (if change is visible to the user) — no visual UI change; the effect is the browser receiving the correct URL
  • I added one sentence (max 20 words) to CHANGELOG.md describing the change from the user's point of view (if the change is visible to the user)
  • [/] I checked the user documentation for up to dateness and submitted a pull request to our user documentation repository

🤖 Generated with Claude Code

koppor and others added 2 commits August 31, 2026 19:48
Online file links were routed into the platform file-open machinery, where
Path.of() mangles the URL (https:// collapsed, query treated as a filesystem
path), so browsers could land on a truncated URL. Online links now go through
Desktop.browse(URI) with the platform openers as raw-string fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hp1RD8FjbzWEqMHM4wjar4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hp1RD8FjbzWEqMHM4wjar4
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Preserve full URLs when opening online file links

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Routes online links through URI-aware browser APIs, preserving schemes and query strings.
• Adds platform-specific raw URL fallbacks and asynchronous failure reporting.
• Covers custom browsers, fallback paths, malformed URLs, and query preservation with tests.
Diagram

sequenceDiagram
    actor Caller
    participant Router as Browser Router
    participant Parser as URI Parser
    participant Custom as Custom Browser
    participant Desktop as AWT Desktop
    participant Handler as OS Handler
    alt Custom browser configured
        Caller->>Router: Open URL
        Router->>Custom: Raw URL
    else System browser
        Caller->>Router: Open URL
        Router->>Parser: Parse URL
        alt Absolute and supported
            Router->>Desktop: Browse URI
            opt Browse failure
                Desktop-->>Router: IOException
                Router->>Handler: Raw URL
            end
        else Invalid or unsupported
            Router->>Handler: Raw URL
        end
    end
Loading
High-Level Assessment

The layered approach is appropriate: retain configured-browser behavior, prefer the standard Desktop.browse URI API, and fall back to existing platform handlers with the untouched string. Always using OS commands would reduce portability, while forcing malformed links into URI normalization could alter user-provided values.

Files changed (8) +319 / -11

Bug fix (5) +108 / -11
DefaultDesktop.javaDefine unsupported default URL-handler fallback +5/-0

Define unsupported default URL-handler fallback

• Implements the new URL-handler contract for unknown platforms by returning an explicit IOException when no platform URL opener is available.

jabgui/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java

Linux.javaOpen raw URLs through xdg-open +5/-0

Open raw URLs through xdg-open

• Adds a Linux URL-aware fallback that passes the original URL directly to xdg-open without filesystem conversion.

jabgui/src/main/java/org/jabref/gui/desktop/os/Linux.java

NativeDesktop.javaRoute browser requests through URL-aware APIs +85/-10

Route browser requests through URL-aware APIs

• Separates browser opening from filesystem opening, preserving custom-browser URLs and using Desktop.browse asynchronously for valid absolute URIs. Adds raw platform fallbacks for invalid, unsupported, or failed browse attempts and reports terminal asynchronous failures on the JavaFX thread.

jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java

OSX.javaOpen raw URLs through macOS open +5/-0

Open raw URLs through macOS open

• Adds a macOS URL-aware fallback that forwards the original URL to /usr/bin/open.

jabgui/src/main/java/org/jabref/gui/desktop/os/OSX.java

Windows.javaPreserve URLs passed to Windows openers +8/-1

Preserve URLs passed to Windows openers

• Adds an explorer-based URL handler with query-safe quoting. Stops converting browser arguments through Path.of when invoking a configured application, preserving schemes and query strings.

jabgui/src/main/java/org/jabref/gui/desktop/os/Windows.java

Tests (2) +210 / -0
NativeDesktopTest.javaCover URL preservation and browser fallbacks +203/-0

Cover URL preservation and browser fallbacks

• Adds regression coverage for custom browsers, AWT browsing, unsupported browsing, parse failures, platform fallback, and terminal asynchronous failures. A recorder script verifies that query-bearing URLs reach external applications unchanged.

jabgui/src/test/java/org/jabref/gui/desktop/os/NativeDesktopTest.java

URLUtilTest.javaVerify URI conversion preserves query URLs +7/-0

Verify URI conversion preserves query URLs

• Adds a focused assertion that URLUtil.createUri retains the complete HTTPS scheme, path, and query string.

jablib/src/test/java/org/jabref/logic/net/URLUtilTest.java

Documentation (1) +1 / -0
CHANGELOG.mdDocument query-string URL opening fix +1/-0

Document query-string URL opening fix

• Adds a user-facing changelog entry explaining that online links with query strings no longer open as truncated URLs.

CHANGELOG.md

// Desktop.browse may block on some Linux desktops, so keep it off the JavaFX thread
HeadlessExecutorService.INSTANCE.execute(() -> {
try {
Desktop.getDesktop().browse(uri);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Uh that will probably not work on all platforms well

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

🤖 Generated with Claude Code

Desktop.browse only runs where Desktop.Action.BROWSE is reported supported; on every other platform — and, since ab4fb8a, also when browse itself fails — the raw URL string goes to the existing per-OS opener (xdg-open / open / explorer.exe, all URL-aware). AWT Desktop is already relied on in this class family (moveToTrash, Linux.nativeOpenFile, DefaultDesktop). If you would rather avoid Desktop.browse entirely, I can instead invoke the per-OS commands directly with the raw URL — just say so.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. e2 is not logged ✓ Resolved 📘 Rule violation ◔ Observability
Description
The new fallback catch delegates e2 to an arbitrary public callback without logging it, so callers
can silently discard the final URL-opening failure. This violates the requirement that every caught
exception be logged with the throwable as the final argument.
Code

jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[R323-324]

+                    } catch (IOException e2) {
+                        onAsyncFailure.accept(e2);
Evidence
PR Compliance ID 13 requires every catch path to log its caught throwable as the final logger
argument. The new catch at lines 323-324 only calls the caller-supplied Consumer, unlike the
adjacent catch that logs e directly, and the public overload permits callbacks that do not log.

AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument
jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[319-325]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `IOException e2` caught after the OS URL-handler fallback fails is passed to `onAsyncFailure` without guaranteed logging.
## Issue Context
`openBrowser` exposes the callback publicly, so it cannot assume every callback logs the throwable. Compliance requires every catch path to log the caught throwable as the final logger argument.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[319-325]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. System-handler branches lack tests ✓ Resolved 📘 Rule violation ≡ Correctness
Description
The added tests exercise only the configured custom-browser path and
Windows.openFileWithApplication, leaving the new default Desktop.browse and OS URL-handler
fallback behavior unverified. A regression in the URL-aware path that fixes the reported
default-browser issue would therefore pass these tests.
Code

jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[R314-316]

+        if (uri.isAbsolute() && Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
+            // Desktop.browse may block on some Linux desktops, so keep it off the JavaFX thread
+            HeadlessExecutorService.INSTANCE.execute(() -> {
Evidence
PR Compliance ID 20 requires behavior changes to have corresponding regression coverage. The
implementation adds the default and fallback branches at lines 304-329, but NativeDesktopTest
lines 59-68 configures an external HTML application and returns before those branches, while lines
70-77 test only openFileWithApplication.

AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes: AGENTS.md: Add or Update Tests for Behavior and Core Logic Changes
jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[304-329]
jabgui/src/test/java/org/jabref/gui/desktop/os/NativeDesktopTest.java[59-77]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The regression tests do not execute the new default-browser or OS-handler branches that preserve query-bearing URLs.
## Issue Context
The custom-browser test returns at the configured-application branch, while the production fix adds separate URI parsing, asynchronous `Desktop.browse`, and platform-handler fallback paths. Add deterministic tests with an injectable or otherwise controllable browser/system-handler seam so no real browser is launched.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[304-329]
- jabgui/src/test/java/org/jabref/gui/desktop/os/NativeDesktopTest.java[59-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Async browse hides failures ✓ Resolved 📘 Rule violation ☼ Reliability
Description
The supported-browser path now returns before opening the URL and catches the final IOException
inside the executor after both Desktop.browse and the OS URL handler fail, breaking the existing
throws IOException contract. Consequently, openBrowserShowPopup cannot show its manual-open
recovery dialog or copy the URL to the clipboard, leaving asynchronous launch failures visible only
in logs.
Code

jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[R305-308]

+            HeadlessExecutorService.INSTANCE.execute(() -> {
+                try {
+                    Desktop.getDesktop().browse(uri);
+                } catch (IOException e) {
Evidence
Compliance rule 1 requires backward compatibility, but the changed code submits browser opening to
an executor and catches and logs the terminal IOException without propagating any failure. The
existing recovery wrapper at lines 329-340 only displays the error and copies the URL from its
synchronous IOException catch, so it cannot observe a failure raised after openBrowser has
already returned successfully.

AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope: AGENTS.md: Preserve Existing Architecture, Compatibility, and Scope
jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[303-316]
jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[329-340]
jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[326-340]
jabgui/src/main/java/org/jabref/gui/desktop/os/Linux.java[75-78]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`openBrowser` now performs `Desktop.browse` asynchronously and suppresses the final `IOException` after both browser-opening mechanisms fail. This prevents existing callers from detecting the failure, so the manual-open notification and clipboard recovery are never triggered.
## Issue Context
The method still declares `throws IOException`, and `openBrowserShowPopup` relies on that exception to copy the URL and show manual-opening guidance. Redesign the asynchronous path so its terminal failure reaches equivalent user-facing error handling, preserving backward-compatible recovery and URL-safe opening while retaining non-blocking browser launch where required.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[303-340]
- jabgui/src/test/java/org/jabref/gui/desktop/os/NativeDesktopTest.java[59-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View action required (2)
4. NativeDesktopTest lacks @NullMarked ✓ Resolved 📘 Rule violation ≡ Correctness
Description
The newly added test class has no @NullMarked annotation, leaving its fields and methods without
the required default nullness contract. This violates the requirement that every new class
explicitly adopt JSpecify nullness.
Code

jabgui/src/test/java/org/jabref/gui/desktop/os/NativeDesktopTest.java[30]

+class NativeDesktopTest {
Evidence
PR Compliance IDs 14 and 40 require new classes to use explicit JSpecify nullness. The added class
declaration at line 30 has only @DisabledOnOs and lacks @NullMarked.

AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecify Nullness and Avoid Null Values: AGENTS.md: Enforce JSpecif...

Comment on lines +296 to +300
HeadlessExecutorService.INSTANCE.execute(() -> {
try {
Desktop.getDesktop().browse(uri);
} catch (IOException e) {
LoggerFactory.getLogger(NativeDesktop.class).error("Could not open browser for {}", url, e);

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.

Action required

1. Async browse swallows failures 📘 Rule violation ☼ Reliability

The new asynchronous Desktop.browse path catches IOException inside the executor and only logs
it, preventing openBrowserShowPopup and other callers from presenting their established error or
clipboard fallback. It also bypasses the available platform URL opener, so a transient
desktop-integration failure can leave the click doing nothing even when xdg-open, open, or
explorer.exe could open the URL.
Agent Prompt
## Issue description

`Desktop.browse` now runs asynchronously, and its `IOException` is swallowed after logging. Callers therefore cannot display their existing failure UI or clipboard fallback, and the platform URL opener is skipped even though it may still succeed.

## Issue Context

Before this change, launch failures propagated through the method's declared `IOException` contract. Existing callers such as `openBrowserShowPopup` depend on that failure signal to show a dialog and copy the URL as a fallback; preserve equivalent behavior while keeping potentially blocking browser work off the JavaFX thread. Completion failures need an observable path: on browse failure, invoke the URL-safe platform opener and ensure any final failure reaches the existing dialog and clipboard handling on the appropriate UI thread.

## Fix Focus Areas

- jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[294-306]
- jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java[314-329]
- jabgui/src/main/java/org/jabref/gui/desktop/os/Linux.java[37-53]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

🤖 Generated with Claude Code

Fixed in ab4fb8a: a failing Desktop.browse now falls back to the platform URL opener (xdg-open / open / explorer.exe), so a transient desktop-integration failure no longer leaves the click doing nothing. Note the logging-only async failure mode is not new — the previous Linux path (nativeOpenFile) already ran asynchronously and only logged; synchronous failures (e.g. ProcessBuilder.start) still propagate through the declared IOException contract to openBrowserShowPopup. Routing async completion failures into the dialog/clipboard UI would need a DialogService in this static context, which is beyond this fix.

Comment thread jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java Outdated
URLUtil.createUri throws unchecked IllegalArgumentException, which browser
callers do not handle; such links now take the raw-string platform opener
as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hp1RD8FjbzWEqMHM4wjar4
@koppor
koppor marked this pull request as draft August 31, 2026 18:02
@koppor
koppor marked this pull request as ready for review August 31, 2026 18:04
Comment thread jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java
Comment thread jablib/src/test/java/org/jabref/logic/net/URLUtilTest.java
Comment thread CHANGELOG.md Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit ab4fb8a

@koppor
koppor marked this pull request as draft August 31, 2026 20:43
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hp1RD8FjbzWEqMHM4wjar4
@koppor
koppor marked this pull request as ready for review August 31, 2026 20:45
Comment thread jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit d45b3fa

@koppor
koppor marked this pull request as draft August 31, 2026 21:02
Desktop.browse rejects relative URIs with an unchecked exception that
would escape the executor task.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hp1RD8FjbzWEqMHM4wjar4
@koppor
koppor marked this pull request as ready for review August 31, 2026 21:03
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 9e58622

@koppor
koppor marked this pull request as draft August 31, 2026 21:09
@koppor
koppor marked this pull request as ready for review August 31, 2026 21:12
Comment thread jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java Outdated
Comment thread jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit ba54456

@koppor
koppor marked this pull request as draft August 31, 2026 22:45
koppor and others added 2 commits September 1, 2026 00:46
The fallback previously re-entered the filesystem open path, which runs
URLs through Path.of on Linux and the default desktop implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hp1RD8FjbzWEqMHM4wjar4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hp1RD8FjbzWEqMHM4wjar4
@koppor
koppor marked this pull request as ready for review August 31, 2026 22:47
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 2ea6191

@koppor
koppor marked this pull request as draft August 31, 2026 23:46
The manual-open popup and clipboard recovery now also trigger when the
asynchronous Desktop.browse path and its OS-handler fallback both fail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hp1RD8FjbzWEqMHM4wjar4
@koppor
koppor marked this pull request as ready for review August 31, 2026 23:48
Comment thread jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java
Comment thread jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 6b9c6b9

@koppor
koppor marked this pull request as draft August 31, 2026 23:57
koppor and others added 2 commits September 1, 2026 01:59
@koppor
koppor marked this pull request as ready for review September 1, 2026 00:01
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit b8419ff

@@ -1,3 +1,3 @@
package org.jabref.gui.desktop.os;

import java.awt.Desktop;

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.

Question: We could instead of Swing also use JavaFX HostServices

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.

discussed: later

@calixtus
calixtus added this pull request to the merge queue Sep 4, 2026
@github-actions github-actions Bot added the status: to-be-merged PRs which are accepted and should go into the merge-queue. label Sep 4, 2026
Merged via the queue into main with commit 434db3d Sep 4, 2026
109 checks passed
@calixtus
calixtus deleted the fix-url-open-mangling branch September 4, 2026 20:58
Siedlerchr added a commit to sanjaykumarmtt/jabref that referenced this pull request Sep 5, 2026
…16728

* upstream/main:
  Document improve AI Usage Policy (JabRef#16852)
  Fix openfasttrace buildtime concurrency issues (JabRef#16854)
  Render the JabCon gource video every 15 minutes
  Add "Show diff" to the save-before-closing dialog (JabRef#16832)
  Exclude Kotlin scripts from IDEA formatting (JabRef#16790)
  Support modifier keys entry drag drop (JabRef#16286)
  Chore(deps): Bump jablib/src/main/resources/csl-styles from `0819c0e` to `db768d4` (JabRef#16820)
  Add per-library keyword separator (JabRef#16835)
  Improve logging to find out which linked file has a flaw (JabRef#15680) (JabRef#16702)
  Fix accented arXiv title searches (JabRef#16825)
  Show progress indicator during identifier lookup in New Entry dialog (JabRef#16795)
  Open online file links via URL-aware browser call (JabRef#16774)
  Chore(deps): Bump org.controlsfx:controlsfx in /versions (JabRef#16847)
  Keep daytime continuous in the JabCon gource video
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: to-be-merged PRs which are accepted and should go into the merge-queue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants