Skip to content

Auto-trigger agent when task moves to in progress#182

Merged
jlia0 merged 3 commits intomainfrom
jlia0/fix-task-auto-trigger
Mar 11, 2026
Merged

Auto-trigger agent when task moves to in progress#182
jlia0 merged 3 commits intomainfrom
jlia0/fix-task-auto-trigger

Conversation

@jlia0
Copy link
Copy Markdown
Collaborator

@jlia0 jlia0 commented Mar 9, 2026

Summary

When a task is dragged to the "In Progress" column on the kanban board, the assigned agent is now automatically triggered to start working on it. Previously, users had to click the "Send" button after moving the task, adding an extra step.

Changes

  • Detect when tasks are newly moved to "in_progress" column
  • Automatically send @agent message for each task with an assignee
  • Add yarn.lock to .gitignore
  • Pin TypeScript version to 5.9.3

Type of Change

  • Bug fix

Testing

  • Built the Next.js project successfully with no errors
  • Drag-and-drop functionality preserved
  • Task status updates work correctly

Checklist

  • I have tested these changes locally
  • My changes don't introduce new warnings or errors

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR adds auto-triggering of assigned agents when a task card is dragged to the "In Progress" column, eliminating the manual "Send" button step. It also introduces a new tasks agent skill (tasks.sh) that lets agents list, update, and create kanban tasks, and injects TINYCLAW_AGENT_ID into each agent's environment so they can identify themselves.

Key observations:

  • Critical bug in src/lib/invoke.ts: The TINYCLAW_AGENT_ID env var injected at initialisation is silently dropped for any agent using a custom provider (claude or codex harness), because the envOverrides object is fully replaced instead of merged. This breaks tasks.sh --mine and status self-updates for all custom-provider agents.
  • Double-trigger path: The pre-existing "Send" button (handleAssign) independently calls sendMessage + updateTask to move a task to in_progress. Now that drag-to-in-progress also auto-sends the message, both code paths can dispatch the same agent message for the same task if the user combines them. The button is only shown for backlog tasks, which limits the risk, but the overlap is undocumented and the button may now be redundant.
  • Unvalidated --status in tasks.sh: The cmd_list function interpolates --status into a jq filter without the allowlist guard that cmd_update already applies.

Confidence Score: 2/5

  • Not safe to merge — the TINYCLAW_AGENT_ID loss for custom providers silently breaks the core feature for a significant subset of agents
  • The auto-trigger UI logic in page.tsx is correct for the standard drag-and-drop case, but the companion server-side change in invoke.ts has a clear regression: custom-provider agents will never receive their agent ID in the environment, making the new tasks skill non-functional for them. This affects tasks.sh --mine lookups and self-directed status updates, which are central to the feature's end-to-end flow.
  • src/lib/invoke.ts requires immediate attention for the envOverrides reassignment bug; tinyoffice/src/app/tasks/page.tsx warrants a second look regarding the interaction between the drag auto-trigger and the existing "Send" button

Important Files Changed

Filename Overview
tinyoffice/src/app/tasks/page.tsx Adds auto-trigger logic to detect tasks dragged into in_progress and dispatch an @agent message; has a potential double-trigger path from the pre-existing "Send" button which also sends a message and updates status to in_progress
src/lib/invoke.ts Injects TINYCLAW_AGENT_ID into agent env; critical bug: the variable is dropped for custom-provider paths (claude/codex harnesses) because envOverrides is fully reassigned rather than merged
.agents/skills/tasks/scripts/tasks.sh New bash skill for managing kanban tasks via REST API; the --status filter in cmd_list is not validated against the allowlist, unlike the cmd_update path
.agents/skills/tasks/SKILL.md New skill documentation describing how agents should use the tasks.sh script; clear and accurate
tinyoffice/.gitignore Adds yarn.lock to .gitignore; straightforward housekeeping change
tinyoffice/package.json Pins TypeScript from ^5 to 5.9.3 to stabilise the build; no functional impact

Sequence Diagram

sequenceDiagram
    actor User
    participant KanbanUI as Kanban Board (page.tsx)
    participant API as TinyClaw API
    participant MsgBus as Message Bus (sendMessage)
    participant Agent as Assigned Agent (invoke.ts)

    User->>KanbanUI: Drag task → In Progress column
    KanbanUI->>KanbanUI: Compute newlyInProgress<br/>(tasks not in prevInProgress with assignee)
    KanbanUI->>API: reorderTasks(colMap)
    API-->>KanbanUI: OK
    KanbanUI->>MsgBus: sendMessage(@agent title [task:ID])
    MsgBus-->>KanbanUI: OK (errors silently swallowed)
    KanbanUI->>KanbanUI: refresh()

    MsgBus->>Agent: Deliver @agent message
    Agent->>Agent: invokeAgent(...)<br/>env: TINYCLAW_AGENT_ID=agentId<br/>(⚠️ lost for custom providers)
    Agent->>Agent: Work on task
    Agent->>API: tasks.sh update TASK_ID --status done
    API-->>Agent: OK

    Note over User, KanbanUI: Alternative: User clicks "Send" button (backlog tasks only)
    User->>KanbanUI: Click Send button
    KanbanUI->>MsgBus: sendMessage(@agent title [task:ID])
    KanbanUI->>API: updateTask(id, {status: in_progress})
    KanbanUI->>KanbanUI: refresh()
Loading

Comments Outside Diff (2)

  1. tinyoffice/src/app/tasks/page.tsx, line 134-148 (link)

    Double-trigger risk: "Send" button + drag-to-in-progress

    handleAssign (invoked by the "Send" button) directly calls sendMessage and then updateTask(..., { status: "in_progress" }). The auto-trigger in handleValueChange fires when an item is dragged into in_progress. If a user clicks "Send" and the task moves to in_progress on the backend, and then the user (or an optimistic UI update) drags the card within the column or back and forth, the agent could receive duplicate dispatch messages for the same task.

    More concretely: handleAssign already sends the [task:ID]-tagged message and transitions the task to in_progress. The "Send" button should now be considered redundant with the drag-triggered path. Consider either:

    • Removing the "Send" button entirely now that drag auto-triggers the agent, or
    • Guarding handleAssign to skip sendMessage if the task is already being handled by the drag path.

    At a minimum, the two paths should be documented so future contributors understand why both exist.

  2. src/lib/invoke.ts, line 101-112 (link)

    TINYCLAW_AGENT_ID lost for custom providers

    envOverrides is initialised on line 86 with TINYCLAW_AGENT_ID: agentId, but for every custom-provider path (both claude and codex harnesses), the object is completely reassigned rather than spread-merged. As a result, TINYCLAW_AGENT_ID is absent from the spawned process's environment for any agent running on a custom provider.

    This directly breaks the new tasks skill: tasks.sh list --mine will abort with "--mine requires TINYCLAW_AGENT_ID to be set", and the agent cannot self-identify when marking tasks done via tasks.sh update TASK_ID --status done.

    The fix is to spread the existing envOverrides into each reassignment instead of replacing it outright, e.g.:

    envOverrides = {
        ...envOverrides,       // preserves TINYCLAW_AGENT_ID
        // provider-specific keys below
    };
    

    This pattern should be applied to both the claude harness block (lines 102–106) and the codex harness block (lines 107–110).

Last reviewed commit: 3ca3497

Comment on lines +78 to +80
const newlyInProgress = (newColumns.in_progress ?? []).filter(
(t) => !prevInProgress.has(t.id) && t.assignee
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agent re-triggered on any in-progress transition, not just from backlog

The current check fires whenever a task is newly present in in_progress — regardless of which column it came from. This means dragging a task from reviewin_progress or donein_progress (e.g., reopening a finished task) will also silently dispatch an @agent message, potentially spamming the agent or restarting already-completed work.

To restrict triggering to the intended case — moving a task from backlog to in_progress — you should also verify the task's previous column:

// Build a quick lookup of previous column for each task
const prevColumnOf = new Map<string, string>();
for (const [status, items] of Object.entries(columns)) {
  for (const t of (items as Task[])) {
    prevColumnOf.set(t.id, status as string);
  }
}

const prevInProgress = new Set((columns.in_progress ?? []).map((t) => t.id));
const newlyInProgress = (newColumns.in_progress ?? []).filter(
  (t) => !prevInProgress.has(t.id) && t.assignee && prevColumnOf.get(t.id) === "backlog"
);

Without this guard, reopening a reviewed or completed task re-triggers the assigned agent unexpectedly.

Comment thread tinyoffice/src/app/tasks/page.tsx Outdated
@jlia0 jlia0 requested a review from mczabca-boop March 9, 2026 18:40
@jlia0
Copy link
Copy Markdown
Collaborator Author

jlia0 commented Mar 9, 2026

@greptileai review new changes

Comment on lines +77 to +79
if [[ -n "$filter_status" ]]; then
jq_filter="${jq_filter} | map(select(.status == \"${filter_status}\"))"
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unvalidated --status value interpolated into jq filter

filter_status is passed directly from the command-line argument into a jq expression with no prior validation. The update sub-command validates its --status input against an allowlist, but list does not. A value containing " or jq operators (e.g., in_progress" | @base64) could break the generated filter or produce unexpected output.

Add the same allowlist guard used in cmd_update:

Suggested change
if [[ -n "$filter_status" ]]; then
jq_filter="${jq_filter} | map(select(.status == \"${filter_status}\"))"
fi
if [[ -n "$filter_status" ]]; then
case "$filter_status" in
backlog|in_progress|review|done) ;;
*) die "Invalid status: $filter_status. Must be one of: backlog, in_progress, review, done" ;;
esac
jq_filter="${jq_filter} | map(select(.status == \"${filter_status}\"))"
fi

Copy link
Copy Markdown
Collaborator

@mczabca-boop mczabca-boop left a comment

Choose a reason for hiding this comment

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

Overall direction makes sense, but I found two functional issues and one rollout gap that should be addressed before merge.

  1. Existing agents do not automatically receive the new tasks skill, so the feature does not work for already-provisioned workspaces. The new task flow depends on agents understanding [task:ID], but older agent workspaces are only updated on first provisioning, not when new default skills are added. In local testing, an existing Claude-based agent ignored task semantics until tasks was manually copied into both .agents/skills/tasks and .claude/skills/tasks. That means this PR works for newly created agents, but not reliably for existing ones. Relevant code: src/lib/agent.ts, src/server/routes/agents.ts.

  2. Custom-provider agents lose TINYCLAW_AGENT_ID, which breaks the new task skill behavior. invokeAgent() initializes envOverrides with TINYCLAW_AGENT_ID, but in the custom: provider branch it replaces the object instead of merging into it. That means tasks.sh list --mine and the default self-assignment path stop working for custom-provider agents. Relevant code: src/lib/invoke.ts.

  3. Dragging a task into in_progress updates task state before notification is guaranteed, so tasks can appear active even if no agent was actually notified. In the new drag path, reorderTasks() is committed first and sendMessage() failures are swallowed. By contrast, the existing send-button flow sends first and only then updates status. This creates an inconsistent and potentially misleading state transition. Relevant code: tinyoffice/src/app/tasks/page.tsx.

I also manually verified that:

  • The web UI changes do work: dragging to in_progress does auto-enqueue a task message.
  • The tasks.sh script itself works correctly against the local API.
  • The main gap is rollout/discovery of the new skill for existing agents, not the script implementation itself.

jlia0 and others added 3 commits March 11, 2026 05:21
When a task is dragged to the in progress column on the kanban board,
automatically send a message to the assigned agent to start working on it,
matching the behavior of the manual "Send" button. Also add yarn.lock to
.gitignore and pin TypeScript version.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Agents can now list, update, and create tasks via a new skill. Messages
dispatched from the task board include a [task:ID] tag so agents know
which task to mark done. TINYCLAW_AGENT_ID env var is passed to agent
processes to support --mine filtering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Preserve TINYCLAW_AGENT_ID when building env overrides for custom
providers by using property assignment instead of object replacement.
Reorder drag-to-in-progress flow to send agent message before updating
task status, matching the send-button pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

2 participants