Skip to content

Slack ticket mirror: reply-level dedup, surrogate-safe truncation, WebClient reuse #152

Description

@NathanTarbert

Follow-ups deliberately left open by #150 (the Slack ticket mirror). All three were raised by the CR rounds on that PR and verified; none blocks the mirror shipping inert.

1. A duplicate reply job re-posts its message

handleSlackMirror makes the TicketExternalLink row the idempotent identity of the thread, so two jobs can no longer open rival threads. It does not dedupe reply posts: if a reply job is retried after its postMessage succeeded (a job timeout that does not cancel the handler, a throw in reportProgress, a re-thrown transient error), the same message is posted into the thread again.

Fixing this needs a per-message marker — e.g. a mirroredAt column on Message, or a metadata entry on the link row keyed by message id — checked before posting. That is a schema decision, which is why it was not smuggled into #150.

packages/outpost/queue/src/handlers/slack-mirror.ts — the idempotency docblock states this limitation explicitly.

2. truncate splits surrogate pairs

value.slice(0, max - 1) can cut an astral character (emoji, many CJK extension chars) in half, producing a lone surrogate in the Slack payload. Use Array.from / Intl.Segmenter or a code-point-aware slice.

3. A new WebClient per job

buildPoster constructs a fresh @slack/web-api client on every job. At mirror volumes this is minor, but it discards connection reuse and the client's rate-limit state, which is precisely the state you want shared across posts. Cache one client per token.

Found by: CR rounds 1-2 on #150.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: integrationsSlack, Teams, Linear, GitHub App, Postmark, Pathfinder, HubSpotroadmapTracked on the Outpost roadmaproadmap: nextRoadmap horizon: after launch path clears

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions