Skip to content

Commit fe72ac1

Browse files
authored
Split Slack integration event context retry fix (#145)
1 parent 7187fee commit fe72ac1

2 files changed

Lines changed: 81 additions & 1 deletion

File tree

src/main/__tests__/integration-event-bridge.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,76 @@ test('slack raw-id and slug alias duplicates suppress when one context read is s
776776
assert.match(harness.sent[0].input.text, /Message:\nreadable Slack message/u)
777777
})
778778

779+
test('slack edits after a blind alias claim still inject once the content changes', async () => {
780+
let messageText = 'original Slack message'
781+
const harness = makeHarness(['alice'], {
782+
readFileResponse: (_workspaceId, path) => {
783+
if (!path.includes('__proj-cloud')) throw new Error('remote file not ready')
784+
return {
785+
path,
786+
revision: 'rev-context',
787+
contentType: 'application/json',
788+
content: JSON.stringify({ provider: 'slack', text: messageText }),
789+
encoding: 'utf-8'
790+
}
791+
}
792+
})
793+
794+
await withMockedNow('2026-06-05T14:00:00.000Z', async () => {
795+
await harness.bridge.reconcile('project-1', [
796+
integration({
797+
provider: 'slack',
798+
integrationId: 'slack-1',
799+
mountPaths: ['/slack/channels/C123ABC__proj-cloud'],
800+
downloadHistoricalData: false,
801+
scope: { notifyAgents: ['alice'] }
802+
})
803+
])
804+
})
805+
806+
// Raw-id copy first: every targeted read fails and the expanded event only
807+
// carries the sparse relayfile pointer, so the injection is blind.
808+
await harness.emit({
809+
...changeEvent(
810+
'/slack/channels/C123ABC/messages/1780668000_000000/meta.json',
811+
'slack',
812+
{ digest: 'revision:raw-copy' }
813+
),
814+
expand: async () => ({
815+
level: 'full',
816+
path: '/slack/channels/C123ABC/messages/1780668000_000000/meta.json',
817+
data: {
818+
path: '/slack/channels/C123ABC/messages/1780668000_000000/meta.json',
819+
deleted: false
820+
}
821+
})
822+
} as ChangeEvent)
823+
await waitForSent(harness, 1, 2_500)
824+
assert.equal(harness.sent.length, 1)
825+
assert.match(harness.sent[0].input.text, /Message: unavailable; targeted context read did not return content\./u)
826+
827+
// The slug alias copy of the same record carries content: suppressed as a
828+
// duplicate, but the claim learns the content hash.
829+
await harness.emit(changeEvent(
830+
'/slack/channels/C123ABC__proj-cloud/messages/1780668000_000000/meta.json',
831+
'slack',
832+
{ digest: 'revision:slug-copy' }
833+
))
834+
await waitForDropped('project-1', 1, 2_500)
835+
assert.equal(harness.sent.length, 1)
836+
837+
// A genuine edit changes the content hash and must inject again.
838+
messageText = 'edited Slack message'
839+
await harness.emit(changeEvent(
840+
'/slack/channels/C123ABC__proj-cloud/messages/1780668000_000000/meta.json',
841+
'slack',
842+
{ digest: 'revision:slug-edit' }
843+
))
844+
await waitForSent(harness, 2)
845+
assert.equal(harness.sent.length, 2)
846+
assert.match(harness.sent[1].input.text, /Message:\nedited Slack message/u)
847+
})
848+
779849
test('remote replayed events older than the subscription session are dropped by default', async () => {
780850
const harness = makeHarness()
781851

src/main/integration-event-bridge.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2581,7 +2581,17 @@ export class IntegrationEventBridge {
25812581
: undefined
25822582
const existing = this.slackLogicalInjections.get(key)
25832583
if (existing) {
2584-
if (!contentHash || !existing.contentHashes || existing.contentHashes.has(contentHash)) {
2584+
if (!contentHash) {
2585+
return false
2586+
}
2587+
if (!existing.contentHashes) {
2588+
// A blind claim (context read returned nothing) suppresses the late
2589+
// content-bearing alias copy, but must learn its hash so a genuine
2590+
// edit afterwards still injects instead of matching the blind claim.
2591+
existing.contentHashes = new Set([contentHash])
2592+
return false
2593+
}
2594+
if (existing.contentHashes.has(contentHash)) {
25852595
return false
25862596
}
25872597
existing.contentHashes.add(contentHash)

0 commit comments

Comments
 (0)