Skip to content

Fix/16679 ocr failure command output - #16682

Open
Fayupable wants to merge 4 commits into
JabRef:mainfrom
Fayupable:fix/16679-ocr-failure-command-output
Open

Fix/16679 ocr failure command output#16682
Fayupable wants to merge 4 commits into
JabRef:mainfrom
Fayupable:fix/16679-ocr-failure-command-output

Conversation

@Fayupable

Copy link
Copy Markdown

Summary

Surfaces the executed command and its captured stdout/stderr in the OCR failure dialog, so a failure gives something to debug instead of a generic message like "OCR process failed". OcrResult.Failure now carries the command line and output; OcrUtils.performOcr captures the process output into a buffer and waits for the output-draining task via HeadlessExecutorService before reading it, avoiding a race between that read and the still-running output-draining task.

Steps to test

  1. Configure an OCR engine path that will fail (e.g. point it at a binary that exits non-zero, or an invalid path).
  2. Try to OCR a linked PDF file from an entry.
  3. The resulting error dialog now shows the command that was run and its output, in addition to the existing failure reason.

Covered by OcrUtilsTest (added), which exercises the IO_ERROR and NON_ZERO_EXIT paths and asserts the command line and output are captured correctly.

Related issues and pull requests

Closes #16679

AI usage

Claude Code (model claude-sonnet-5), AIL3 — AI wrote the implementation and test under my direction; I reviewed the diff, understand it, and take ownership of it.

AI CHECKLIST.md walkthrough

1. Code self-review

Nullability and control flow

  • No ==null/!=null — still present (gobblerFuture == null, mirrors the pre-existing Process process = null pattern in this file; this file isn't @NullMarked yet, so converting only the touched lines to JSpecify would be inconsistent with the rest of the class)
  • No Objects.requireNonNull(...)
  • [/] New classes @NullMarked — no new classes added
  • [/] Optional handling — not used in this diff
  • [/] StringUtil.isBlank(...) — we use .isEmpty() on a non-null String field (never null by construction), not a null-or-blank check

Exceptions

  • No catch (Exception e) — only IOException/InterruptedException/ExecutionException/TimeoutException
  • No RuntimeException/IllegalStateException thrown
  • Logged exceptions passed as last logger argument

Style and idioms

  • [/] BibEntry withers — not applicable
  • Modern Java (List.of() used in the test)
  • [/] Precompiled Pattern — no regex
  • Background work uses HeadlessExecutorService (project's executor wrapper), not raw new Thread()
  • No commented-out code, no AI-disclosure comments in source
  • /// Markdown Javadoc used, no inline {@code}/{@link}

User-facing text

  • Localization.lang used for the new "Command"/"Output" labels
  • Sentence case, no trailing !, no trailing :
  • No string concatenation for user-facing variance

Security

  • [/] Not applicable — this is a desktop error dialog, not an HTML response

Tests

  • Added OcrUtilsTest covering the IO_ERROR, NON_ZERO_EXIT, and success paths
  • Plain JUnit asserts, no AssertJ, no @DisplayName, exceptions not caught in tests
  • [/] Fetcher tests — not applicable, no fetcher involved

2. Verification commands

  • ./gradlew :jablib:test --tests "org.jabref.logic.ocr.OcrUtilsTest" — 3/3 passed
  • ./gradlew checkstyleMain checkstyleTest checkstyleJmh — not run
  • ./gradlew modernizer — not run
  • ./gradlew --no-configuration-cache :rewriteDryRun — not run
  • ./gradlew javadoc — not run
  • [/] markdownlint — no Markdown changed

3. Documentation

4. Pull request

  • Built from the PR template
  • Checklist items kept and marked honestly
  • HTML comments removed
  • gh pr create --body-file — will be used to open the PR
  • [/] No CHANGELOG.md TODO placeholder used — a real entry was added directly

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
  • I added JUnit tests for changes
  • [/] I added screenshots in the PR description
  • I added one sentence to CHANGELOG.md
  • [/] I checked the user documentation

Surface the executed command and its captured stdout/stderr in the OCR
failure dialog, so failures give something to debug instead of just
"OCR process failed" or similar. OcrResult.Failure now carries the
command line and output; OcrUtils.performOcr captures the process
output into a buffer and joins the reader thread before returning, to
avoid a race between it and the failure result being read.
Fixes JabRef#16679
Surface the executed command and its captured stdout/stderr in the OCR
failure dialog, so failures give something to debug instead of just
"OCR process failed" or similar. OcrResult.Failure now carries the
command line and output; OcrUtils.performOcr captures the process
output into a buffer and waits for the output-draining task via
HeadlessExecutorService before returning, avoiding a race between it
and the failure result being read.
Also switched the output-draining task from a raw new Thread() to
HeadlessExecutorService after re-checking CHECKLIST.md caught that
this project avoids new Thread() for background work.

Fixes JabRef#16679
Adds unit tests for the IO_ERROR, NON_ZERO_EXIT, and success paths of
OcrUtils.performOcr, verifying the captured command line and output.
Also adds the CHANGELOG.md entry required by CONTRIBUTING.md, which
the previous commits were missing.
@github-actions

Copy link
Copy Markdown
Contributor

Hey @Fayupable! 👋

Thank you for contributing to JabRef!

We have automated checks in place, based on which you will soon get feedback if any of them are failing. We also use Qodo for review assistance. It will update your pull request description with a review help and offer suggestions to improve the pull request.

After all automated checks pass, a maintainer will also review your contribution. Once that happens, you can go through their comments in the "Files changed" tab and act on them, or reply to the conversation if you have further inputs. You can read about the whole pull request process in our contribution guide.

Please ensure that your pull request is in line with our AI Usage Policy and make necessary disclosures.

@github-actions github-actions Bot added first contrib good first issue An issue intended for project-newcomers. Varies in difficulty. labels Aug 25, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Surface OCR command output in failure dialogs

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

Grey Divider

AI Description

• Capture executed OCR commands and merged process output for failed runs.
• Wait for asynchronous output draining before constructing failure results.
• Show localized diagnostics in dialogs and cover failure paths with tests.
Diagram

sequenceDiagram
    actor User
    participant Action as OCR Action
    participant Engine as OCR Engine
    participant Utils as OCR Utils
    participant Process as OCR Process
    participant Gobbler as Output Gobbler
    participant Dialog as Error Dialog
    User->>Action: Start OCR
    Action->>Engine: Process PDF
    Engine->>Utils: Run command
    Utils->>Process: Start process
    Process-->>Gobbler: stdout and stderr
    Utils->>Gobbler: Await completion
    Gobbler-->>Utils: Captured output
    Utils-->>Engine: Failure details
    Engine-->>Action: OCR failure
    Action->>Dialog: Localized diagnostics
    Dialog-->>User: Command and output
Loading
High-Level Assessment

The chosen approach is appropriate: it extends the existing typed result boundary, keeps localization in the GUI, and continues draining process output asynchronously to avoid pipe backpressure. Synchronous post-exit reads could deadlock on large output, while temporary-file redirection would add unnecessary filesystem handling.

Files changed (6) +131 / -10

Bug fix (4) +76 / -10
OcrLinkedFileAction.javaShow OCR command diagnostics in error dialogs +18/-1

Show OCR command diagnostics in error dialogs

• Builds the localized failure message from the existing reason and appends the command line and captured output when available. Failures without command details retain the prior message.

jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java

OcrResult.javaEnrich OCR failure results with diagnostics +11/-3

Enrich OCR failure results with diagnostics

• Extends 'OcrResult.Failure' with command-line and output fields. Adds an overloaded factory while preserving reason-only failures with empty diagnostic values.

jablib/src/main/java/org/jabref/logic/ocr/OcrResult.java

OcrUtils.javaCapture complete OCR process output safely +45/-6

Capture complete OCR process output safely

• Captures merged stdout and stderr while the OCR command runs and includes it in timeout, non-zero-exit, I/O, and interruption failures. Waits briefly for the executor-backed output gobbler before reading its buffer to avoid incomplete or concurrent reads.

jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java

JabRef_en.propertiesAdd labels for OCR diagnostic details +2/-0

Add labels for OCR diagnostic details

• Adds English localization keys for the command and output sections shown in OCR failure dialogs.

jablib/src/main/resources/l10n/JabRef_en.properties

Tests (1) +54 / -0
OcrUtilsTest.javaCover OCR command execution outcomes +54/-0

Cover OCR command execution outcomes

• Adds tests for missing commands, non-zero exits with captured output, and successful execution. Shell-dependent cases are disabled on Windows.

jablib/src/test/java/org/jabref/logic/ocr/OcrUtilsTest.java

Documentation (1) +1 / -0
CHANGELOG.mdDocument improved OCR failure diagnostics +1/-0

Document improved OCR failure diagnostics

• Adds a fixed-issue entry explaining that failed OCR attempts now expose the executed command and output for troubleshooting.

CHANGELOG.md

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

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

Copy link
Copy Markdown
Contributor

Code Review by Qodo

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

Grey Divider


Action required

1. Gobbler interruption permits racy read 📎 Requirement gap ☼ Reliability
Description
awaitGobblerQuietly returns after an interrupted Future.get without ensuring the gobbler
stopped, so performOcr can read the unsynchronized StringBuilder while output is still appended.
The resulting OCR failure dialog can contain incomplete or corrupted diagnostic output, and the
caught interruption is not logged.
Code

jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[R116-117]

+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
Evidence
Rule 1 requires OCR failures to show the command result/output, while Rule 13 requires every caught
exception to be logged. The changed helper catches InterruptedException, only restores the flag,
and returns; the gobbler task independently appends to outputBuilder, which callers then
immediately convert to a string.

Show OCR command and result when OCR fails
AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument: AGENTS.md: Log Caught Exceptions with the Throwable as the Final Argument
jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[65-71]
jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[114-119]

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

## Issue description
An interruption while awaiting the output gobbler is swallowed after restoring the interrupt flag, allowing callers to read `outputBuilder` before the gobbler has completed and leaving the caught exception unlogged.
## Issue Context
The failure result must contain deterministic captured output, and every caught exception must be logged with the throwable as the final argument. Refactor the waiting/cleanup flow so output is never read concurrently, while preserving interruption semantics.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[99-119]

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


2. Failure fields lack null contracts 📘 Rule violation ≡ Correctness
Description
The new public commandLine and output record components are added in code without a null-marked
scope or explicit JSpecify annotations. Callers therefore cannot rely on the non-null contract
required before invoking methods such as isEmpty().
Code

jablib/src/main/java/org/jabref/logic/ocr/OcrResult.java[20]

+    record Failure(OcrFailureReason reason, String commandLine, String output) implements OcrResult {
Evidence
Rules 11 and 34 require new public API/data members to express nullability using JSpecify. The
changed Failure record introduces two public String components without @NullMarked, @NonNull,
or nullable normalization.

AGENTS.md: Use Optional Idiomatically and Avoid Null-Based APIs: AGENTS.md: Use Optional Idiomatically and Avoid Null-Based APIs
jablib/src/main/java/org/jabref/logic/ocr/OcrResult.java[20-20]
jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[127-140]
Best Practice: Learned patterns

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 newly introduced `commandLine` and `output` public record components have unspecified nullability.
## Issue Context
The implementation constructs non-null strings and GUI callers immediately dereference them, so encode that contract using the repository's JSpecify conventions, preferably by establishing an appropriate null-marked scope.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/OcrResult.java[5-46]

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


3. Localization keys miss locale bundles 📘 Rule violation ⚙ Maintainability
Description
The new Command and Output keys were added only to the English bundle, while every translated
JabRef_.properties bundle lacks them. This violates the required key synchronization and causes
non-English locales to fall back instead of providing corresponding translations.
Code

jablib/src/main/resources/l10n/JabRef_en.properties[R785-786]

+Command=Command
+Output=Output
Evidence
Rule 33 requires every key used by code to exist consistently in the English and all translated
bundles. Repository-wide searches find the exact new assignments only in JabRef_en.properties,
while the GUI now calls both keys.

jablib/src/main/resources/l10n/JabRef_en.properties[785-786]
jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[131-140]
Best Practice: Learned patterns

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 new localization keys exist in the English source bundle but are absent from translated locale bundles.
## Issue Context
Keep the exact `Command` and `Output` keys synchronized across all locale property files according to the repository's localization workflow.
## Fix Focus Areas
- jablib/src/main/resources/l10n/JabRef_en.properties[785-786]
- jablib/src/main/resources/l10n/JabRef_ar.properties[1-1]
- jablib/src/main/resources/l10n/JabRef_de.properties[1-1]

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


View action required (1)
4. Changelog entry exceeds limit ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The new changelog sentence contains 29 words, exceeding the mandated maximum of 20 words. It must be
shortened while retaining the user-visible OCR fix and existing issue link.
Code

CHANGELOG.md[91]

+- We fixed an issue where a failed OCR attempt only showed a generic error message; the executed command and its output are now shown to help diagnose the failure. [#16679](https://github.com/JabRef/jabref/issues/16679)
Evidence
Rule 30 limits a user-visible changelog sentence to at most 20 words. The added sentence has 29
words before its issue reference.

AGENTS.md: Add a Properly Formatted Changelog Entry Only for User-Visible Changes: AGENTS.md: Add a Properly Formatted Changelog Entry Only for User-Visible Changes
CHANGELOG.md[91-91]

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 OCR changelog entry exceeds the 20-word maximum required for user-visible changes.
## Issue Context
Keep one sentence beginning with `We fixed`, describe only the user effect, remain under `Fixed`, and preserve the existing #16679 issue link.
## Fix Focus Areas
- CHANGELOG.md[91-91]

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



Remediation recommended

5. Gobbler race remains 🐞 Bug ☼ Reliability
Description
awaitGobblerQuietly suppresses its five-second timeout, after which performOcr immediately calls
toString() on the same non-thread-safe StringBuilder the still-running gobbler may be appending
to. A slow or pipe-inheriting process can therefore produce incomplete or inconsistent failure
output despite this change's stated race avoidance.
Code

jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[115]

+            gobblerFuture.get(5, TimeUnit.SECONDS);
Evidence
The output callback mutates a plain StringBuilder on an executor task; Future.get is capped at
five seconds and its timeout is swallowed, while callers then read that builder. StreamGobbler
confirms that callback execution continues until the stream reaches EOF.

jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[56-93]
jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[108-120]
jablib/src/main/java/org/jabref/logic/util/HeadlessExecutorService.java[69-75]
jablib/src/main/java/org/jabref/logic/util/StreamGobbler.java[24-30]

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 bounded gobbler wait can return while the asynchronous task is still mutating `outputBuilder`, so subsequent reads remain racy and may return incomplete output.
## Issue Context
`StreamGobbler` invokes the append callback from the shared executor. Swallowing `TimeoutException` does not establish task completion or safe publication.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[65-82]
- jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[85-120]

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


6. Captured output is unbounded ✓ Resolved 🐞 Bug ☼ Reliability
Description
Every output line is retained in outputBuilder for the full OCR run with no size limit, so a noisy
or malfunctioning configured OCR process can exhaust the application heap before the five-minute
process timeout. The same unbounded value is then copied into the failure record and error-dialog
message, increasing the memory and UI impact.
Code

jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[R65-67]

+            StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), line -> {
+                LOGGER.debug(line);
+                outputBuilder.append(line).append(System.lineSeparator());
Evidence
The newly added callback appends every line to one StringBuilder; OCR can run for up to the
configured five-minute timeout, and the configured executable and file arguments are passed to
performOcr. The GUI appends the entire captured value to the error message.

jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[53-82]
jablib/src/main/java/org/jabref/logic/ocr/OcrMyPdfEngine.java[48-53]
jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[131-140]

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

## Issue description
OCR stdout/stderr is accumulated without a cap, allowing an external process to consume arbitrary heap and create an unusably large failure dialog.
## Issue Context
Continue draining the process pipe to avoid blocking it, but retain only a documented bounded amount and indicate when output was truncated.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[55-68]
- jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[136-140]

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


7. Command loses argument boundaries ✓ Resolved 🐞 Bug ◔ Observability
Description
String.join(" ", command) does not quote or escape individual ProcessBuilder arguments, so paths
or options containing spaces are displayed as a different, ambiguous command. OCR input/output paths
are separate arguments and commonly contain spaces, making the new diagnostic command unreliable to
copy or reproduce.
Code

jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[55]

+        String commandLine = String.join(" ", command);
Evidence
performOcr passes the list directly to ProcessBuilder but records it using a plain space join.
Both OCR engines append input and output paths as distinct list elements, while the GUI presents the
flattened value as the executed command.

jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[54-62]
jablib/src/main/java/org/jabref/logic/ocr/OcrMyPdfEngine.java[48-53]
jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[55-66]
jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[127-140]

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 displayed command flattens the argument list without preserving argument boundaries, producing misleading diagnostics for paths containing whitespace or special characters.
## Issue Context
The actual process receives a list of arguments. Format that list with clear, platform-appropriate quoting or display each argument distinctly without changing process execution.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[54-55]
- jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[131-135]

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


Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +116 to +117
} catch (InterruptedException e) {
Thread.currentThread().interrupt();

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. Gobbler interruption permits racy read 📎 Requirement gap ☼ Reliability

awaitGobblerQuietly returns after an interrupted Future.get without ensuring the gobbler
stopped, so performOcr can read the unsynchronized StringBuilder while output is still appended.
The resulting OCR failure dialog can contain incomplete or corrupted diagnostic output, and the
caught interruption is not logged.
Agent Prompt
## Issue description
An interruption while awaiting the output gobbler is swallowed after restoring the interrupt flag, allowing callers to read `outputBuilder` before the gobbler has completed and leaving the caught exception unlogged.

## Issue Context
The failure result must contain deterministic captured output, and every caught exception must be logged with the throwable as the final argument. Refactor the waiting/cleanup flow so output is never read concurrently, while preserving interruption semantics.

## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[99-119]

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

/// Contains the reason why the failure occurred that the GUI part can localize it and output to the user,
/// plus the command line that was executed and its captured output, when available, so the GUI can show
/// them for debugging. Both are empty strings when the failure occurred before a command could be run.
record Failure(OcrFailureReason reason, String commandLine, String output) implements OcrResult {

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

2. Failure fields lack null contracts 📘 Rule violation ≡ Correctness

The new public commandLine and output record components are added in code without a null-marked
scope or explicit JSpecify annotations. Callers therefore cannot rely on the non-null contract
required before invoking methods such as isEmpty().
Agent Prompt
## Issue description
The newly introduced `commandLine` and `output` public record components have unspecified nullability.

## Issue Context
The implementation constructs non-null strings and GUI callers immediately dereference them, so encode that contract using the repository's JSpecify conventions, preferably by establishing an appropriate null-marked scope.

## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/OcrResult.java[5-46]

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

Comment on lines +785 to +786
Command=Command
Output=Output

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

3. Localization keys miss locale bundles 📘 Rule violation ⚙ Maintainability

The new Command and Output keys were added only to the English bundle, while every translated
JabRef_<lang>.properties bundle lacks them. This violates the required key synchronization and
causes non-English locales to fall back instead of providing corresponding translations.
Agent Prompt
## Issue description
The new localization keys exist in the English source bundle but are absent from translated locale bundles.

## Issue Context
Keep the exact `Command` and `Output` keys synchronized across all locale property files according to the repository's localization workflow.

## Fix Focus Areas
- jablib/src/main/resources/l10n/JabRef_en.properties[785-786]
- jablib/src/main/resources/l10n/JabRef_ar.properties[1-1]
- jablib/src/main/resources/l10n/JabRef_de.properties[1-1]

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

Comment thread CHANGELOG.md Outdated
return;
}
try {
gobblerFuture.get(5, TimeUnit.SECONDS);

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.

Remediation recommended

5. Gobbler race remains 🐞 Bug ☼ Reliability

awaitGobblerQuietly suppresses its five-second timeout, after which performOcr immediately calls
toString() on the same non-thread-safe StringBuilder the still-running gobbler may be appending
to. A slow or pipe-inheriting process can therefore produce incomplete or inconsistent failure
output despite this change's stated race avoidance.
Agent Prompt
## Issue description
The bounded gobbler wait can return while the asynchronous task is still mutating `outputBuilder`, so subsequent reads remain racy and may return incomplete output.

## Issue Context
`StreamGobbler` invokes the append callback from the shared executor. Swallowing `TimeoutException` does not establish task completion or safe publication.

## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[65-82]
- jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java[85-120]

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

Comment thread jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java Outdated
Comment thread jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java Outdated
@github-actions github-actions Bot added the status: changes-required Pull requests that are not yet complete label Aug 25, 2026

@subhramit subhramit left a comment

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.

@koppor can you try out this PR and see what error message you get?

@ZiadAbdElFatah this needs a deeper look, perhaps. Seems more complex than needed?


/// Waits briefly for the output-gobbler task to finish, so its buffered output is safe to
/// read afterward. A null future (the process never started) is a no-op.
private static void awaitGobblerQuietly(Future<?> gobblerFuture) {

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.

This seems really unnecessary for a synchronous task.

I addressed feedback from Qodo's automated review and @subhramit on
PR JabRef#16682:

- I collapsed the two separate waits (process.waitFor() + a fixed 5s
  gobbler wait) into a single wait on the gobbler future using the
  real timeout, closing the race window and answering the "why is
  this async for a synchronous task" question.
- I capped captured output at 3000 characters. The finding didn't
  specify a number, so I picked one generous enough for a typical
  failure log without letting it grow unbounded.
- I quoted command arguments containing whitespace so paths with
  spaces don't read as extra arguments.
- I shortened the CHANGELOG.md entry to fit the 20-word limit.

I left two things unaddressed, with my reasoning in PR comments:
- I didn't add JSpecify @NullMarked on OcrResult. This is an existing
  file I didn't write, and I couldn't confirm NullAway actually
  enforces it for this package, so I didn't want to add an annotation
  I couldn't verify the effect of.
- I didn't add the missing translations for the new localization keys
  in non-English bundles. I think this is expected, since this
  project pulls translations from Crowdin rather than contributors
  adding them by hand, but I'll confirm with a maintainer.
@jabref-machine

Copy link
Copy Markdown
Collaborator

You ticked that you modified CHANGELOG.md, but no new entry was found there.

If you made changes that are visible to the user, please add a brief description to the CHANGELOG.md file, along with the issue number when one exists; otherwise link the pull request. If you did not, please replace the cross ([x]) by a slash ([/]) to indicate that no CHANGELOG.md entry is necessary. More details can be found in our Developer Documentation about the changelog.

@jabref-machine

Copy link
Copy Markdown
Collaborator

Your code currently does not meet JabRef's code guidelines. IntelliJ auto format covers some cases. There seem to be issues with your code style and autoformat configuration. Please reformat your code (Ctrl+Alt+L) and commit, then push.

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

Labels

component: external-files component: internationalization i18n component: ocr first contrib good first issue An issue intended for project-newcomers. Varies in difficulty. status: changes-required Pull requests that are not yet complete

Projects

None yet

Development

Successfully merging this pull request may close these issues.

If OCR failed, there should be the command line shown

3 participants