Problem
The current resolveUserId() trusts syntactically valid user IDs and returns the first exact handle/email match it encounters. It does not verify active-human status or continue after a provisional match to detect ambiguity.
That is convenient for existing operational commands, but it cannot prove that a human-facing mention is safe. Slack documents that users.list is cursor-paginated, unordered, and includes deactivated users. A later page may contain another exact match, or enumeration may fail after an earlier provisional match.
There is also a separate first-match resolver in search-query.ts with different matching behavior.
Proposed command
Add a reusable TypeScript batch resolver behind an opt-in command:
agent-slack user resolve <identities...>
Strict, fail-closed resolution would be the command’s only behavior, so it would not need a --strict switch. Existing operational resolution would remain unchanged initially. This avoids adding full-directory latency, rate-limit exposure, or active-human restrictions to user get, DM/channel operations, invites, and historical search.
Resolution contract
- Accept one or more explicit identities in one workspace.
- Classify inputs deterministically:
U… or W… → user ID
- email → profile email
@handle or bare handle → Slack name
- whitespace-bearing full name →
real_name or display_name
- Normalize case and whitespace, but perform exact matching only.
- Traverse every
users.list page once for the entire batch, with no arbitrary page cap.
- Verify supplied user IDs against the directory instead of passing them through.
- Deduplicate repeated directory records by canonical user ID.
- Only active humans may resolve. Relevant records must provide enough lifecycle/type evidence to prove eligibility; exclude deactivated users, Slackbot, and positive bot-specific signals.
is_app_user alone is not a bot signal.
- Report ambiguity when multiple eligible IDs match within the fields authorized for that input type. Equality in unrelated identity fields does not block an otherwise unique typed match.
- Treat request failures, malformed pages or cursors, repeated cursors, conflicting duplicate records, or unknown eligibility on a relevant match as incomplete evidence.
- Do not report definitive per-input outcomes from an incomplete directory traversal.
Output contract
For a complete traversal, return resolved, not_found, or ambiguous per input alongside directory evidence:
{
"workspace": "https://example.slack.com",
"directory": {
"status": "complete",
"pages": 2
},
"safe_to_mention": true,
"results": [
{
"source": "@alice",
"status": "resolved",
"matched_by": ["input.handle->slack.name"],
"mention": "<@U01234567>"
}
]
}
Mention output must be atomic. Emit live <@U…> tokens only when pagination completed and every input resolved uniquely. If any input is missing or ambiguous, or directory evidence is incomplete, the entire output must contain no live mention tokens—even for inputs that provisionally resolved. Echoed input must also be inert.
Exit successfully only when safe_to_mention is true.
Problem
The current
resolveUserId()trusts syntactically valid user IDs and returns the first exact handle/email match it encounters. It does not verify active-human status or continue after a provisional match to detect ambiguity.That is convenient for existing operational commands, but it cannot prove that a human-facing mention is safe. Slack documents that
users.listis cursor-paginated, unordered, and includes deactivated users. A later page may contain another exact match, or enumeration may fail after an earlier provisional match.There is also a separate first-match resolver in
search-query.tswith different matching behavior.Proposed command
Add a reusable TypeScript batch resolver behind an opt-in command:
Strict, fail-closed resolution would be the command’s only behavior, so it would not need a
--strictswitch. Existing operational resolution would remain unchanged initially. This avoids adding full-directory latency, rate-limit exposure, or active-human restrictions touser get, DM/channel operations, invites, and historical search.Resolution contract
U…orW…→ user ID@handleor bare handle → Slacknamereal_nameordisplay_nameusers.listpage once for the entire batch, with no arbitrary page cap.is_app_useralone is not a bot signal.Output contract
For a complete traversal, return
resolved,not_found, orambiguousper input alongside directory evidence:{ "workspace": "https://example.slack.com", "directory": { "status": "complete", "pages": 2 }, "safe_to_mention": true, "results": [ { "source": "@alice", "status": "resolved", "matched_by": ["input.handle->slack.name"], "mention": "<@U01234567>" } ] }Mention output must be atomic. Emit live
<@U…>tokens only when pagination completed and every input resolved uniquely. If any input is missing or ambiguous, or directory evidence is incomplete, the entire output must contain no live mention tokens—even for inputs that provisionally resolved. Echoed input must also be inert.Exit successfully only when
safe_to_mentionis true.