Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(slack): scope channel linking to user membership, not the whole workspace

merged#2597CedarCopilot

CedarCopilot wants to merge 4 commits into staging from fix/slack-link-scope-v2

Live on prod since Wed, 2 of 7 behaviors workingTimeline and evidence
  1. Opened
    Aug 26, 2026, 5:03 PM
  2. Aug 26, 2026, 6:35 PM
  3. Merged
    Aug 26, 2026, 7:03 PM
  4. Live on prod
    Aug 26, 2026, 7:03 PM
  5. Observed 2 days
    Aug 26, 2026, 7:03 PM
  6. Pipelines steady after this deploy
    Aug 26, 2026, 7:03 PM
  7. Verified

    Live on prod since Wed, 2 of 7 behaviors working

    Aug 29, 2026, 8:23 AM

Behaviors Libra is checking

Link-only Slack sync creates a user channel container when one does not yet exist, allowing newly connected users to link selected channels before any messages have been ingested.Workingmedium confidence

Since the deploy, 301 prod OTEL spans matching linkSlackChannelOnly ran with 0 error spans. In the 59h before the deploy the same surface ran 361 OTEL spans with 0 error spans. Confidence is medium because the surface was already clean before the deploy, so this confirms no.

prod, checked Aug 29, 2026, 5:22 AM
If Slack workspace metadata listing fails, channel linking still evaluates the user's member channels with unknown member counts instead of abandoning all linking work.Workingmedium confidence

Post-deploy OTEL had 0 instrumented spans for the exact function, so verification relies on CloudWatch. The fallback warning appeared 93 times in prod after 2026-08-27T02:03:14Z, including workspace-listing failures with token_revoked and ratelimited. Concrete [SLACK_LINK].

prod, checked Aug 29, 2026, 3:23 AM
Member channels are enriched with workspace member counts so linking re-evaluates a channel when participants join or leave, even when Slack channel metadata has not changed.Inconclusivelow confidence

Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.

prod, checked Aug 29, 2026, 6:23 AM
Slack pattern-based channel linking now evaluates only public and private channels that the connecting user is a member of, rather than matching channels across the entire workspace.Inconclusivelow confidence

Verification reached its 120s wall-clock budget before a tied verdict. Libra is keeping this intent verifying until the next check.

prod, checked Aug 29, 2026, 6:25 AM
Slack membership discovery uses the installing user's token and paginates `users.conversations` through all available pages so private and later-page member channels remain eligible for linking.Inconclusivelow confidence

Unable to complete verification: the available telemetry shows repeated successful listUserSlackChannels logs, including multi-page runs, but the returned data is truncated and does not establish the full post-deploy success/error rate, latency, distinct-user count, or absence.

prod, checked Aug 29, 2026, 7:22 AM
Genuinely malformed Slack sync configurations now produce a structured error and an error result instead of silently appearing as an unconfigured account.Inconclusivelow confidence

Unable to complete verification within the available telemetry loop; the retrieved CloudWatch results show linker activity before the deployment cutoff, but not yet a post-deploy invalid-config outcome or verdict.

prod, checked Aug 29, 2026, 7:23 AM

Libra has verdicts on 2 of 7 tracked behaviors on prod; 5 are still being checked. Libra checks hourly for 3 days after each deploy.

Replaces #2596, which was branched off tool-surface/master-agent-families and carried three unrelated customer docs. Same two commits, cut from staging.

Root cause

Linking selected candidates from listSlackChannels → Slack's conversations.list, every channel in the workspace. Corgi names customer channels {company}-corgi, so endsWith: "-corgi" matched 445 workspace channels for a user who is a member of 16. Each candidate costs a members fetch plus a DB write at SLACK_SYNC_CONCURRENCY_LIMIT = 1, exhausting both the Step Functions 10-minute state timeout and the HTTP gateway timeout , while linking nothing.

An earlier hypothesis (a hung call missing Effect.timeout) was disproved by the logs: every batch emitted initial_sync_step_complete and there were zero initial_sync_slack_channel_error rows.

Changes

  1. listUserSlackChannelsWithMetadata , intersects users.conversations (membership) with conversations.list (metadata). Membership decides which channels; the workspace listing supplies num_members.

    Why the intersect rather than membership alone: users.conversations omits num_members (verified live). linkMatchingSlackChannelsForUser skips a channel as unchanged only when the count and updated both hold steady , and Slack's updated does not move on a member join (verified: channels created and invited-to the same day report updated === created). Membership-only would have pinned every count to 0 and silently stopped re-evaluating channels that become linkable because a customer joined. Both calls are cheap listings; the cost that timed out the sync was per-channel work, still bounded by the intersection.

  2. Container bootstrap , linkSlackChannelOnly returned success:false whenever no channel_containers row existed, and containers were only ever created by message ingest. That made link-only sync structurally unable to onboard a new user: every channel is uningested by definition. Now bootstraps via the idempotent ensureContainerForMember. Verified this doesn't leak into the message-fetch cron, which requires cu.conversation_id IS NOT NULL.

  3. Config-shape coercion , SlackConfigSchema.autoSelectPatterns now accepts a legacy bare object and normalises it to an array. Raw SQL bypasses the schema on write, so the read path is the only place that can guarantee the shape; a parse failure there rejected the entire config and read as "nothing configured". Six connections across five orgs (Greptile, Hamming Ɨ2, Invoicebutler, Listenlabs, Understudylabs) were silently linking zero channels. Genuinely unparseable values still fail loudly, and the failure is now a structured error counted as errors: 1.

  4. Fixed the source , this repo's account-setup runbook documented the bare-object form in its own Phase 7 SQL, which is where these configs came from. Corrected, plus a rule recording the trap.

Data repair (already applied)

All 8 malformed connections normalised to the array form; 0 remain fleet-wide.

Tests

101 passing across the Slack suite (up from 94 pre-change). Typecheck clean for touched files , the 2 errors in phase2c-seed-playbook.ts are uncommitted WIP that predates this work. pnpm deps:check āœ” no violations.

Full report: docs/bugs/slack-channel-link-timeout-2026-08-26.md

šŸ¤– Generated with Claude Code

Greptile Summary

The PR scopes Slack auto-link candidates to the installing user's channel memberships, normalizes legacy Slack pattern configuration, and bootstraps containers for link-only onboarding.

  • Intersects users.conversations membership with workspace metadata to bound per-channel work.
  • Adds compatibility for legacy bare-object autoSelectPatterns and structured configuration errors.
  • Creates missing channel containers during link-only sync and updates the account-setup guidance.

Confidence Score:

Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • .claude/skills/account-setup-from-transcript/SKILL.mdno production surface mapped
  • apps/server/src/db/integration-schemas.tsno production surface mapped
  • apps/server/src/services/integrations/slack/__tests__/channel-change-detection.test.tsno production surface mapped
  • apps/server/src/services/integrations/slack/__tests__/list-user-slack-channels.test.tsno production surface mapped
  • apps/server/src/services/integrations/slack/__tests__/slack-config-shape.test.tsno production surface mapped
  • apps/server/src/services/integrations/slack/link-matching-channels.tsno production surface mapped
  • apps/server/src/services/integrations/slack/slack-api.tsno production surface mapped
  • apps/server/src/services/integrations/slack/slack-sync.tsno production surface mapped