feat: resolve Slack @mentions by display name - #71
Conversation
Add SlackMentionResolver for bot-token mode to translate display-name mentions to Slack user/subteam IDs via users.list and usergroups.list, with caching, ambiguity warnings, and webhook graceful degradation. Closes #46 Generated by Codex Co-authored-by: Cursor <cursoragent@cursor.com>
Generated by Codex Co-authored-by: Cursor <cursoragent@cursor.com>
Code ReviewOverall: Solid feature addition with good documentation and test coverage. A few issues worth addressing. TestsWeak webhook test ( Missing test: No test for Thread Safety
Documentation
UXThe fallback behavior (leave mention unresolved + warn) is the right call — silent corruption would be worse. The priority order (username → display name → real name) is sensible. |
Code ReviewOverall: well-structured feature with solid test coverage and clear docs. TestsThe Weak assertion in webhook test ( Thread safety
DocumentationDocs in Minor
|
| @@ -0,0 +1,348 @@ | |||
| /* | |||
| * Copyright 2025, Seqera Labs | |||
There was a problem hiding this comment.
| * Copyright 2025, Seqera Labs | |
| * Copyright 2026, Seqera Labs |
There was a problem hiding this comment.
Updated to 2025-2026 to match the rest of the plugin (not 2026 alone).
| /** Matches unresolved user mentions; skips Slack user IDs (U...) and workspace IDs (W...). */ | ||
| private static final java.util.regex.Pattern USER_MENTION_PATTERN = | ||
| ~/<@(?!U[A-Z0-9]+(?:\|[^>]+)?>)(?!W[A-Z0-9]+(?:\|[^>]+)?>)([^>]+)>/ |
There was a problem hiding this comment.
What if a users name starts with U or W?
There was a problem hiding this comment.
Good catch. Resolved mentions are now detected by Slack’s uppercase ID shape (U/W + 4+ alphanumerics, optional |label), so mixed-case display names like <@Ulysses> still resolve. Added a test for that case.
| @@ -0,0 +1,228 @@ | |||
| /* | |||
| * Copyright 2025, Seqera Labs | |||
There was a problem hiding this comment.
| * Copyright 2025, Seqera Labs | |
| * Copyright 2026, Seqera Labs |
There was a problem hiding this comment.
Same — 2025-2026 here too.
Synchronize user cache loading, strengthen webhook warning test, and add coverage for users.list ok=false responses. Generated by Codex Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed Claude review in 27f79ff:
|
Code ReviewThis PR adds Bugs1. Empty cache permanently set on API failure When Fix: only assign 2. The capture group 3. Field-priority loop bails out early on first ambiguous field If 4. Mid-pagination failure is silent If page 1 succeeds and a subsequent page returns 5.
Tests6. No test verifies Test helpers override Docs
|
Drop synchronized per-run caching, consolidate Slack API calls into a single apiGet helper, and trim tests. Update copyright headers to 2025-2026. Generated by Codex Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks for the feedback — pushed a simplification pass in 6c61029. Removed / simplified
Unchanged behavior (issue #46)
Copyright
Size
|
Code ReviewThis PR adds Bugs1. Data race on lazy-init cache —
2. Silent truncation on mid-pagination failure —
3. Early-return on ambiguity skips potentially unique later fields —
Tests4. The existing 5. Webhook warning test verifies dispatch, not behavior —
Cleanup6. Redundant The method calls |
Drop subteam/usergroups support, remove cache abstractions, and use a single lazy users.list fetch with the same HTTP pattern as BotSlackSender. Generated by Codex Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks for the feedback — agreed this had grown more complex than a short-lived Nextflow run needs. What changed in the latest push:
Line counts:
|
Code ReviewThis PR adds Two correctness issues to address before merge: Bugs
private static final java.util.regex.Pattern SLACK_USER_ID = ~/^[UW][A-Z0-9]{4,}(\|.+)?$/Any all-uppercase display name of 5+ chars starting with
private List<Map> users
Minor
The
private final Set<String> loggedWarnings = Collections.synchronizedSet(new HashSet<String>())The warning text is always the same compile-time constant, so this |
Code ReviewThis PR adds Correctness
If Fix: warn whenever
In Example: two users share Fix: replace Testing
A test using the anonymous-class override pattern (already used in
The test asserts Minor / Plausible
|
Use POST for users.list, surface Slack API errors (especially missing_scope), validate users:read at startup when messages contain @mentions, and document scope requirements in the example config. Generated by Codex Co-authored-by: Cursor <cursoragent@cursor.com>
Point users to Slack app OAuth settings with step-by-step scope setup when users.list fails or @mentions cannot be resolved. Generated by Codex Co-authored-by: Cursor <cursoragent@cursor.com>
Cache failed users.list state after startup validation so sendMessage does not repeat the same permission warning. Generated by Codex Co-authored-by: Cursor <cursoragent@cursor.com>
Remove startup mention validation; scope errors are logged once when the first message with display-name mentions is sent. Generated by Codex Co-authored-by: Cursor <cursoragent@cursor.com>
Code ReviewThis PR adds Bug —
|
Generated by Codex Co-authored-by: Cursor <cursoragent@cursor.com>
Code ReviewThis PR adds Bugs1. Mid-pagination failure silently truncates the user list In // Fix: set usersListUnavailable on any pagination failure
if (!response) {
if (result.isEmpty()) {
usersListUnavailable = true
} else {
log.warn "Slack plugin: users.list pagination failed after ${result.size()} users — @mention resolution may be incomplete"
}
break
}2.
message = [text: ':wave: Hi <@Jane>!']This silently bypasses Documentation gaps3. PR description claims subteam support ( The PR summary says:
But the 4. Example config uses a real contributor username message = 'Hi <@adamtalbot>'Users copying this example will get a confusing "no matching Slack user found" warning in their own workspace. Use a generic placeholder like Minor5.
|
Code ReviewThis PR adds Bugs
if (!response) {
if (result.isEmpty()) { // <-- only sets unavailable if NO users were fetched
usersListUnavailable = true
users = result
}
break
}If page 1 succeeds (200+ users) and page 2 fails, the partial list is cached with UX
|
Code ReviewThis PR adds 1. PR description promises subteam support that isn't implemented
The PR summary says: "translate private static final java.util.regex.Pattern MENTION = ~/<@([^>]+)>/There is no 2. Token trimming inconsistency between
|
Code ReviewThis PR adds Findings (most severe first)1. Pattern Real Slack IDs are always alphanumeric and at least 9 chars (e.g. 2. Mid-pagination failure silently caches partial user list When page 1 of Should log a warning on partial failure regardless of 3. Per-mention "not found" warning has no deduplication The ambiguous and "no matching Slack user found" log paths in 4. Ambiguity on When two users share the same Minor
|
Summary
SlackMentionResolverto translate<@DisplayName>and<!subteam^TeamName>into Slack user/subteam IDs before bot messages are sentusers.list/usergroups.listfor the pipeline run; warn when webhooks cannot resolve display-name mentionsTest plan
./gradlew test(168 tests passing)Closes #46
Made with Cursor