slack-relayfile-writeback-debugging

star 2

Use when an outbound Slack writeback (channel post, thread reply, or DM) silently fails to deliver — no error, no dead-letter. Covers the writeback pipeline, the relayfile ops-API getOp recipe to read the persisted lastError, where the Slack error actually surfaces (NOT the consumer logs), the missing_scope/im:write 3-place fix, cf-vs-bridge dispatch, and duplicate/silent-send pitfalls.

AgentWorkforce By AgentWorkforce schedule Updated 6/9/2026

name: slack-relayfile-writeback-debugging description: Use when an outbound Slack writeback (channel post, thread reply, or DM) silently fails to deliver — no error, no dead-letter. Covers the writeback pipeline, the relayfile ops-API getOp recipe to read the persisted lastError, where the Slack error actually surfaces (NOT the consumer logs), the missing_scope/im:write 3-place fix, cf-vs-bridge dispatch, and duplicate/silent-send pitfalls.

Debugging Slack writeback non-delivery (relayfile ↔ cloud)

You are diagnosing why an outbound Slack writeback did not reach Slack. A writeback is a JSON file dropped under a mounted command root (…/channels/<id>__<slug>/messages[/<ts>/replies]/<file>.json or …/users/<U>/messages/<file>.json). Confirm with evidence, never a hypothesis.

Pipeline (cloud, AgentWorkforce/cloud)

local file → mount pushes up → ingest creates a writeback OP → WRITEBACK_QUEUE
  → batch executor → cf OR bridge provider → Slack API
  • Ingest → op: packages/relayfile/src/durable-objects/handlers/ops.ts recordMutations (~1043-1101) makes a writeback op for ANY agent_write; provider_sync origin is suppressed (returns "succeeded" with NO op).
  • Queue consumer (batch): packages/relayfile/src/queue-consumer.ts:71executeProviderWritebackBatch. It logs ONLY writeback.start (provider-executor.ts:258) — never the Slack error. So wrangler tail relayfile-writeback-consumer confirms dispatch but never shows why it failed.
  • Path allowlist: provider-executor.ts getUnsupportedReason (951-1010); SLACK_UPSERT_WRITEBACK_PATH (1203-1205) — includes users/<U>/messages (DM path allowed).
  • cf-vs-bridge fork: :132 (single) / :288 (batch). Default writeback_dispatch_via = bridge (workspace.ts:1062); cf has the idempotency guard the bridge lacks (cloud#1985).

THE diagnostic: read the op's lastError (ground truth)

curl -H "Authorization: Bearer <token from <mount>/.relay/creds.json>" \
     -H "X-Correlation-Id: anything" \
     https://api.relayfile.dev/v1/workspaces/<rw_id>/ops/<opId>
  • ⚠️ X-Correlation-Id is required — without it: HTTP 400 missing X-Correlation-Id header.
  • base = DEFAULT_RELAYFILE_BASE_URL = https://api.relayfile.dev.
  • <rw_id>: grep the mount's .relay/state.json for "workspaceId".
  • token: .relay/creds.json token (JWT, has ops:read; re-minted ~hourly — re-read it).
  • <opId>: from the writeback.start log, or list …/ops?limit=N and match path.
  • Result: {status, lastError, attemptCount}. status:failed + attemptCount:0 = a permanent (non-retryable) failure that is not dead-lettered and not surfaced — the "silent, no error" signature.
  • The error ALSO surfaces as the cf audit event console.info("[relayfile] writeback audit event", {event}) in cloud-web-worker (event.metadata.slackError) — but getOp is faster + persisted.

Known failure modes → fix

  • lastError: missing_scope on a DM → the Slack app lacks im:write. DM send calls conversations.open (needs im:write) before chat.postMessage; channels only need chat:write (so channels work, DMs don't). missing_scope isn't retryable (providers/slack.ts:476-485) → permanent_failure → silent. Fix = 3 places, in order: (1) Slack app → Bot Token Scopes → add im:write(+mpim:write) → reinstall; (2) Nango dashboard → add the same to the slack-relay integration's requested scopes (the OAuth request set lives here, NOT in the repo — easy to miss); (3) re-OAuth/reconnect (existing tokens get no retroactive grant). Verify: re-fire → getOp succeeded.
  • Duplicate posts → the legacy bridge path lacks the cf idempotency guard. Delete the command file after confirmed delivery; migrate the workspace to cf (cloud#1977).
  • Silently never sent (looks clean) → on a poll mount the push is ~30s-cadence. The workspace revision counter is GLOBAL (not a dispatch signal); deleting the command file before a push cycle uploads it = silent non-delivery. Confirm dispatch by: operator receipt, OR the file surviving ≥1–2 cycles with pendingWriteback 1→0, OR (top-level) the bot echo in the flat messages/<ts>/ tree. Thread REPLIES never echo.
  • mirror does NOT auto-delete dispatched command files — manual delete-after-send is still required, or a future clearState re-dispatches them.

Don't

  • Don't conclude from wrangler tail of the consumer (it only logs writeback.start).
  • Don't blame adapter version: @relayfile/adapter-slack 0.3.9 already resolves DM→post_dm; cloud#1997 was a READ-side fix. The send path is present + deployed — the gap is almost always scope/config, confirmed via getOp.
Install via CLI
npx skills add https://github.com/AgentWorkforce/workforce --skill slack-relayfile-writeback-debugging
Repository Details
star Stars 2
call_split Forks 1
navigation Branch main
article Path SKILL.md
More from Creator
AgentWorkforce
AgentWorkforce Explore all skills →