Skip to content

Requirements: emoji reactions acknowledging that the-loop is processing an entity ​

Phase 1 of 3 (requirements → design → tasks). Following the Kiro spec approach (https://kiro.dev/docs/specs/). Tier 3 (human-approves-pr): spec + code are approved together at the PR.

Introduction ​

Today there is no visible signal that the-loop has picked up a comment, issue or PR — the first feedback a human gets is the harness's reply comment after the work is done (issue #84). The owner asked for lightweight emoji acknowledgements on the triggering GitHub entity: one when the-loop starts working an event (👀), one when it has completed it (✅), and one on errors (⁉️) — configurable in the CLI config yaml, and a no-op on work-item platforms that have no emoji reactions.

One platform constraint shapes the acceptance criteria: GitHub's reaction palette is fixed (+1 -1 laugh confused heart hooray rocket eyes). ✅ and ⁉️ do not exist as GitHub reactions, so the defaults map each state to the closest supported emoji (👀 eyes, 🎉 hooray, 😕 confused) and the mapping is operator-configurable.

Requirements ​

Requirement 1 — lifecycle reactions on the triggering entity ​

User story: As an operator (or any collaborator watching the repo), I want the-loop to react on the comment/issue/PR that triggered it, so that I can see at a glance that it has started working, finished, or hit an error — before any reply comment exists.

Acceptance criteria (EARS) ​

  1. WHEN the dispatcher begins processing a routed event (the event is dequeued for delivery to, or spawn of, a harness session) THEN the system SHALL add the configured started reaction (default eyes 👀) to the triggering entity.
  2. WHEN the dispatch of that event succeeds (delivered/resumed/spawned) THEN the system SHALL add the configured completed reaction (default hooray 🎉) to the same entity.
  3. WHEN the dispatch fails terminally for that attempt (delivery/spawn failure, missing adapter, vanished session, or a dispatch-worker crash) THEN the system SHALL add the configured error reaction (default confused 😕).
  4. WHEN the event carries a comment (issue_comment, pull_request_review_comment — webhook or poll path) THEN the reaction SHALL land on that comment; WHEN it carries no comment but an issue/PR (presence/labeled/review events) THEN the reaction SHALL land on the issue/PR itself; CI events with neither SHALL get no reaction.
  5. Reactions SHALL be best-effort: a reaction failure SHALL NOT fail, retry, delay or drop the dispatch itself, and SHALL NOT change the existing retry/dedup semantics. (Corollary: a retried event may legitimately end up with both the error and, later, the completed reaction — reactions are an append-only trail, and GitHub ignores a duplicate reaction by the same user.)
  6. Events the-loop does not process (unauthorized actor, self-authored reply, disabled event type, duplicate delivery, unmatched with spawnOnUnmatched: never, PR-close lifecycle handling) SHALL get no reaction.

Requirement 2 — configurable in the CLI config yaml ​

User story: As an operator, I want to choose the emoji per state (or turn the feature off) in cli-config.yaml, so the acknowledgement style is mine.

Acceptance criteria (EARS) ​

  1. The feature SHALL be configured under webhooks.ghWebhook.routing.reactions in the CLI config (shared by the webhook receiver AND the poller, like all other dispatch behaviour): enabled, started, completed, error, ghBinary.
  2. Each state SHALL accept one of GitHub's supported reaction names (+1 -1 laugh confused heart hooray rocket eyes) or "" (skip that state), enforced by cli-config.schema.json.
  3. enabled SHALL default to true (owner decision, PR #85 review comment): out-of-box visibility is the feature's point, the write is reaction-only (no text), and it degrades to a silent no-op when gh is absent. enabled: false opts out. (Originally drafted default-off as the conservative posture for the daemon's first GitHub write; flipped on the owner's explicit call — the paper trail is the PR comment.)
  4. WHEN the CLI config file is edited while the receiver/poller runs THEN the reactions config SHALL hot-reload with the rest of the soft routing policy.

Requirement 3 — no-op where reactions are not available ​

User story: As an operator on a platform without emoji reactions, I want the feature to disappear silently rather than error.

Acceptance criteria (EARS) ​

  1. IF the routed event's work item is not a GitHub one (a future Jira/other poll provider) THEN reacting SHALL be a silent no-op.
  2. IF the event payload carries no reactable target (no repository, no comment, no issue/PR number — e.g. workflow_run) THEN reacting SHALL be a no-op.
  3. IF the gh CLI is not on PATH THEN reacting SHALL be a no-op with a single warning (not one per event), and the receiver/poller SHALL still start and dispatch normally — the CLI's zero-required-dependency guarantee is preserved (reactions never join check_dependencies).

Requirement 4 — observability ​

User story: As an operator debugging the trigger path, I want reaction outcomes in the event log, so the-loop events shows the full story.

Acceptance criteria (EARS) ​

  1. WHEN a reaction is added THEN the system SHALL emit a reaction.added event-log record (work_item, state, content, target).
  2. WHEN adding a reaction fails (gh error/timeout) THEN the system SHALL emit reaction.failed (warning level) and carry on.

Security considerations (threat-model-lite) ​

  • New write surface: this is the CLI daemon's first write to GitHub (everything before was read-only gh listing or inbound webhooks). The write is confined to the reactions API — it posts no text, so it adds no prompt-injection surface and cannot leak workspace content. It runs with the operator's own gh auth, exactly like the poller reads (decision: no token of its own).
  • Untrusted payload → API coordinates: the reaction target (owner/repo, comment id / node id, issue number) is derived from event payloads. Those payloads are already HMAC-verified (webhook) or synthesized from gh's own output (poll), and only authorized-actor events reach dispatch — but the values are still validated defensively (numeric ids parsed as integers, node ids / owner / repo matched against strict character classes) before being placed into a gh api argv (list-form exec, no shell).
  • Config injection: the reaction content is operator-controlled config, validated against the fixed palette (schema + runtime check); an unknown value is skipped with a warning, never passed through.
  • Fail closed on doubt: any doubt (no target, non-GitHub provider, missing gh, invalid content) resolves to a no-op, never to a blocked or altered dispatch. Default-on is an explicit owner decision (PR #85): the write surface is reaction-only, posts no text, and no-ops without gh.
  • Abuse case — reaction spam: a retried event re-adds the same reaction; GitHub treats a duplicate reaction by the same user as idempotent, so retries cannot accumulate spam.

Released under the MIT License.