fix(slack): form encode Slack Web API requests - #3824
Closed
nahyeongjin1 wants to merge 1 commit into
Closed
Conversation
The provider sent every Slack Web API call with `Content-Type: application/json`. `conversations.replies` is form-encoded only and answers `invalid_arguments` for a JSON body, so the reconciliation lookup that follows an uncertain root post threw an uncaught SlackProviderError and killed the notification daemon before it published ownership. The failure surfaced as `slack runtime is not ready or attached` and `Unable to attach or spawn slack daemon owner` with valid credentials, scopes, and configuration, and no session ever received a Slack thread. Form encoding is the canonical Slack request format and is lossless here: every request body is a flat `Record<string, string | undefined>`. Undefined entries are dropped so optional fields cannot reach Slack as the literal string "undefined", which `JSON.stringify` also omitted. The existing reconciliation test only asserted that the body contained `client_msg_id`, which held under both encodings. The added test asserts the wire encoding directly and fails against the previous behavior. Verified with: bun test packages/coding-agent/test/sdk-slack-live-provider.test.ts Co-authored-By: Claude <noreply@anthropic.com>
Contributor
Author
This was referenced Aug 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
SlackLiveProvider#requestnow sends Slack Web API calls asapplication/x-www-form-urlencodedinstead ofapplication/json.Why
conversations.repliesis form-encoded only. It answersinvalid_argumentsfor a JSON body:
findMessageByClientMsgIdcalls it for the reconciliation lookup that followsan uncertain root post.
#apiturns theok=falseinto a thrownSlackProviderError, which is uncaught in the daemon's post path, so thenotification daemon dies before publishing ownership:
Observed symptoms with valid credentials, scopes (
chat:write,channels:history), and a completegjc notify setup slack:gjc daemon restart slack->slack daemon did not publish ownership after spawninggjc notify test --provider slack->slack runtime is not ready or attachedensureSlackDaemon->Unable to attach or spawn slack daemon ownergjc notify health --provider slack --probereports OK throughout, becauseauth.testandapps.connections.openboth accept JSON. Only the readmethods reject it, which makes this hard to attribute to encoding.
Note
conversations.historyaccepts JSON, so the failure only appears on thethread-reply branch after the history lookup misses.
Why form encoding for all calls
Form encoding is Slack's canonical request format and is lossless here: every
#requestbody is a flatRecord<string, string | undefined>.formBodydrops undefined entries so an absent optional field cannot reach Slack as the
literal string
"undefined"—JSON.stringifydropped them too, so thispreserves existing behavior.
Tests
The existing reconciliation test asserted only that the request body contained
client_msg_id, which held under both encodings — that is why this slippedthrough. The added test asserts the wire encoding directly:
Reverting the source change makes the new test fail with
Expected: application/x-www-form-urlencoded; charset=utf-8/Received: application/json; charset=utf-8.Also run:
biome checkandtsc --noEmitclean on the changed files.sdk-slack-daemon.test.tsandsdk-slack-thread-state.test.tsfail in a freshworktree because
pi_natives.darwin-arm64.nodeis not built; they failidentically on an unmodified checkout.
Verified against a live workspace
After patching a local
0.12.11install, the daemon attached and posted realsession roots: