-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
.pr_agent_accepted_suggestions
| PR 17000 (2026-09-08) |
[reliability] Fork runs can cancel repository fixes
Fork runs can cancel repository fixes
`concurrency.group` keys every `workflow_run` only by `head_branch` and event name while `cancel-in-progress` is enabled. A completed Source Code Tests run from any fork with the same branch name enters concurrency before the same-repository job guard applies, so it can cancel an active auto-fix and leave its changes unpushed.Workflow runs from unrelated forks can cancel an active auto-fix when their head branches have the same name.
The workflow triggers for every completed Source Code Tests run, but its concurrency group does not distinguish the head repository. The same-repository restriction is a job condition and therefore does not prevent a fork-triggered workflow run from participating in workflow-level concurrency.
- .github/workflows/run-openrewrite.yml[23-25]
- .github/workflows/run-openrewrite.yml[29-35]
| PR 16988 (2026-09-08) |
[maintainability] Readers get an overlong release note
Readers get an overlong release note
The new typing entry in `CHANGELOG.md` contains 23 words, exceeding the permitted 20-word maximum. It appears under Unreleased whenever users read the next release notes, where its extra wording breaks the required concise format.The new typing CHANGELOG entry exceeds the 20-word limit.
Preserve the We changed prefix, user-facing meaning, section, and pull-request link while reducing the sentence to at most 20 words.
- CHANGELOG.md[85-85]
[correctness] Maintainers cannot trust event nulls
Maintainers cannot trust event nulls
`MetaDataChangeSource` is a new enum without `@NullMarked`, and `MetaDataChangedEvent` exposes it through newly changed constructor and getter contracts in another unmarked class. Static analysis and later callers therefore cannot determine whether the source may be null when listener logic relies on exact enum comparisons.The new metadata source type and its event API do not declare their null contracts with JSpecify.
The source is expected to be non-null because listeners compare it directly with LOCAL and JOURNAL. Apply package-consistent JSpecify annotations to the new type and changed event surface.
- jablib/src/main/java/org/jabref/model/metadata/event/MetaDataChangeSource.java[1-17]
- jablib/src/main/java/org/jabref/model/metadata/event/MetaDataChangedEvent.java[7-25]
[maintainability] Developer docs omit an undo contract
Developer docs omit an undo contract
`UndoManager` adds the public `endStep` operation to the exported undo package without updating package-level or module-level API documentation. Consumers of that exported surface must discover the new action-boundary responsibility from one method rather than from the package contract that explains how undo recording is used.The exported undo package gains a public action-boundary API without the required package or module documentation update.
Document when clients must call the new operation and how it interacts with coalescing, recorded blocks, saves, undo, and redo.
- jablib/src/main/java/org/jabref/logic/undo/UndoManager.java[38-44]
- jablib/src/main/java/module-info.java[1-11]
[correctness] Automated edits undo with typed text
Automated edits undo with typed text
`CoalescingPolicy.CONSECUTIVE_FIELD_EDITS` treats every contiguous `UndoableFieldChange` for the same entry and field as typing, without distinguishing editor keystrokes from independently initiated commands. If a user types in a field and then runs `ExtractReferencesAction`, citation-key generation, or another direct field-changing command, its edit joins the pending typing step and one undo reverts both the command change and the preceding typed text.The coalescing policy cannot distinguish field-editor keystrokes from independently initiated commands that create UndoableFieldChange records. A command that directly changes the field most recently edited can merge into the pending typing step, causing one undo to reverse both the command and the user's preceding text.
Only editor-generated keystrokes should remain eligible to continue a typing step. Ordinary commands should establish an undo boundary before recording their field changes, or the journal API should provide a command-specific recording path that always creates that boundary. Audit direct applyEdit(new UndoableFieldChange(...)) command call sites, especially asynchronous ones, and add regression coverage for typing a citation key and then generating one, verifying that the first undo preserves the typed key.
- jablib/src/main/java/org/jabref/logic/undo/CoalescingPolicy.java[29-47]
- jablib/src/main/java/org/jabref/logic/undo/JabRefUndoManager.java[225-264]
- jabgui/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java[58-80]
- jabgui/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java[159-165]
- jabgui/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java[32-38]
- jablib/src/test/java/org/jabref/logic/undo/KeystrokeCoalescingTest.java[129-146]
| PR 16953 (2026-09-08) |
[reliability] Reviewers can see stale size labels
Reviewers can see stale size labels
The `size` job derives `wanted` and `present` from each event snapshot, then performs separate label mutations without a per-pull-request concurrency guard or fresh state check. When synchronization events overlap, an older run can finish last, re-add an obsolete label, miss a newer label, or fail while removing an already-removed label, so the pull request can retain stale or multiple size labels.Concurrent pull-request update events can race while removing and adding size labels because each run uses event-time line-count and label snapshots. An older run can therefore determine the final classification or leave multiple size labels.
Rapid pushes can start overlapping workflow runs. Add a concurrency group scoped to the pull request so newer synchronization work supersedes or runs after older size-labeling work, and base mutations on freshly fetched pull-request state so the newest run reconciles all size labels against the latest changed-line count.
- .github/workflows/pr-labeler.yml[29-50]
| PR 16945 (2026-09-07) |
[correctness] Invalid settings can crash theme lists
Invalid settings can crash theme lists
`themeName()` passes the nullable value of `selectedThemeColorSchemeProperty` to `ThemePreset.getLocalizedName`, which immediately switches on that value without handling `null`. When a missing or invalid stored color-scheme setting leaves the property unset, rendering a theme entry reaches this callback in both appearance dialogs and throws a `NullPointerException`.Theme label callbacks pass a potentially null color scheme into a switch that does not accept null.
Both view models use nullable JavaFX object properties, and invalid or absent stored settings can leave the selected color scheme unset. Use a safe fallback such as FOLLOW_SYSTEM, or express and handle nullability explicitly before invoking getLocalizedName.
- jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java[135-147]
- jabgui/src/main/java/org/jabref/gui/welcome/quicksettings/ThemeDialog.java[55-68]
- jabgui/src/main/java/org/jabref/gui/theme/ThemePreset.java[88-98]
[reliability] Community theme regressions go untested
Community theme regressions go untested
`themeLeavesTheLadderColorsToModena` now uses `builtInThemes`, whose parent filter excludes every newly bundled community theme from the existing stylesheet contract check. A later submodule update can therefore pin an adaptive ladder color in any community stylesheet without failing this test, reducing the regression protection previously applied to every preset.The ladder-color contract test now excludes all community themes.
Community themes are bundled from a separately updated submodule, making automated contract checks especially important. Keep inheritance-specific tests where needed, but run stylesheet-independent guarantees such as the ladder-color check against every preset.
- jabgui/src/test/java/org/jabref/gui/theme/ThemeTokenContractTest.java[163-174]
- jabgui/src/test/java/org/jabref/gui/theme/ThemeTokenContractTest.java[328-340]
| PR 16943 (2026-09-07) |
[correctness] Whitespace-only input creates an empty field
Whitespace-only input creates an empty field
`jumpToSelectedField` checks whether the untrimmed text is non-empty, then strips it and submits the result even when it becomes an empty string. Entering only spaces therefore reaches `FieldFactory.parseField` as an empty name and can add an empty custom field through the all-fields tab.The dialog validates selectedField before stripping whitespace, so whitespace-only input becomes an empty field name and can be added as a custom field.
Trim the value first, then skip the jump when the normalized value is empty. Preserve the existing behavior for non-empty field names.
- jabgui/src/main/java/org/jabref/gui/entryeditor/JumpToFieldDialog.java[83-88]
| PR 16937 (2026-09-07) |
[correctness] Pasted author lists merge real authors
Pasted author lists merge real authors
`getSimpleNormalForm` splits preceding names only on the exact delimiter `", "`, so missing or repeated spaces prevent it from inserting BibTeX `and` separators. For input such as `Z. Yao,D. S. Weld, et al.`, the suffix is recognized but `parse` treats the remaining comma as one person's name syntax and returns one malformed person plus `others` instead of two people plus `others`.The new et al. normalization splits preceding initial-first names only on an exact comma followed by one space. Inputs with no space or multiple spaces after an internal comma are consequently parsed as one malformed author.
The suffix matcher already recognizes the final et al. delimiter, but after removing it the normalization must reliably separate comma-delimited initial-first names before the regular author parser runs. Add coverage for no-space and repeated-space variants.
- jablib/src/main/java/org/jabref/logic/importer/AuthorListParser.java[104-117]
- jablib/src/test/java/org/jabref/logic/importer/AuthorListParserTest.java[56-59]
| PR 16936 (2026-09-07) |
[maintainability] Undo fixes lose requirement traceability
Undo fixes lose requirement traceability
`CHANGELOG.md` records several substantial user-visible undo fixes, but the PR does not add a requirement to the existing `docs/requirements/undo.md` area. Shipping these fixes without a trace identifier leaves their new behavior unlinked to its implementation and tests.The significant user-visible undo and redo fixes need an OpenFastTrace requirement entry.
The repository already has an undo requirements document with the required identifier placement and markdownlint directive.
- docs/requirements/undo.md[4-30]
| PR 16935 (2026-09-07) |
[reliability] Closed tabs leak database sessions
Closed tabs leak database sessions
`createLibraryTab` installs the connected context only through a success callback, while `createDummyContext` gives the loading tab a different context and synchronizer that are the only resources reached by `LibraryTab.onClosed`. When a loading tab is closed and the JDBC operation finishes despite task cancellation or interruption, callback suppression leaves the newly opened connection with no tab, owner, or cleanup path.Closing a shared-library loading tab can leave the actual database context without an owner or cleanup path when the background JDBC connection completes after cancellation. The loading tab owns only a separately created placeholder context and synchronizer, while installation of the connected context depends on a success callback that cancellation suppresses.
SharedDatabaseUIManager.createDummyContext() and connect() create distinct contexts and synchronizers. LibraryTab.onClosed can cancel the task and close only the placeholder stored by the tab; if the JDBC operation ignores interruption and completes after cancellation, UiTaskExecutor does not deliver the result to the success consumer. Fix the lifecycle either by connecting through an explicitly shared lifecycle object or by guaranteeing that any connected context produced after cancellation is closed.
- jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java[159-166]
- jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java[215-235]
- jabgui/src/main/java/org/jabref/gui/LibraryTab.java[778-786]
- jabgui/src/main/java/org/jabref/gui/LibraryTab.java[1242-1256]
- jabgui/src/main/java/org/jabref/gui/util/UiTaskExecutor.java[195-207]
| PR 16931 (2026-09-07) |
[maintainability] Clipboard URLs are parsed twice
Clipboard URLs are parsed twice
`applyClipboardConnectionUrl` branches on `DBMSConnectionUrl.parse(contents).isPresent()` and then updates a property whose existing subscription parses the same value again. Every valid clipboard URL follows this duplicate path, and future changes must keep the validation branch and property subscriber behavior aligned.The clipboard URL parser result is checked with isPresent() rather than consumed idiomatically.
Use an Optional operation such as ifPresent to perform the property update when parsing succeeds.
- jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java[171-176]
| PR 16930 (2026-09-07) |
[correctness] Callers can receive undocumented null
Callers can receive undocumented null
`abbreviatePath` accepts an unannotated `fullPath` and returns it unchanged when it is `null`, despite declaring a plain `String` return type. Any caller passing a nullable path receives an implicit null through the new public API, extending ambiguous null handling beyond the method boundary.The new public path abbreviation API can return an undocumented null despite its plain String contract.
Use JSpecify annotations for the parameter and return type, and either reject nullable input or normalize it before processing so the method always returns a non-null value.
- jablib/src/main/java/org/jabref/logic/util/strings/StringUtil.java[775-778]
[maintainability] Maintainers see inconsistent API docs
Maintainers see inconsistent API docs
`abbreviatePath` is documented with a newly added legacy `/** ... */` Javadoc block and `@param` tags. When this API is read or extended, its documentation follows a different syntax from the required Markdown Javadoc convention used for new multiline documentation.The new multiline API documentation uses legacy Javadoc rather than the required Markdown Javadoc syntax.
Rewrite the block using consecutive /// lines and Markdown constructs without legacy tags or HTML.
- jablib/src/main/java/org/jabref/logic/util/strings/StringUtil.java[768-774]
| PR 16927 (2026-09-07) |
[correctness] Localization checks fail for OCR errors
Localization checks fail for OCR errors
`OcrFailureReason.getMessage` passes the variable `message` as the first argument to `Localization.lang` instead of exposing each localization key as a string literal. When localization consistency checks scan this call, `localizationParameterMustIncludeAString` rejects the nonliteral parameter and obsolete-key detection cannot discover the five OCR messages as usages.OcrFailureReason.getMessage calls Localization.lang with a variable key, but JabRef's localization tooling requires the first argument of each call to contain a string literal. This causes localization consistency checks to reject the call and prevents the five OCR localization keys from being recognized as used.
Keep failure-message selection encapsulated in OcrFailureReason while making every localization key statically discoverable through a literal Localization.lang("...") call, such as via an enum switch. Pass the engine name and path arguments only for NOT_AVAILABLE.
- jablib/src/main/java/org/jabref/logic/ocr/OcrFailureReason.java[7-20]
[maintainability] New engine APIs lose null-safety checks
New engine APIs lose null-safety checks
`OcrEngineFactory` is a new class but has no `@NullMarked` annotation on its declaration. Its public `create` contract therefore introduces unverified nullability for both inputs and the return value wherever the exported OCR package is consumed.The new factory class lacks the required JSpecify null-marking annotation.
Add the standard JSpecify import and mark the class so its public factory contract defaults to non-null.
- jablib/src/main/java/org/jabref/logic/ocr/OcrEngineFactory.java[1-8]
| PR 16917 (2026-09-07) |
[correctness] Users lose left-side indentation
Users lose left-side indentation
`padding-left-16` is assigned to nested preference groups and related-article rows, but `jabref-base.css` defines no matching selector, and `PreferencesFormBuilder.styleClass` passes the inert class directly to JavaFX. When the related-article, citation-key, or table preference views render, the citation-key options, both name-format groups, and every related-article result remain flush with their parents instead of receiving the intended indentation.The newly used padding-left-16 style class has no CSS definition, so nested preference controls and related-article rows receive no left padding.
PreferencesFormBuilder.styleClass passes class names directly to JavaFX, so the undefined class is inert. Add a corresponding standardized utility rule with the intended 16-unit padding to the base stylesheet, or replace these references with an existing declared padding class if that spacing is intended.
- jabgui/src/main/resources/org/jabref/gui/theme/internal/jabref-base.css[98-113]
- jabgui/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java[127-130]
- jabgui/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTab.java[43-56]
- jabgui/src/main/java/org/jabref/gui/preferences/table/TableTab.java[58-75]
| PR 16896 (2026-09-07) |
[correctness] Pushes can briefly restore stale status
Pushes can briefly restore stale status
The synchronize handler removes `status: changes-required` outside the `pr-status-*` concurrency group that protects status evaluation. If an older `Comment on PR` run passes its pending check immediately before the push, it can add the old verdict after this removal, while the new-head evaluator then exits because CI or Qodo is pending and leaves that stale label in place.The synchronize-time label removal can interleave with an already-running Comment on PR evaluation and be overwritten by its stale result. Serialize the removal with all other status-label mutations so an earlier evaluation must finish before the new head clears its old verdict.
Comment on PR uses the pr-status-* concurrency group, but the workflow containing the new removal has no matching concurrency protection. Preserve the intended ordering across workflows, not only within one workflow run.
- .github/workflows/on-pr-opened-updated.yml[14-42]
- .github/workflows/pr-comment.yml[29-40]
- .github/workflows/pr-comment.yml[240-257]
- .github/workflows/pr-comment.yml[290-310]
| PR 16894 (2026-09-07) |
[maintainability] The same fix appears twice in release notes
The same fix appears twice in release notes
The Unreleased `Fixed` section lists the entry-type header fix twice, at lines 126 and 235, with the later entry repeating pull request #16894 and using the outdated “Entrytype” spelling instead of “Entry type”. This duplicate and inconsistent release-note coverage makes the history inaccurate and harder to maintain.The entry-type header fix for pull request #16894 is listed twice in the Unreleased Fixed section, and the later duplicate uses the outdated spelling “Entrytype” instead of the implemented “Entry type”.
Keep the concise, correctly worded entry at CHANGELOG.md:126, remove the later duplicate, and ensure the user-facing label remains consistent with the implementation and test.
- CHANGELOG.md[235-235]
[maintainability] Users get an oversized release-note entry
Users get an oversized release-note entry
The new Unreleased entry contains more than the permitted 20 words for a user-facing change. Its wording reaches 23 words before the issue link, so the release note does not meet the required concise format.The new changelog entry exceeds the maximum 20-word limit for user-facing release notes.
Keep the entry under the Fixed section, start it with We fixed, describe only the visible outcome, and retain the confidently matched issue reference.
- CHANGELOG.md[121-121]
[correctness] JabRef internal IDs lose their brand casing
JabRef internal IDs lose their brand casing
`FieldTextMapper.getDisplayName` applies `capitalizeFirst` to every `InternalField`, and that helper lowercases all characters after the first one. Whenever the `JabRef-internal-id` field is displayed, it becomes `Jabref-internal-id`, changing the established product name in UI labels and other display-name consumers.Applying StringUtil.capitalizeFirst to every InternalField lowercases the Ref portion of JabRef-internal-id, producing Jabref-internal-id.
StringUtil.capitalizeFirst uppercases the first character and lowercases the remainder. FieldTextMapper.getDisplayName is used by UI and other display-name consumers, while InternalField.INTERNAL_ID_FIELD is defined with the intentional JabRef brand casing.
- jablib/src/main/java/org/jabref/model/entry/field/FieldTextMapper.java[35-37]
- jablib/src/main/java/org/jabref/model/entry/field/InternalField.java[32-33]
- jablib/src/main/java/org/jabref/logic/util/strings/StringUtil.java[631-638]
- jablib/src/test/java/org/jabref/model/entry/field/FieldTextMapperTest.java[13-20]
| PR 16893 (2026-09-07) |
[maintainability] Maintainers lose import traceability
Maintainers lose import traceability
`insertEntries` changes entry-ordering and lookup behavior, but the PR adds no corresponding requirement under `docs/requirements`. The change is a user-visible import fix recorded in the changelog and covered as model behavior, so future changes cannot trace this invariant to its implementation and regression test.The significant import behavior fix lacks an OpenFastTrace requirement.
Add the requirement under the import requirements area. Place its req identifier immediately after the heading without a blank line, describe the sorted-entry invariant and resulting lookup behavior, and retain the required MD022 footer.
- docs/requirements/import.md[15-17]
[performance] Large imports can freeze the interface
Large imports can freeze the interface
`insertEntries` loops over `sortedNewEntries` and inserts each item into the middle of the array-backed observable list, repeatedly shifting the remaining library and emitting per-item list changes instead of merging the two sorted sequences once. Background file import sends the accumulated batch to this method on the JavaFX thread, so importing many older-ID entries into a large open library performs quadratic work and blocks the interface.BibDatabase.insertEntries individually inserts every incoming entry into an array-backed observable list. For large out-of-order batches this repeatedly shifts existing elements and notifies observers, while the operation runs on the JavaFX thread.
Both the existing entries and incoming entries are sorted by ID, so use a linear merge and apply the merged result as one bulk list update. Preserve the append fast path and verify lookup and ordering behavior with a large out-of-order batch.
- jablib/src/main/java/org/jabref/model/database/BibDatabase.java[188-198]
- jablib/src/test/java/org/jabref/model/database/BibDatabaseTest.java[549-564]
- jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java[253-264]
| PR 16887 (2026-09-06) |
[correctness] Users still see off-scale spacing
Users still see off-scale spacing
The modified `getNotFoundPane()` and `getErrorPane()` retain `VBox` gaps of `30`, while `getRelatedArticleInfo()` retains a gap of `5.0` despite moving their padding onto the standardized scale. These layouts therefore remain outside the established 4-to-24 spacing range and preserve arbitrary values during the unification pass.Modified layouts still use off-scale gaps of 30 and 5.0, leaving their spacing inconsistent with the standardized values introduced by this PR.
Use an appropriate established spacing value between 4 and 24 for both citation status panes and the related-article rows. Prefer shared style classes where applicable.
- jabgui/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java[133-146]
- jabgui/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java[127-130]
| PR 16883 (2026-09-06) |
[reliability] Rapid replies can leave the wrong label
Rapid replies can leave the wrong label
The review-activity bridge starts an independent downstream label run for every created or deleted author reply, with no per-pull-request serialization around the live-state query and subsequent mutation. When opposite events occur close together, an earlier run can read the old thread state but apply its label after the later run, leaving every consumer of the status labels with a result that contradicts the current thread state.Review activity can launch concurrent privileged label updates for the same pull request. Serialize the state evaluation and label mutation per pull request so an older run cannot overwrite the result of newer review activity.
This race is newly activated for fork pull requests because the bridge now allows their previously failing label updates to reach Comment on PR. Each downstream run queries live review-thread state and later mutates the status labels without a per-PR lock.
- .github/workflows/pr-review-activity.yml[18-40]
- .github/workflows/pr-comment.yml[250-301]
| PR 16882 (2026-09-06) |
[correctness] Locked commits expose raw error details
Locked commits expose raw error details
`GitCommitDialogViewModel.commitAction` uses `ex.getMessage()` for every non-`JabRefException`, while `GitHandler.createCommitOnCurrentBranch` lets JGit failures from staging and committing pass through untranslated. When `.git/index.lock` blocks `git.add().call()` or `git.commit().call()` after the status checks succeed, committing a changed library sends JGit’s generic command message to the dialog instead of localized repository-lock guidance.Commit-time staging and committing failures can bypass repository-lock translation and expose raw, opaque JGit messages in the error dialog.
The preliminary status checks translate lock failures, but .git/index.lock can be encountered later when GitHandler.createCommitOnCurrentBranch calls JGit add or commit. Convert these failures into a localized JabRefException, including lock-cause detection, before the failure handler chooses the displayed text, or translate them in the dialog failure handler.
- jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogViewModel.java[88-98]
- jablib/src/main/java/org/jabref/logic/git/GitHandler.java[266-291]
[reliability] Lock failures can regress unnoticed
Lock failures can regress unnoticed
`brokenRepositoryStatusShowsAnErrorDialog` corrupts the branch ref, and `invalidHeadIsReportedAsJabRefException` only exercises the missing-object path; neither test creates an index lock or reaches the actual commit operation. A regression in staging or committing with `.git/index.lock` therefore passes these tests even though that path is the ticket's named failure.The added tests cover repository corruption but not an index-lock failure during the commit operation.
Add a regression test that creates .git/index.lock, attempts a commit with changes, and verifies that the dialog contains localized lock guidance without raw JGit details.
- jabgui/src/test/java/org/jabref/gui/git/GitCommitActionTest.java[170-192]
- jablib/src/test/java/org/jabref/logic/git/status/GitStatusCheckerTest.java[266-282]
| PR 16879 (2026-09-06) |
[maintainability] The test weakens failure diagnostics
The test weakens failure diagnostics
`removesWindowAfterDatabaseContentChanges()` checks the optional result with `assertTrue(...isEmpty())` instead of comparing it directly with `Optional.empty()`. When the state manager returns an unexpected window, the assertion reports only a boolean mismatch and provides less precise content-based diagnostics for this behavior.The test uses a boolean assertion for an optional value, which weakens failure diagnostics.
The repository requires direct content-based JUnit assertions when the expected value can be expressed directly.
- jabgui/src/test/java/org/jabref/gui/JabRefGuiStateManagerAiChatWindowTest.java[53-53]
| PR 16877 (2026-09-06) |
[correctness] Maintainers mislabel funding updates
Maintainers mislabel funding updates
`any-glob-to-any-file` uses `.github/*.yml`, which matches `.github/FUNDING.yml` even though that file contains donation and sponsorship settings rather than build, release, or workflow configuration. Any pull request that updates those sponsorship destinations therefore receives `dev: ci-cd`, and `sync-labels: true` applies this classification during the normal labeler run.The .github/*.yml glob labels unrelated top-level GitHub configuration, including the repository's sponsorship settings, as CI/CD changes.
Keep coverage for workflows, composite actions, the labeler, and pull-request comment configuration, but replace the broad top-level YAML wildcard with explicit CI/CD configuration paths or exclude unrelated files such as FUNDING.yml.
- .github/labeler.yml[301-307]
[correctness] Some automation changes stay unlabeled
Some automation changes stay unlabeled
The `any-glob-to-any-file` list only covers files under `.github`, omitting Docker build files, the CI failure-reporting scripts, formatter settings, and the TeX package manifest directly consumed by workflows or actions. Pull requests changing any of those inputs still alter build or test automation without receiving the new label.The new CI/CD label patterns omit repository files that directly configure or implement workflows and composite actions.
Add patterns for the directly consumed automation inputs, including the Docker build files, failure-reporting scripts and stylesheet, IntelliJ formatter configuration, and TeX Live package manifest. Keep ordinary application sources that CI merely tests outside this label.
- .github/labeler.yml[301-307]
| PR 16874 (2026-09-06) |
[correctness] DOI lookup can alter another entry
DOI lookup can alter another entry
`doiLookUp()` passes one nullable `getCurrentEntry()` value to `findIdentifier` but rereads the mutable selection before writing the DOI, rebinding, and refreshing the tab instead of retaining and validating the initiating entry. If the user changes or clears the selection while CrossRef is running, the callback mutates and rebinds the newly selected entry or dereferences `null`, then rebuilds the tab for the wrong target.The asynchronous DOI lookup rereads the current entry when its callback runs, allowing the result to be applied and rebound to a different entry or to reach a null entry.
Capture the selected entry before creating the task and use that same entry for both findIdentifier and setField. Before updating UI state, rebinding, or rebuilding the active tab, validate that the callback still owns the active operation and that the user has not selected or cleared another entry.
- jabgui/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java[276-293]
| PR 16872 (2026-09-06) |
[reliability] Some pull requests skip issue handling
Some pull requests skip issue handling
`concurrency.group` serializes all pull requests repository-wide, but GitHub replaces an existing pending run when another run joins the group even with `cancel-in-progress: false`. When three events overlap, the displaced run never labels or assigns its issue and never records a foreign-assignment incident unless that pull request later receives another event.Repository-wide GitHub Actions concurrency can discard a pending pull-request run, leaving that pull request entirely unprocessed.
Serialization is needed to protect the shared strike ledger, but it must not depend on a concurrency group that retains only one pending run. Use a lossless serialization or retry mechanism while preserving idempotent comments and strike records.
- .github/workflows/link-issue.yml[121-127]
[correctness] Existing strikes close compliant work
Existing strikes close compliant work
The initial `blocked` condition is derived solely from the author's historical incident count and closes the current pull request before checking the referenced issue's assignment. Once the ledger contains three incidents, every later pull request is closed even when its issue is unassigned or already assigned to that author, although closure is intended for the pull request that creates the third incident.Authors with three historical incidents have every subsequent linked pull request closed before the workflow determines whether the new pull request violates the assignment policy, including pull requests linked to unassigned issues or issues already assigned to the author.
The documented behavior closes the pull request that creates the third incident; it does not make three historical incidents a permanent ban on compliant future contributions. Preserve retry handling for the actual third offending pull request while ensuring historical incidents do not bypass assignment checks for unrelated compliant work.
- .github/workflows/link-issue.yml[145-170]
- .github/workflows/link-issue.yml[268-299]
- docs/requirements/ci.md[11-14]
[correctness] New assignees cannot stop issue pinning
New assignees cannot stop issue pinning
The successful assignment path uses the earlier `author_assigned` output without refreshing the issue immediately before adding the pinned label and pull-request author. If an unassigned issue is assigned to another contributor after the initial lookup, this run still pins it and adds the pull-request author alongside that contributor.The positive assignment decision can become stale before the workflow pins the issue and adds the pull-request author.
The warning path already refreshes assignees to handle concurrent changes. Apply an equivalent atomic or immediately refreshed check to the successful path so another contributor's new assignment prevents pinning and author assignment.
- .github/workflows/link-issue.yml[168-185]
- .github/workflows/link-issue.yml[221-227]
[reliability] Multi-assignee issues halt the workflow
Multi-assignee issues halt the workflow
The assignment step writes the pretty-printed `ASSIGNEES` JSON array to `GITHUB_OUTPUT` using the single-line `name=value` form. When an issue has multiple assignees, the array spans multiple lines and GitHub rejects the remaining lines as malformed output before either the normal assignment path or warning path can run.The assignment step writes a potentially multiline JSON array using GitHub Actions' single-line output syntax, causing output parsing to fail for issues with multiple assignees.
The output is subsequently consumed by the warning step. Serialize the array compactly before writing it, or use the documented multiline output delimiter syntax.
- .github/workflows/link-issue.yml[173-180]
- .github/workflows/link-issue.yml[228-240]
[correctness] Unrecorded incidents close contributions
Unrecorded incidents close contributions
The record step publishes the incremented `count` before checking whether `GH_TOKEN_STRANGE_USERS` exists or persisting the updated ledger. When an author has two stored incidents and the write token is unavailable, the step warns that the third incident was not persisted but the following condition still sees three and closes the pull request.An incident that cannot be written to STRANGE_USERS still contributes to the closure output, allowing an unrecorded third incident to close a pull request.
Only expose a closure-eligible count after gh secret set succeeds, or emit a separate persistence output and require it in the closure condition.
- .github/workflows/link-issue.yml[284-293]
- .github/workflows/link-issue.yml[294-299]
[security] Mutable actions can use write tokens
Mutable actions can use write tokens
`actions/checkout` and `awalsh128/cache-apt-pkgs-action` are changed from full commit SHAs to mutable tags `v7` and `v1.6.3` in a `pull_request_target` job. If either upstream tag is retargeted, unreviewed action code executes with the job's `GH_TOKEN` issue and pull-request write permissions, reaching privileged repository operations.Two third-party GitHub Actions in a write-capable pull_request_target workflow were changed from immutable full commit SHAs to mutable version tags.
Restore reviewed full commit-SHA references for actions/checkout and awalsh128/cache-apt-pkgs-action, following the pattern used for the same actions elsewhere in the repository. Retain release-tag comments for readability.
- .github/workflows/link-issue.yml[121-134]
[maintainability] Maintainers cannot trace closures
Maintainers cannot trace closures
`link-issue.yml` adds persistent strike counting and automatic closure on a contributor's third incident without adding an OpenFastTrace requirement. When an unassigned author links a pull request to another contributor's issue, the warning, persistence, and closure policy has no documented requirement for later maintainers to validate or change.The new strike-tracking and automatic pull-request closure policy lacks an OpenFastTrace requirement.
Add a requirement describing assignment checks, one-time warnings, strike persistence, and the closure threshold. Place its identifier immediately after its heading without a blank line and preserve the required markdownlint footer.
- docs/requirements/ci.md[4-11]
- .github/workflows/link-issue.yml[235-283]
[correctness] Concurrent assignment can be overwritten
Concurrent assignment can be overwritten
The assignment workflow exports one assignee snapshot and later derives a full-list replacement from that stale value. If a maintainer or another workflow assigns somebody between the check and the replacement, the later patch removes that newer assignee and recreates the motivating failure.The assignment gate reads the complete assignee list in one step and later replaces it from that snapshot, allowing a concurrent assignment to be removed.
The workflow must never remove the contributor who currently owns the issue. Re-read before mutation and use the single-assignee addition API rather than replacing the complete list.
- .github/workflows/link-issue.yml[161-180]
- .github/workflows/link-issue.yml[218-234]
[reliability] Concurrent incidents lose strike records
Concurrent incidents lose strike records
Each run appends an incident to its local copy of `STRANGE_USERS` and then replaces the complete repository secret. When incidents overlap, both can read the same prior value and the last write discards the other run's update.Concurrent read-modify-write operations can overwrite incidents recorded by another workflow run.
The secret is one repository-wide ledger, so per-PR serialization alone is insufficient. Serialize all ledger updates globally or move strike records to storage supporting atomic writes and conflict detection.
- .github/workflows/link-issue.yml[259-278]
[correctness] Contributors get duplicate close notices
Contributors get duplicate close notices
The closure step always posts its comment whenever `steps.record.outputs.count` is at least three, even when the record step found that this pull request was already in the ledger. An `edited`, `synchronize`, `reopened`, or workflow rerun for the same violating pull request therefore posts the closure message again before attempting another close.The automatic-closure step runs again for an already-recorded incident and posts another closure comment whenever the workflow is rerun or receives another supported pull-request event.
The record step emits the existing total count when it finds the pull request in the ledger. A count of three or more therefore cannot distinguish the run that persisted the incident from later runs. Add an explicit newly-recorded output or a workflow-authored closure marker so the notice is posted only once; preserve any desired enforcement when a closed pull request is reopened.
- .github/workflows/link-issue.yml[232-249]
- .github/workflows/link-issue.yml[250-255]
[reliability] Existing warnings can be posted again
Existing warnings can be posted again
The marker checks pipe paginated `gh api` output into `grep -qF` while the warning step has `pipefail` enabled. When `grep` finds a marker and closes its input before the producer finishes, `gh api` can fail on the closed pipe and the negated pipeline is treated as a missing marker, causing duplicate pull-request or issue comments on a later event.The idempotency checks combine grep -q with a paginated producer under pipefail, so a successful early match can make the overall pipeline appear unsuccessful.
Capture the API output before searching it, or use a query that returns marker existence without an early-closing consumer. Apply the same correction to both marker checks.
- .github/workflows/link-issue.yml[233-253]
- .github/workflows/link-issue.yml[255-264]
[correctness] Newly assigned authors receive a strike
Newly assigned authors receive a strike
The warning path relies only on the `author_assigned` value captured near the beginning of the job and never validates the assignment again. When the author is assigned after that snapshot but before comments are posted, the workflow still warns both parties and records the incident.An author assigned after the initial API request can still be warned and receive a strike because the warning uses a stale output.
Assignment can change while the workflow is running, including through repository workflows. Re-fetch assignees immediately before warning and recording, and stop if the author is now assigned or the issue is now unassigned.
- .github/workflows/link-issue.yml[161-173]
- .github/workflows/link-issue.yml[235-261]
[reliability] A failed issue note loses the strike
A failed issue note loses the strike
The warning step posts the marker-bearing pull-request comment before posting the issue comment and emits `warned=true` only after both commands succeed. If the second command fails, a rerun sees the first comment's marker and exits without retrying the issue notification or recording the incident.A partial warning failure leaves a marker that suppresses the missing issue comment and strike forever.
Track or detect each side effect independently so reruns can finish incomplete work. Do not treat the PR-comment marker alone as proof that the issue note and strike both succeeded.
- .github/workflows/link-issue.yml[243-258]
- .github/workflows/link-issue.yml[259-278]
[reliability] Overlapping runs repeat policy warnings
Overlapping runs repeat policy warnings
The marker lookup and creation are separate operations with no per-pull-request serialization. Two runs that overlap can both observe no marker, post both warning comments, and enter strike processing despite the intended one-time behavior.Overlapping workflow runs can both pass the marker check before either creates the marker.
The workflow runs on several event types and currently has no concurrency group. Add per-PR serialization with cancellation or otherwise make marker creation and processing mutually exclusive.
- .github/workflows/link-issue.yml[3-8]
- .github/workflows/link-issue.yml[115-120]
- .github/workflows/link-issue.yml[243-258]
[security] Contributors can suppress policy strikes
Contributors can suppress policy strikes
The idempotency check accepts the fixed marker from any pull-request comment without checking who created it. A contributor who posts that hidden marker before the workflow reaches the check causes `warned=false`, suppressing the warning, issue notification, and strike.A contributor-authored comment can impersonate the workflow marker and bypass strike processing.
Inspect structured comment data and accept a marker only from the trusted workflow identity, ideally also validating the expected warning content or storing processed state somewhere contributors cannot modify.
- .github/workflows/link-issue.yml[243-258]
| PR 16871 (2026-09-06) |
[correctness] Capitalized searches return no styles
Capitalized searches return no styles
`matchStyleSearch` lowercases only `styleName`, while tokens derived from the verbatim search-box value retain the user's casing when passed to `contains`. Any query containing uppercase letters, including the documented `Springer lecture` example, therefore fails against an otherwise matching lowercase style name and regresses the previous case-insensitive filter.Subset searching is unintentionally case-sensitive because matchStyleSearch lowercases style names but not the search terms, regressing the previous case-insensitive behavior.
Text from the CSL style search box is passed directly to setAvailableCslLayoutsFilter. Preserve case-insensitive behavior while retaining separator-aware matching by normalizing the query or its tokens with Locale.ROOT before applying contains, and add coverage for capitalized multi-token searches such as Springer lecture.
- jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java[241-253]
[correctness] New work appears in an old release
New work appears in an old release
The new subset-search entry is inserted under `6.0-alpha.6` instead of the existing `Unreleased` `Added` section. Because that release is dated 2026-05-14, future release notes omit this September change while the historical release record is rewritten.The subset-search changelog entry was added to an already released version rather than Unreleased.
Keep the existing wording and issue reference, but place the entry alongside related OpenOffice and CSL additions under the current Unreleased Added heading.
- CHANGELOG.md[10-33]
- CHANGELOG.md[218-223]
[maintainability] Search regressions can pass unnoticed
Search regressions can pass unnoticed
`matchStyleSearch` introduces separator-aware, all-term matching without adding any deterministic test for the new behavior. Inputs involving case, punctuation, multiple terms, and blanks now depend on this branch, so defects in those paths can pass continuous integration.The new style subset-search behavior has no automated regression coverage.
Add fast parameterized tests covering empty input, mixed case, punctuation and hyphens, multiple matching terms, and a missing term. Tests should directly compare expected matching results with plain JUnit assertions.
- jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java[237-253]
- jabgui/src/test/java/org/jabref/gui/openoffice[1-1]
[maintainability] Feature loses requirements traceability
Feature loses requirements traceability
The subset-search feature changes filtering behavior but adds no requirement under `docs/requirements`. With only the GUI implementation and changelog entry in the PR, later changes have no OpenFastTrace requirement capturing separator and multi-term semantics.The new subset-search feature lacks its required OpenFastTrace requirement document.
Add or update the appropriate requirements file with a singular, current requirement describing case-insensitive separator-aware CSL style searching. Place the req~...~1 identifier immediately below its heading and preserve the required markdownlint directive.
- docs/requirements[1-1]
- jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java[237-253]
| PR 16869 (2026-09-06) |
[correctness] Valid imports can lose whole entries
Valid imports can lose whole entries
`parseBracketedFieldContent(true)` treats every column-one `@token{` or `@token(` sequence as a new entry without considering the current nested-brace depth or establishing that the field is malformed. When valid braced field text contains such a sequence on its own line, the method throws, `parseAndAddEntry` discards the enclosing entry, and the restored text is reparsed as a fresh entry.The unmatched-brace recovery heuristic mistakes valid line-leading BibTeX-like text inside a braced field for a new top-level entry, causing the otherwise valid enclosing entry to be discarded and the nested text to be reparsed as an entry.
Braced field values may legally contain nested text such as @foo{bar}. Recovery must distinguish an actual parser synchronization boundary from valid nested field content rather than firing merely because an @token{ or @token( sequence begins at column one. Add regression coverage for a valid braced value containing such a sequence.
- jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java[1077-1141]
- jablib/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java[562-579]
[correctness] Release notes overstate parser recovery
Release notes overstate parser recovery
`parseBracketedFieldContent` only attempts recovery when the next `@` leaves `column == 2`, and `isEntryStart` requires `{` or `(` immediately after the type token instead of allowing the whitespace accepted by the normal parser. After an unmatched brace, a valid following entry is consumed as malformed field content and skipped if it is indented, has a space before its delimiter, or puts that delimiter on the next line, despite `CHANGELOG.md` claiming recovery.Recovery after an unmatched brace recognizes a narrower entry grammar than the normal parser, so the release note promises recovery for valid layouts that remain unrecoverable.
The normal parser accepts leading whitespace before entries and whitespace or newlines between an entry type and its opening delimiter. Update the recovery lookahead to preserve and recognize those same layouts, add coverage for indented entries and delimiters separated by spaces or newlines, or narrow the changelog entry to the behavior actually supported.
- CHANGELOG.md[166-166]
- jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java[1086-1141]
- jablib/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java[199-227]
- jablib/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java[562-579]
| PR 16868 (2026-09-06) |
[maintainability] Group diff lacks changelog update
Group diff lacks changelog update
The PR adds a user-visible group-tree comparison but does not update the existing Unreleased semantic-diff changelog entry. Users therefore receive no release-note coverage for the new display behavior.The user-visible group-tree diff feature is missing from the Unreleased changelog.
Update the existing semantic Git diff preview entry rather than adding an unrelated release note. Keep the entry concise, user-facing, and reference issue #16729.
- CHANGELOG.md[22-22]
| PR 16862 (2026-09-05) |
[reliability] Concurrent workflow restores label
Concurrent workflow restores label
The one-shot removal can race with an already-running `Comment on PR` job that observed the PR before conversion and adds `status: ready-for-review` after the removal. That PAT-generated label event causes `Adapt PR status` to convert the draft back to ready, undoing the user's transition.The draft-transition job removes status: ready-for-review without coordination with the workflow that adds that label. An in-progress Comment on PR run can make its ready decision before conversion, add the label afterward, and trigger the automation that marks the PR ready again.
Use a shared per-PR concurrency group for draft removal and ready-label mutation, and ensure the label-writing workflow checks the live draft state while serialized. Preserve serialization until the label mutation completes.
- .github/workflows/remove-ready-for-review.yml[10-20]
- .github/workflows/pr-comment.yml[36-51]
- .github/workflows/pr-comment.yml[305-315]
[maintainability] Duplicate guidance confuses maintainers
Duplicate guidance confuses maintainers
`guard-review` has two consecutive comments that explain the same `ready_for_review` condition with slightly different wording. When maintainers update the event guard, they must reconcile both versions and may mistake the duplication for two distinct constraints.Two consecutive comments provide duplicate explanations for the guard-review event condition.
Keep one concise rationale explaining why the guard only runs for the transition to ready-for-review.
- .github/workflows/remove-ready-for-review.yml[29-32]
| PR 16860 (2026-09-05) |
[correctness] Non-CSL Zotero mode allowed
Non-CSL Zotero mode allowed
The new preferences allow Zotero compatibility and CSL-style inference to be enabled while a non-CSL style is selected, even though the panel treats that state as invalid and the inference path refuses to run. Users can therefore save an enabled inference setting that has no effect until they separately select a CSL style.Preserve the existing restriction that Zotero compatibility and CSL-style inference are available only when a CitationStyle is active. The moved controls currently permit an enabled state that the OpenOffice integration ignores for JStyle and BST styles.
OpenOfficePanel.updatePreferences still clears both settings for non-CitationStyle values, and OOBibBase.shouldReadStyleInPreference requires a CitationStyle. Make the preferences UI and persistence behavior consistent with those constraints, or explicitly redesign the downstream behavior if these are intended to become style-independent global settings.
- jabgui/src/main/java/org/jabref/gui/preferences/openoffice/OpenOfficeTab.java[49-52]
- jabgui/src/main/java/org/jabref/gui/preferences/openoffice/OpenOfficeTabViewModel.java[47-66]
- jabgui/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java[690-700]
| PR 16856 (2026-09-05) |
[maintainability] Comment misstates retained statuses
Comment misstates retained statuses
The new comment says all not-ready cases have no status label, but `status: awaiting-second-review` and `status: to-be-merged` classify progressed PRs and are intentionally retained by the removal command. This inaccurately documents the status state machine and could mislead future workflow changes.The comment states that every not-ready PR has no status label, although progressed PRs retain status: awaiting-second-review or status: to-be-merged.
PR_STATE treats those labels as not-ready, while the corresponding command removes only changes-required, stale, and ready-for-review. Update the comment and log wording to distinguish draft/human-review cases from already-progressed PRs.
- .github/workflows/pr-comment.yml[301-315]
| PR 16855 (2026-09-05) |
[maintainability] `isEmpty()` weakens assertion
`isEmpty()` weakens assertion
The new test wraps a computed emptiness condition in `assertTrue`, which produces less useful failure output than directly comparing the resulting map with `Map.of()`.The test indirectly checks map contents using assertTrue(...isEmpty()) instead of a direct content assertion.
Direct equality assertions provide expected and actual values when the test fails.
- jabgui/src/test/java/org/jabref/gui/search/HighlighterTest.java[32-37]
[correctness] Empty query crashes validator
Empty query crashes validator
The grammar permits an empty query as `EOF` with no `andExpression`, but `visitStart` unconditionally visits the absent child, causing a null Boolean to be unboxed and throwing a `NullPointerException`. Existing search-bar synchronization and clearing construct `new SearchQuery("")`, so initializing or clearing a search fails instead of preserving the prior empty-query behavior.Prevent SearchQuery("") from throwing when regular-expression validation visits the grammar's empty-input alternative. Preserve the prior empty-query validity and clearing behavior by avoiding compilation or visitation of a nonexistent andExpression, and add regression coverage for constructing and clearing an empty search.
The grammar accepts EOF as a complete start rule without an andExpression. visitStart currently visits that absent child, yielding null that is unboxed into a primitive boolean and causes a NullPointerException; the constructor only catches parse cancellation. Existing GUI synchronization converts an empty active query into new SearchQuery(""), so initialization or clearing can trigger this failure.
- jablib/src/main/java/org/jabref/model/search/query/SearchQuery.java[107-141]
- jablib/src/test/java/org/jabref/logic/search/query/SearchQueryTest.java[17-70]
- jabgui/src/main/java/org/jabref/gui/search/GlobalSearchBar.java[377-390]
| PR 16854 (2026-09-05) |
[correctness] Smoke port input untracked
Smoke port input untracked
The required smoke-test port is now validated and assigned only inside `doFirst`, after Gradle has evaluated task inputs and up-to-date state. A prior successful result can therefore make an invocation with a changed or missing port report `UP-TO-DATE` without validating the property or testing the requested server.nativeSmokeTest reads its required port only from a doFirst action, so the port is unavailable when Gradle determines whether the Test task is up to date.
Preserve lazy property access, but declare the provider as a task input before execution and ensure the effective system property participates in input snapshotting. Missing or malformed values must still fail even when previous test outputs exist.
- jabsrv/build.gradle.kts[57-66]
| PR 16853 (2026-09-05) |
[reliability] Nested small-caps exhaust stack
Nested small-caps exhaust stack
Each nested `\textsc` or `{\sc ...}` group recursively invokes the full converter on a substring, causing quadratic work and linear stack growth. A deeply nested entry can therefore make preview generation fail with `StackOverflowError`.Deeply nested small-caps groups recursively invoke the complete conversion pipeline, resulting in quadratic processing and possible StackOverflowError during preview generation.
BibEntry field content reaches this parser without a nesting limit. Replace recursive conversion with an iterative balanced-group parser, or enforce a safe depth limit while avoiding repeated substring scans.
- jablib/src/main/java/org/jabref/logic/preview/BstPreviewLayout.java[176-292]
- jablib/src/test/java/org/jabref/logic/bst/BstPreviewLayoutTest.java[65-100]
| PR 16851 (2026-09-04) |
[correctness] `crossref` assigned implicit null
`crossref` assigned implicit null
Under `@NullMarked`, the new code assigns an absent optional value to a non-null `String` and stores it without an explicit nullable contract. This makes the field map's intentional null semantics inconsistent with its declared types.The new cross-reference loading logic uses orElse(null) under @NullMarked, but the local variable and field-map value type do not explicitly permit null.
Missing BST fields intentionally use null values. Express that contract with precise JSpecify type-use annotations rather than assigning null to a non-null String.
- jablib/src/main/java/org/jabref/logic/bst/BstVMVisitor.java[81-85]
- jablib/src/main/java/org/jabref/logic/bst/BstEntry.java[8-13]
| PR 16846 (2026-09-04) |
[correctness] `flash` selector never matches
`flash` selector never matches
The timeline toggles `:flash` on `searchBox`, but the CSS also requires the `flashing-color` style class, which is never assigned to the control. Consequently, clicking Add with an active filter clears the filter but does not display the intended red invalid-search warning flash.The new flash selector requires the flashing-color style class, but searchBox never receives that class, so toggling its flash pseudo-class has no visual effect.
The previous inline style binding visibly animated the search field. Preserve that behavior and the theme-aware CSS implementation by assigning the selector's style class to the search box, or by changing the selector to target a class already present on that specific control without affecting unrelated fields.
- jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java[106-116]
- jabgui/src/main/resources/org/jabref/gui/theme/jabref-theme.css[951-957]
[maintainability] Changelog includes implementation context
Changelog includes implementation context
The phrase `fixed as a side effect` describes implementation history rather than only the user-visible result. Rule 26 requires the entry to contain only user impact.Rewrite the changelog entry to omit the fixed as a side effect implementation context.
Keep the entry under Fixed, begin it with We fixed, retain the existing issue link, and describe the visible search-box correction in one sentence of at most 20 words.
- CHANGELOG.md[196-196]
[correctness] Flash missing from Primer
Flash missing from Primer
The pseudo-class styling was added only to `jabref-theme.css`, while selecting Primer replaces that stylesheet rather than layering it underneath. Therefore the replacement animation has no CSS definition under the supported Primer theme, unlike the previous theme-independent inline animation.The flash rules are defined only in the JabRef theme, so the supported Primer theme cannot render the new pseudo-class animation.
ThemeManager installs exactly one selected theme stylesheet and then the shared JabRef base stylesheet. Put component-specific selectors needed by every theme in the shared base stylesheet, using theme color tokens where appropriate.
- jabgui/src/main/resources/org/jabref/gui/theme/jabref-theme.css[951-957]
- jabgui/src/main/resources/org/jabref/gui/theme/internal/jabref-base.css[1-1]
| PR 16845 (2026-09-04) |
[reliability] Selection behavior remains untested
Selection behavior remains untested
The updated test explicitly supplies an empty active-tab property, so the newly added selection and scrolling path is never exercised. No changed test verifies imported-entry selection, citation-key-free selection, or merged-entry selection as required for this behavioral change.The PR changes imported and merged entry selection behavior, but the updated test bypasses the active-tab selection path and only verifies inserted-copy tracking.
Add tests that use a target LibraryTab or appropriately mocked equivalent and verify clearAndSelect receives the imported entries. Also cover citation-key-free entries and merged-entry selection so selection and scrolling behavior cannot regress.
- jabgui/src/test/java/org/jabref/gui/externalfiles/ImportHandlerTest.java[178-210]
- jabgui/src/main/java/org/jabref/gui/externalfiles/EntryImportHandlerTracker.java[75-87]
- jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java[351-375]
- jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeTwoEntriesAction.java[43-46]
| PR 16841 (2026-09-03) |
[correctness] Superseded ADR marked implemented
Superseded ADR marked implemented
The `--input` alias is linked to ADR 45 even though ADR 45 is explicitly superseded and the current positional-or-alias behavior is defined by ADR 57. This makes tracing report an obsolete decision as covered while leaving the active decision untraceable.The OpenFastTrace link on InputOption targets superseded ADR 45, while ADR 57 defines the current positional-input behavior and retention of --input as an alias.
Give ADR 57 an OpenFastTrace identifier and implementation need, point InputOption to that identifier, and avoid treating superseded ADR 45 as an active decision requiring coverage.
- jabkit/src/main/java/org/jabref/toolkit/commands/InputOption.java[81-90]
- docs/decisions/0045-use-input-flag-always-for-input-files.md[4-13]
- docs/decisions/0057-allow-positional-input-file-argument.md[4-10]
| PR 16836 (2026-09-03) |
[maintainability] Module requirement immediately unmet
Module requirement immediately unmet
The new rule says every module has orientation Javadoc, but all eight module descriptors currently begin directly with imports or module declarations and have no such comment. This makes the repository noncompliant as soon as the policy lands and leaves contributors without a conforming module example.The new policy requires every module-info.java to have Markdown Javadoc, while none of the repository's module descriptors currently does. Either add the required documentation to each descriptor or explicitly scope the policy to newly created or substantially modified modules and provide a migration plan/example.
There are eight module descriptors, all of which currently start directly with an import or module declaration. The policy should not assert a repository-wide invariant that is immediately false without explaining how existing modules are handled.
- AGENTS.md[540-544]
- jabgui/src/main/java/module-info.java[1-3]
- jabkit/src/main/java/module-info.java[1-2]
- jablib/src/main/java/module-info.java[1-2]
- jabls-cli/src/main/java/module-info.java[1-2]
- jabls/src/main/java/module-info.java[1-2]
- jabsrv-cli/src/main/java/module-info.java[1-2]
- jabsrv/src/main/java/module-info.java[1-2]
- test-support/src/main/java/module-info.java[1-2]
[maintainability] Examples violate required structure
Examples violate required structure
Both files presented as existing examples omit the developer-documentation deep links that the preceding rule requires, and neither demonstrates the complete prescribed structure. Contributors copying these examples therefore cannot produce documentation that complies with the new guidance.The two package-info files identified as examples do not include the deep links required by the immediately preceding structure rules. Make the referenced files conform, replace them with genuinely conforming examples, or clearly label which limited aspects they demonstrate and add a complete example.
The guidance requires package documentation to conclude with deep links to developer documentation and to cover purpose, entry points, and adjacent concerns. The comparator example is only eight lines and the forms example has no developer-documentation link, so neither demonstrates the full format contributors are instructed to follow.
- AGENTS.md[545-548]
- jablib/src/main/java/org/jabref/logic/bibtex/comparator/package-info.java[1-8]
- jabgui/src/main/java/org/jabref/gui/preferences/forms/package-info.java[1-39]
| PR 16833 (2026-09-03) |
[correctness] Minimum size constraints removed
Minimum size constraints removed
The replacement increases the New Entry dialog’s preferred size but removes its existing 400×300 minimum constraints; because `BaseDialog` remains resizable, users can now shrink the content-heavy tabbed dialog until its tabs, fields, and actions are unusably clipped. This is an unrelated behavior change, since increasing the default dimensions does not require removing the existing safeguards.The PR increases the New Entry dialog’s preferred dimensions while removing its existing minimum width and height constraints. Because the dialog remains resizable, users can shrink it until its tabs, fields, and actions are unusably clipped.
NewEntryView extends BaseDialog, which enables resizing. Keep the new 1000×650 preferred size, but preserve the prior resizing behavior by restoring the 400×300 minimum dimensions on the dialog window or adding equivalent minimum constraints to the dialog pane.
- jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java[157-158]
[maintainability] Changelog uses invalid prefix
Changelog uses invalid prefix
The new Changed entry starts with `We increased` instead of the required `We changed`. This violates the prescribed user-facing changelog format.The new entry under Changed does not begin with the required We changed prefix.
Keep the entry as one user-facing sentence of at most 20 words and retain the existing issue link.
- CHANGELOG.md[72-72]
[maintainability] Blank line has trailing whitespace
Blank line has trailing whitespace
The newly added blank line contains trailing spaces, violating the repository's Java formatting conventions.The newly added blank line contains trailing whitespace.
Java changes must follow JabRef's configured formatting conventions.
- jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java[157-157]
| PR 16832 (2026-09-03) |
[correctness] `JabRef_en` duplicates localization keys
`JabRef_en` duplicates localization keys
Both new localization keys are defined twice in the English bundle. The localization consistency test explicitly rejects duplicate bundle keys, so this introduces a failing quality gate.Unsaved changes and Could not read file. each occur twice in JabRef_en.properties, causing the localization duplicate-key check to fail.
Retain the definitions grouped with the related diff strings and remove the repeated definitions near the end of the bundle.
- jablib/src/main/resources/l10n/JabRef_en.properties[1712-1713]
- jablib/src/main/resources/l10n/JabRef_en.properties[3673-3674]
[maintainability] `Optional` manually unwrapped with `get`
`Optional` manually unwrapped with `get`
The saved path is consumed through an `isPresent()`/`get()` branch instead of an idiomatic `Optional` operation. This conflicts with the required Optional usage conventions and makes the access pattern unnecessarily brittle.Replace the isPresent()/get() path access with an idiomatic Optional operation.
Preserve the existing empty-database behavior when no path exists and the current handling of checked IOException failures.
- jabgui/src/main/java/org/jabref/gui/LibraryTab.java[713-715]
[maintainability] Localization keys lack grouping
Localization keys lack grouping
The new localization keys are appended among unrelated importer messages instead of being grouped with semantically related diff and file-reading strings. In particular, `Unsaved changes` belongs beside the existing `Show diff` and `Saved file` keys.Move the new English localization keys into their appropriate semantic groups.
Unsaved changes should be placed near the existing diff labels Show diff, Committed version, and Saved file. Place Could not read file. with related file-reading errors rather than at the end of the bundle.
- jablib/src/main/resources/l10n/JabRef_en.properties[3670-3671]
- jablib/src/main/resources/l10n/JabRef_en.properties[1709-1711]
[maintainability] Changelog entry exceeds word limit
Changelog entry exceeds word limit
The new changelog statement contains 22 words before its reference, exceeding the required maximum of 20 words. It should be shortened while retaining the user-visible outcome.Reduce the new changelog statement to at most 20 words while preserving its We added prefix and user-facing description.
The issue reference can remain unchanged; only the release-note statement needs to be made more concise.
- CHANGELOG.md[20-20]
[performance] Diff parsing blocks UI
Diff parsing blocks UI
`showDiffToSavedFile()` reads and parses the entire saved library synchronously while handling the close dialog on the JavaFX thread. Large libraries can therefore freeze the application before the diff dialog appears, whereas the existing Git diff workflow performs the same loading through a `BackgroundTask`.The close-dialog diff workflow synchronously reads and parses the saved library on the JavaFX application thread, which can freeze the UI for large libraries. Move snapshot loading into a BackgroundTask, then open the diff dialog on successful completion and display the existing error dialog on failure.
The existing Git semantic-diff workflow demonstrates the intended BackgroundTask and TaskExecutor pattern.
- jabgui/src/main/java/org/jabref/gui/LibraryTab.java[710-723]
- jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogView.java[82-102]
- jabgui/src/main/java/org/jabref/gui/git/GitCommitDialogViewModel.java[137-150]
[reliability] Diff previews leak listeners
Diff previews leak listeners
Every diff preview reparses the saved file with the shared `FileUpdateMonitor`, causing each parsed TeX group to register another listener that is never removed when the temporary database is discarded. Repeated previews retain stale groups and make subsequent auxiliary-file changes invoke duplicate callbacks.Parsing a temporary saved-file snapshot with the long-lived FileUpdateMonitor registers listeners for TeX groups that outlive the diff dialog. Parse snapshot databases with a non-registering monitor, or explicitly unregister every listener when the snapshot is discarded.
TexGroup.create unconditionally registers itself, while DefaultFileUpdateMonitor retains listeners in a multimap until explicitly removed. Diff snapshots do not need live external-file monitoring.
- jabgui/src/main/java/org/jabref/gui/LibraryTab.java[710-723]
- jablib/src/main/java/org/jabref/logic/git/diff/GitDiffChecker.java[52-61]
- jablib/src/main/java/org/jabref/model/groups/TexGroup.java[49-68]
- jabgui/src/main/java/org/jabref/gui/util/DefaultFileUpdateMonitor.java[82-108]
| PR 16831 (2026-09-03) |
[correctness] Stale result attaches wrong PDF
Stale result attaches wrong PDF
The background search resolves a PDF from a cloned snapshot, but its success handler attaches that result to the original mutable entry without checking whether its DOI, URL, title, or other lookup metadata changed meanwhile. Editing the entry during the now-non-modal search can therefore save the previous citation’s PDF on the updated entry.Full-text results are calculated from an entry snapshot but later applied to the mutable original entry. Detect relevant entry changes while lookup is running and discard or restart stale results before starting the file download.
FulltextFetchers clones the entry before querying fetchers, while the success callback retains and mutates the original entry. The non-modal UI allows identifying fields to change between those operations.
- jabgui/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java[60-124]
- jablib/src/main/java/org/jabref/logic/importer/FulltextFetchers.java[69-85]
[correctness] Deleted entries receive downloads
Deleted entries receive downloads
The success handler checks only whether the library remains open, so an entry deleted during the non-modal search still receives a downloaded attachment through its detached object reference. The file is written using the library context but is absent from every saved database entry, losing the attachment and potentially leaving an orphaned file.The background full-text search retains selected BibEntry references while users can delete those entries. Before launching each download, verify that the originating database still contains the entry; skip results for removed entries and add regression coverage for deletion during lookup.
Checking only whether the library remains open does not establish that every snapshotted entry is still part of its database. Download completion otherwise writes a file and mutates a detached entry that will not be saved.
- jabgui/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java[112-127]
| PR 16830 (2026-09-03) |
[maintainability] Requirement directive is misplaced
Requirement directive is misplaced
The updated requirements file does not end with `` as required; the directive remains before later requirements.The requirements document does not end with the required <!-- markdownlint-disable-file MD022 --> directive.
The new requirement is correctly adjacent to its identifier, but the existing directive appears before subsequent requirements rather than at the end of the file.
- docs/requirements/save.md[15-41]
| PR 16829 (2026-09-03) |
[maintainability] Changelog entry format invalid
Changelog entry format invalid
The new changelog entry does not begin with an approved `We changed` prefix and exceeds the 20-word limit. This violates the required concise, user-facing release-note format.The changelog entry exceeds 20 words and does not begin with the required We changed prefix.
PR Compliance ID 41 requires one user-facing sentence of at most 20 words beginning with We changed under the Changed section.
- CHANGELOG.md[72-72]
[correctness] Changelog uses placeholder reference
Changelog uses placeholder reference
The entry commits `#PRNUM` and a `PRNUM` URL rather than a valid issue/PR reference or `TODO`. This leaves invalid release-note traceability.The changelog entry contains the unresolved placeholder PRNUM.
Use the actual PR or matching issue reference when known; otherwise use TODO as required by PR Compliance ID 43.
- CHANGELOG.md[72-72]
| PR 16826 (2026-09-03) |
[reliability] Shutdown leaves autosave callback active
Shutdown leaves autosave callback active
`AutosaveManager.shutdown()` unregisters the change listener but never terminates its repeating `ScheduledThreadPoolExecutor`, so a scheduled callback can still post an `AutosaveEvent` after the manager and library UI are discarded. This retains manager and UI state, may execute a pending save after disposal, leaks a non-daemon executor across tests, and can delay JVM termination.Update AutosaveManager.shutdown() so it terminates the repeating scheduled executor and prevents queued callbacks from posting an AutosaveEvent after the manager and library tab have been discarded. This must also ensure that the newly added tests do not leak non-daemon executors or periodic tasks after cleanup.
The constructor schedules an indefinitely repeating fixed-rate task. The restored listener can set needsSave immediately before shutdown, and unregistering that listener only prevents future changes; it does not cancel an already scheduled periodic task, which retains the manager and its event-bus listeners and may execute a pending save against a discarded component. PR Compliance ID 43 requires disposal to invalidate asynchronous work, but the current shutdown path only unregisters the listener and removes the manager from runningInstances.
- jabgui/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java[42-52]
- jabgui/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java[67-74]
- jabgui/src/test/java/org/jabref/gui/autosaveandbackup/AutosaveManagerTest.java[21-46]
[maintainability] `AutosaveManagerTest` lacks `@NullMarked`
`AutosaveManagerTest` lacks `@NullMarked`
The newly added `AutosaveManagerTest` class has no JSpecify `@NullMarked` annotation, leaving its nullability contract implicit.The new AutosaveManagerTest class lacks the required JSpecify @NullMarked annotation.
New classes must declare non-null-by-default semantics using org.jspecify.annotations.NullMarked.
- jabgui/src/test/java/org/jabref/gui/autosaveandbackup/AutosaveManagerTest.java[3-11]
[correctness] Single-character edits never autosave
Single-character edits never autosave
Registering AutosaveManager with CoarseChangeFilter does not satisfy the new “every change” requirement because consecutive one-character edits to an existing field are all marked filtered and ignored. A user who only types in one field can therefore wait indefinitely without the library being autosaved.Autosave is registered through CoarseChangeFilter, which filters consecutive one-character changes in the same existing field. Since AutosaveManager.listen ignores filtered events, those edits never request an autosave despite the requirement covering every library change.
CoarseChangeFilter marks a field event as filtered unless the entry/field changes or the character delta exceeds one. AutosaveManager must receive or act upon all changes that make the library dirty, while avoiding autosave-induced feedback loops if applicable.
- jabgui/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java[54-58]
- jabgui/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java[74-77]
- jablib/src/main/java/org/jabref/logic/util/CoarseChangeFilter.java[37-57]
- jabgui/src/test/java/org/jabref/gui/autosaveandbackup/AutosaveManagerTest.java[13-24]
[reliability] Autosave request race
Autosave request race
The restored listener writes the plain boolean `needsSave` from the change-delivery thread while the scheduled executor reads and clears it without synchronization. Updates may be invisible or overwritten when a change arrives while an autosave is executing, causing that later change to remain unsaved.Registering AutosaveManager activates cross-thread access to needsSave, but the flag is a non-volatile boolean with a non-atomic read/save/clear sequence. A request arriving during eventBus.post can be erased by the subsequent assignment to false.
Library changes invoke listen through CoarseChangeFilter; a scheduled executor independently checks the flag and synchronously posts the autosave event. Use an atomic operation that clears the pending request before beginning the save, so requests arriving during the save remain pending for the next interval.
- jabgui/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java[34-34]
- jabgui/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java[42-47]
- jabgui/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java[54-58]
- jabgui/src/test/java/org/jabref/gui/autosaveandbackup/AutosaveManagerTest.java[13-24]
| PR 16822 (2026-09-02) |
[reliability] `Throwable` catch is overbroad
`Throwable` catch is overbroad
Catching `Throwable` around the AWT headless check intercepts unrelated initialization, resource, and fatal JVM failures such as `OutOfMemoryError` and `ThreadDeath`, then misrepresents them as an unavailable AWT environment. This can reject legitimate requests with a misleading headless-mode warning while hiding the underlying failure and stack trace; the catch should be limited to the expected native-link failure.isLibraryPathAccessAllowed catches every Throwable around the AWT headless check, masking fatal JVM errors and unrelated initialization or resource failures as ordinary headless behavior.
The documented native-image failure is an UnsatisfiedLinkError caused by unavailable AWT native libraries. Narrow the catch to this expected failure so only it activates the headless fallback, while unrelated errors propagate with their original diagnostics and stack traces.
- jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWResource.java[267-276]
[observability] AWT failure is not logged
AWT failure is not logged
The caught AWT linkage failure is converted into `headless = true` without logging the throwable, losing its stack trace and diagnostic context. Log it at debug level or higher with the throwable as the final argument.The AWT failure is handled without recording the throwable, so the reason for entering fallback mode cannot be diagnosed.
Use a parameterized logger call at debug level or higher and pass the caught throwable as the final logger argument.
- jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWResource.java[270-272]
[correctness] Default smoke task lacks provider
Default smoke task lacks provider
`nativeSmokeTest` defaults to port 9998 when `jabsrv.native.smoke` is absent, but the external Jersey provider is only added when that property exists. Running `:jabsrv:nativeSmokeTest` without the property therefore configures `ExternalTestContainerFactory` while omitting its implementation from the task classpath, causing test initialization to fail.nativeSmokeTest can be invoked without jabsrv.native.smoke and defaults to port 9998, but its required external Jersey provider is only included when that property is supplied.
Make the task configuration internally consistent. Either require jabsrv.native.smoke with a clear configuration error, or give the task a dedicated classpath that always contains the external provider.
- jabsrv/build.gradle.kts[23-26]
- jabsrv/build.gradle.kts[40-63]
| PR 16817 (2026-09-02) |
[correctness] BLG fixtures remain unlabeled
BLG fixtures remain unlabeled
The LaTeX-support rule matches `logic/biblog`, but the parser's BLG fixture is stored under `logic/blg`, so a PR changing that fixture alone will not receive `component: latex-file-support`. This leaves part of the newly advertised BLG area uncovered.The new component: latex-file-support mapping does not match BLG fixtures stored under org/jabref/logic/blg, leaving fixture-only PRs unlabeled.
BibtexLogParserTest directly reads src/test/resources/org/jabref/logic/blg/Chocolate.blg, while the labeler only includes the similarly named logic/biblog package.
- .github/labeler.yml[180-191]
- jablib/src/test/java/org/jabref/logic/biblog/BibtexLogParserTest.java[28-31]
[maintainability] JabKit search uses old label
JabKit search uses old label
The labeler now applies `component: jabkit`, but the component documentation still searches for the renamed `component: JabKit [cli]` label. The documented open-issues link therefore omits issues carrying the current label.The JabKit documentation still searches for the old label after the labeler was changed to component: jabkit.
The architecture page is referenced by the labeler as the component-label documentation and uses an exact GitHub label query.
- .github/labeler.yml[160-162]
- docs/architecture-and-components.md[227-232]
[maintainability] Journal search uses old label
Journal search uses old label
The labeler now applies `component: journal-abbreviations`, but the component documentation still searches for `component: journal abbreviations`. The documented open-issues link therefore omits issues carrying the current label.The journal-abbreviations documentation still searches for the old space-separated label after the labeler rename.
The architecture page uses an exact GitHub label query, so it must use the same renamed label as the automation.
- .github/labeler.yml[170-174]
- docs/architecture-and-components.md[234-239]
- Home
- General Information
- Development
- Please go to our devdocs at https://devdocs.jabref.org
- Completed "Google Summer of Code" (GSoC) projects
- GSoC 2026 - Improved LibreOffice‐JabRef integration
- GSoC 2026 - OCR and AI Integration for JabRef
- Summer 2026 - Improving Systematic Literature Review (SLR) Support
- GSoC 2025 ‐ Git Support for JabRef
- GSoC 2025 - LSP
- GSoC 2025 - Walkthrough and Welcome Tab
- GSoC 2024 ‐ Improved CSL Support (and more LibreOffice‐JabRef integration enhancements)
- GSoC 2024 - Lucene Search Backend Integration
- GSoC 2024 ‐ AI‐Powered Summarization and “Interaction” with Academic Papers
- GSoC 2022 — Implement a Three Way Merge UI for merging BibTeX entries
- GSoC 2021 - Improve pdf support in JabRef
- GSoC 2021 - Microsoft Word Integration
- GSoc 2019 - Bidirectional Integration — Paper Writing — LaTeX and JabRef 5.0
- GSoC Archive
- Release
- JabCon Archive