Skip to content

fix(teams): scope team tasks to the delegation origin, not the delivery channel - #1530

Open
yatul wants to merge 2 commits into
nextlevelbuilder:devfrom
yatul:fix/team-task-origin-channel
Open

fix(teams): scope team tasks to the delegation origin, not the delivery channel#1530
yatul wants to merge 2 commits into
nextlevelbuilder:devfrom
yatul:fix/team-task-origin-channel

Conversation

@yatul

@yatul yatul commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #1529. Sibling of #1527 / #1528 — same underlying assumption, no code overlap. The two are independent and can land in either order.

Problem

buildAgentLinkRunRequest is explicit that a delegated run is delivered on an internal channel while its real origin is kept aside:

// preserves the origin's authorization-bearing identity while keeping
// delegation on its internal delivery channel.
Channel:          "delegate",
WorkspaceChannel: req.Channel,
WorkspaceChatID:  req.ChatID,

The team tools never consulted that origin, so a task created by a lead reached through delegate was stamped with the delivery channel. Nothing is registered for "delegate", so internal/channels/dispatch.go dropped every notification about that task — completion, failure, blocker escalation, ask_user:

16:11:39  INFO teammate announce: batch processed  session=delegate:019f4566:brain:e7ee9913-...
16:11:39  WARN unknown channel for outbound message channel=delegate

The delegatee's first answer still arrived, because it travels back as the delegation result rather than through a channel. Everything the lead said afterwards was lost — one session produced 16 such drops, including an escalation the user needed:

Reviewer is blocked: tetris.html does not exist, the coder's generation failed. Restarted generation (running), re-pointed the review at it.

Change

Add OriginChannelFromCtx / OriginChatIDFromCtx beside the existing workspace-scope helpers, and use them where team scoping and notification routing are decided: task records, list/search scoping, dispatch fallbacks, event payloads, escalation tasks, ask_user, leader notifications. Both resolve to the identity when no delegation origin is present, so non-delegated flows are untouched.

Create and read had to move together — the create lock key and the list chat filter must agree, or a lead would stop seeing its own board.

Deliberately not changed: the channel used in authorization decisions — checkTeamAccess, requireLead, and the approve/reject lead bypass. Those ask "how did this call arrive", not "where should the answer go"; widening them is a separate question and a separate risk.

Test

Two subtests around executeCreate: one shapes the context the way buildAgentLinkRunRequest does (delivery channel "delegate", origin preserved separately) and asserts the task records the origin; the other asserts a non-delegated lead still records its own channel and chat. The first fails on dev:

task.Channel = "delegate", want "telegram" — notifications on the delivery channel are dropped
task.ChatID = "system", want "313683273"

go test ./internal/tools/ ./cmd/... ./internal/gateway/... ./internal/http/... ./internal/agent/... is green.

Verified in a live deployment

Built with this change (on top of #1528, so the tasks actually dispatch) and deployed to Kubernetes. Same delegated-lead flow:

16:39:49  v3.run.completed agent=brain
16:39:49  team_tasks.dispatch: sent task to agent agent_key=coder
16:41:03  v3.run.completed agent=coder
16:41:03  team_tasks.dispatch: sent task to agent agent_key=reviewer
16:41:04  run_id=teammate-announce-brain-1  session_key=agent:019f4566-...:http-313683273-5fa7002d

That last line is the point: the completion announce is now delivered into the caller's session instead of a delegation session that closed minutes earlier. Drops on channel=delegate went from 16 in the previous session to 2, and both remaining ones come from tasks created before the change — their bad channel is already persisted, so the fix is forward-looking only. Existing stuck tasks keep routing nowhere until they are recreated; worth a note in release notes, or a migration if you would rather backfill from TaskMetaOriginSession.

@yatul

yatul commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the merge gates from the #1529 triage — both named tests are in, and each fails when the fix is reverted.

TestDelegatedLeadCompletionNotifiesOrigin — completes a task raised in a delegated context and asserts the completion event is addressed to the caller's origin. Reverting team_event_helpers.go to ToolChannelFromCtx fails it with:

completion event addressed to delegate/system, want telegram/313683273 —
the delivery channel has no registered handler and the notification is dropped

TestCompletionNotificationStaysWithinItsOwnOrigin — the guard you asked for: two tasks from different origins on one board, complete one, assert no completion notification carries the other origin.

Together with the two tests already in the PR (create persists the origin; a non-delegated run is unchanged) that covers persistence, notification routing, isolation, and the identity case.

go test ./internal/tools/ ./cmd/... ./internal/gateway/... ./internal/http/... ./internal/agent/... is green.

yatul and others added 2 commits September 1, 2026 15:35
…ry channel

A delegated run is delivered on the internal "delegate" channel while its real
origin is preserved separately — buildAgentLinkRunRequest is explicit about it:

    // preserves the origin's authorization-bearing identity while keeping
    // delegation on its internal delivery channel.
    Channel:          "delegate",
    WorkspaceChannel: req.Channel,
    WorkspaceChatID:  req.ChatID,

The team tools did not consult that origin, so a task created by a lead reached
through delegate was stamped with the delivery channel. Nothing is registered
for it, so every notification about that task — completion, failure, blocker
escalation, ask_user — was dropped:

    unknown channel for outbound message channel=delegate

The delegatee's first answer still arrived, because it travels back as the
delegation result rather than through a channel; everything the lead said after
the delegation closed was lost. One session produced 16 such drops, including a
blocker escalation the user needed to see.

Add OriginChannelFromCtx / OriginChatIDFromCtx next to the existing workspace
scope propagation helpers, and use them where team scoping and notification
routing are decided: task records, list/search scoping, dispatch fallbacks,
event payloads, escalation tasks, ask_user and leader notifications. The
resolution is the identity when no delegation origin is present, so
non-delegated flows are unchanged.

Deliberately not touched: the channel used in authorization decisions
(checkTeamAccess, requireLead, approve/reject lead bypass). Those ask "how did
this call arrive", not "where should the answer go", and widening them is a
separate question.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tion

Triage on nextlevelbuilder#1529 named two gates this PR had not met: regression coverage for
delegated lead task completion, and a guard that unrelated origins cannot
receive the notification. The existing tests only covered what create persists.

TestDelegatedLeadCompletionNotifiesOrigin completes a task raised in a delegated
context and asserts the completion event is addressed to the caller's origin.
Reverting team_event_helpers.go to the delivery channel fails it with
"addressed to delegate/system".

TestCompletionNotificationStaysWithinItsOwnOrigin puts two tasks from different
origins on one board, completes one, and asserts no completion notification
carries the other origin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@clark-cant clark-cant left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix(teams): scope team tasks to the delegation origin, not the delivery channel

Verdict: Approve

Risk level: Low — focused, consistent change across 10 files with comprehensive tests.

Mandatory gates:

  • Duplicate / prior implementation: clear — no overlap with existing work. Sibling PR #1537 addresses a different concern (team workspace for delegated lead).
  • Project standards: clear — follows established context-key helper pattern (WorkspaceChannelFromCtx/WorkspaceChatIDFromCtx).
  • Strategic necessity: clear value — fixes 16 notification drops in a single session, including missed blocker escalations.

Findings: None. The change is clean:

  • New OriginChannelFromCtx/OriginChatIDFromCtx helpers correctly prefer workspace origin over delivery channel, with identity fallback for non-delegated runs.
  • All call sites consistently updated (create, list, search, followup, attach, dispatch, escalation, event helpers, validation).
  • Authorization paths deliberately untouched — correct security boundary awareness.
  • 4 tests cover: delegation origin persistence, non-delegated identity, completion notification routing, and cross-origin isolation.
  • PR body is excellent: problem statement, fix rationale, test strategy, live deployment verification with before/after evidence.

Posted by github-maintain at 2026-09-01T13:42:00Z

@yatul

yatul commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Force-pushed to strip build output that should never have been in this branch.

internal/webui/dist is not tracked upstream — zero files on dev and in every release; only internal/webui/*.go is. .gitignore covers ui/web/dist/, but not the directory the UI build copies into, so a git add -A swept 411 built assets into the fix commit.

The diff was 421 files for a ten-line behavioural change. That is on me, and it made this PR unreviewable at a glance — quite possibly why it has sat while its siblings (#1528, #1533) were triaged and merged.

Now:

internal/tools/context_keys.go                   21 +
internal/tools/team_event_helpers.go              4 +-
internal/tools/team_task_origin_channel_test.go 204 +
internal/tools/team_tasks_create.go               6 +-
internal/tools/team_tasks_followup.go             4 +-
internal/tools/team_tasks_mutations.go            2 +-
internal/tools/team_tasks_read.go                 8 +-
internal/tools/team_tool_dispatch.go              4 +-
internal/tools/team_tool_helpers.go               4 +-
internal/tools/team_tool_validation.go            4 +-
10 files changed, 243 insertions(+), 18 deletions(-)

Both commits keep their original messages, authorship and dates; only the file set of the first changed. Verified: diffing the old head against the new one while excluding internal/webui/dist produces no output, so nothing else moved.

Nothing else about the PR changed — the fix and TestDelegatedLeadCompletionNotifiesOrigin / TestCompletionNotificationStaysWithinItsOwnOrigin are as reviewed in the triage on this issue. go test ./cmd/... ./internal/tools/ ./internal/agent/... ./internal/gateway/... ./internal/http/... is green on 4b9337f0.

Unrelated, but perhaps worth a line in .gitignore upstream: internal/webui/dist/ is a build artefact and easy to commit by accident this way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Team task results never reach the caller when the lead is reached via delegate: notifications routed to the internal "delegate" channel are dropped

2 participants