Show that a Slack turn is still running - #1166
Merged
Merged
Conversation
A service turn that takes a while left its Slack thread completely silent
until the answer arrived. From the thread that is indistinguishable from a
delivery that was dropped, and the reasonable response — send it again —
starts a second turn in the same thread.
Slack channels now show that a turn is in flight, configured per channel:
[channels.my-bot]
progress = "placeholder" # off | placeholder | reaction | both
- placeholder (default) posts a thread message that later becomes the answer
- reaction marks the triggering message 👀, then ✅ (⚠️ on failure)
Nothing appears for a turn that answers promptly. The affordance waits 8s
first, so the common case stays exactly as clean as it is today; it exists for
a wait that has already become long enough to look like a failure.
Three things make it honest rather than decorative:
- A turn stopped at a tool approval says so and names the tool. That turn will
not resume until an operator acts in the TUI, and the person waiting in
Slack cannot see that prompt — so "working on it" would be a lie. The
ingress publishes the turn's phase on a watch channel; the channel renders
it. Publishing is advisory and nothing waits on a reader.
- Whatever the affordance posted is replaced by the outcome, including when
the turn fails. Failures previously went only to the daemon log, leaving the
asker with silence; now the thread says the turn ended without an answer.
- Anything cosmetic that Slack refuses is logged, not fatal. reactions:write
is a scope an existing app install will not have, so a missing scope must
not turn a delivered answer into a failed delivery.
The channel put path cannot express `progress` yet, so it carries the existing
value forward — otherwise saving an unrelated field would silently reset an
operator's choice.
Also fixes a latent conflation: reactions target the message that triggered
the turn, which is not the thread root for a reply inside a thread.
This was referenced Aug 2, 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.
Requested after testing #1162: when the agent takes a while, give the person in Slack some sign it's working.
Today a slow turn leaves the thread completely silent until the answer lands. From the thread that's indistinguishable from a dropped delivery — and the reasonable response, sending it again, starts a second turn in the same thread.
Configuration
Per Slack channel, as requested:
placeholder(default)reactionbothoffNothing appears for a turn that answers promptly — it waits 8s first, so the common case stays exactly as clean as it is now. The affordance is for a wait that has already become long enough to look like a failure.
Three things that make it honest rather than decorative
It stops claiming to work when it isn't. A turn parked on a tool approval will not resume until an operator acts in the TUI — and the person waiting in Slack cannot see that prompt. The placeholder changes to name the tool awaiting sign-off. The ingress publishes the turn's phase on a
watchchannel and the channel renders it; publishing is advisory, so nothing about a turn depends on anyone reading it.It resolves, including on failure. Whatever was posted is replaced by the outcome. Turn failures previously went only to the daemon log, leaving the asker with silence forever; now the thread says the turn ended without an answer.
A missing scope is cosmetic, never fatal.
reactions:writeis a scope an existing app install won't have. Construct logs the refusal and still delivers the answer.Two smaller things
progressyet, so it carries the existing value forward — otherwise saving an unrelated field (a rotated token, an allowlist edit) would silently reset the operator's choice back to default.Tests
a_slow_turn_announces_itself_then_says_what_it_is_blocked_ondrives the real affordance against a stub Slack Web API and asserts the whole lifecycle: reaction on222.22(the message) while the placeholder goes to thread111.11, thenchat.updaterewriting it to the approval text. Runs in 0.04s.Plus: silence for a fast turn,
offtouching nothing, progress-phase transitions and their de-duplication, TOML parsing incl. the pre-existing-definition default, and put-path preservation.cargo test --workspace: 51 test binaries, 0 failures.Not covered by tests: the Slack calls themselves are exercised against a stub, not a real workspace — worth a live sanity check in your thread before merging.
Notes
placeholder, so existing Slack channels get the affordance on upgrade. Setprogress = "off"to keep them silent.New spec
0178-a-channel-shows-that-a-turn-is-still-running.md;docs/services.mdgains a Slack channel section (it had none).Binary
Touches
crates/daemon→ the relevant binary isconstruct.