Skip to content

feat: support discussion_comment event #101

Closed

Description

@wadackel

Summary

Extend command-action to support the discussion_comment event in addition to issue_comment, so IssueOps commands can be triggered from comments on GitHub Discussions.

To stay aligned with the I/F finalized in #100, this change extends the same output vocabulary (number, context) and the allowed_contexts input vocabulary to cover discussions.

Note

This issue depends on #100. Implement it after the number / context output redesign in #100 has landed, and base the new behavior on that redesigned surface.

Motivation

  • command-action currently only handles issue_comment and emits a warning for any other eventName (src/main.ts:11-14). Workflows triggered by on: discussion_comment are dropped without doing any parsing.
  • GitHub Discussions are a first-class collaboration surface in many repositories, and IssueOps-style commands (.deploy, .preview, .label, etc.) are equally useful in discussion threads (Q&A triage, RFC voting, release-note drafting, etc.).
  • The I/F redesign in feat(output): add number and context outputs, soft-deprecate issue_number #100 already moves the output vocabulary toward an event-agnostic shape (number / context). Extending it once more to cover discussions is cheap if it ships right after feat(output): add number and context outputs, soft-deprecate issue_number #100, and avoids a third migration later.

Proposed changes

1. Accept discussion_comment in isValidContext

  • src/main.ts:11-14 currently rejects any event other than issue_comment. Replace the equality check with an allow-list of {"issue_comment", "discussion_comment"}.
  • Keep the warning message for unsupported events but list both accepted events.

2. Extend allowed_contexts vocabulary

  • Add discussion to the allowed values of allowed_contexts (currently issue / pull_request).
  • Update the validContexts set in src/main.ts:8 to {"issue", "pull_request", "discussion"}.
  • Update the default of allowed_contexts in action.yaml:14 from issue,pull_request to issue,pull_request,discussion. This is a strict addition: workflows that do not listen to discussion_comment are unaffected, and workflows that do gain support without manual opt-in.

3. Determine the triggering context

Introduce a single helper that, given the current @actions/github context, returns the discriminated triple { kind: "issue" | "pull_request" | "discussion", number, commentId, actor, body }. Use it from both isValidContext (for the allowed_contexts filter) and the output emission site, so the kind is computed in exactly one place.

Determination rules:

Event Condition kind
issue_comment payload.issue.pull_request != null "pull_request"
issue_comment otherwise "issue"
discussion_comment always "discussion"

This replaces the existing isPr boolean in src/main.ts:26 (which #100 already plans to lift out — coordinate so both changes converge on the same helper rather than introducing two competing abstractions).

4. Outputs on the discussion context

After isValidContext passes, emit the following for a discussion kind:

Output Value Notes
number payload.discussion.number Same semantics as #100's number for issue/PR.
context "discussion" Extends #100's `"issue"
comment_id payload.comment.id Same payload shape as issue_comment.
actor payload.comment.user.login Same payload shape as issue_comment.
command / params / continue Per existing logic Comment body parsing is event-agnostic.
issue_number not emitted The legacy name does not apply to discussions; downstream consumers should migrate to number (per #100's deprecation). For issue_comment events, issue_number continues to be emitted unchanged.

5. Parser reuse

  • parse(body) in src/parse.ts already operates on a plain comment body string. No parser changes required.
  • Confirm via a unit test on main.ts that a discussion comment body flows through the same parsing path.

6. README updates

  • Update §Inputs / §Outputs tables (regenerated by pnpm generate):
    • allowed_contexts default changes to issue,pull_request,discussion.
    • Description adds discussion as an allowed value.
    • context output description lists the three possible values.
  • Add a §Getting Started example for discussions:
    • on: discussion_comment: types: [created]
    • permissions: discussions: write (required for adding reactions to discussion comments via github.graphql — addReaction mutation; same level of detail as the existing Tips section for issue/PR reactions).
    • Brief callout linking to allowed_contexts if the workflow wants to restrict to discussions only.

7. Tests

src/main.ts currently has no unit tests. Add src/main.test.ts (vitest) covering the matrix below. Use vi.mock("@actions/github", ...) to inject eventName and payload per case, and spy on core.setOutput / core.warning to assert emission.

Case Event Payload shape Expected
Issue comment issue_comment payload.issue.pull_request == null context="issue", number=payload.issue.number, issue_number emitted
PR comment issue_comment payload.issue.pull_request != null context="pull_request", number=payload.issue.number, issue_number emitted
Discussion comment discussion_comment payload.discussion.number set context="discussion", number=payload.discussion.number, issue_number not emitted
Unsupported event e.g., push n/a warning emitted, continue="false", no other outputs
allowed_contexts filter mismatch discussion_comment with allowed_contexts="issue" discussion payload info emitted, continue="false"

Spec details

action.yaml

inputs:
  allowed_contexts:
    description: 'The comment contexts that trigger the IssueOps command, specified as a comma-separated list. Allowed values: "issue", "pull_request", "discussion".'
    default: 'issue,pull_request,discussion'

outputs:
  context:
    description: 'The context that triggered this action. One of "issue", "pull_request", or "discussion".'
  # (other outputs unchanged from #100)

src/main.ts (sketch)

const validContexts = new Set(['issue', 'pull_request', 'discussion'] as const);
type CommentKind = 'issue' | 'pull_request' | 'discussion';

const resolveCommentContext = (): {
  kind: CommentKind;
  number: number;
  commentId: number;
  actor: string;
  body: string;
} | null => {
  if (context.eventName === 'issue_comment') {
    const isPr = context.payload.issue?.pull_request != null;
    return {
      kind: isPr ? 'pull_request' : 'issue',
      number: context.payload.issue!.number!,
      commentId: context.payload.comment!.id,
      actor: context.payload.comment!.user.login,
      body: (context.payload.comment?.body ?? '') as string,
    };
  }
  if (context.eventName === 'discussion_comment') {
    return {
      kind: 'discussion',
      number: context.payload.discussion!.number,
      commentId: context.payload.comment!.id,
      actor: context.payload.comment!.user.login,
      body: (context.payload.comment?.body ?? '') as string,
    };
  }
  return null;
};

Output emission inside run then becomes:

core.setOutput('number', ctx.number);
core.setOutput('context', ctx.kind);
core.setOutput('comment_id', ctx.commentId);
core.setOutput('actor', ctx.actor);
if (ctx.kind !== 'discussion') {
  core.setOutput('issue_number', ctx.number);
}

Acceptance criteria

  • action.yaml declares allowed_contexts default issue,pull_request,discussion and lists discussion in the description.
  • action.yaml's context output description lists "issue", "pull_request", "discussion".
  • src/main.ts accepts eventName of either issue_comment or discussion_comment and rejects others with a warning.
  • A single helper computes { kind, number, commentId, actor, body } and is used by both the allowed_contexts filter and the output emission.
  • For discussion_comment: number === payload.discussion.number, context === "discussion", issue_number is not emitted.
  • For issue_comment (issue or PR): behavior matches feat(output): add number and context outputs, soft-deprecate issue_number #100 (number === payload.issue.number, issue_number still emitted).
  • When allowed_contexts does not include discussion, a discussion_comment event sets continue="false" and emits an info message, without setting number / context / etc.
  • src/main.test.ts covers the five cases in the test matrix above.
  • README §Inputs / §Outputs tables are regenerated via pnpm generate and reflect the new default and discussion value.
  • README has a discussion_comment example workflow including permissions: discussions: write.
  • pnpm build produces an updated dist/, committed in the same PR.
  • pnpm typecheck, pnpm lint, pnpm test, pnpm generate (no drift) all pass in CI.

Out of scope

  • Filtering by Discussion category (Q&A, General, Announcements, etc.). If demand surfaces, design a separate input (e.g. allowed_discussion_categories) in a follow-up issue.
  • Answering / marking discussion comments as the chosen answer. Belongs to user-side workflow logic via the GraphQL API, not this action.
  • Reaction support. Already handled by the README Tips section pattern and out of scope for this action.
  • Removal of issue_number. Tracked separately under feat(output): add number and context outputs, soft-deprecate issue_number #100's "next major release" plan.
  • Locked / archived discussion handling. GitHub already prevents new comments in those states, so no special handling needed here.

Migration

For existing v1 consumers, no migration is required:

on:
  issue_comment:
    types: [created]
  discussion_comment:
    types: [created]

permissions:
  contents: read
  issues: write
  pull-requests: write
  discussions: write

jobs:
  demo:
    runs-on: ubuntu-latest
    steps:
      - id: command
        uses: knowledge-work/command-action@v1
        with:
          command: 'greet'

      - if: ${{ steps.command.outputs.continue == 'true' }}
        run: |
          echo "context=${{ steps.command.outputs.context }}"
          echo "number=${{ steps.command.outputs.number }}"
          echo "name=${{ fromJSON(steps.command.outputs.params).name }}"

Assumptions

  • The discussion_comment payload exposes discussion.number, comment.id, comment.body, and comment.user.login in the same shape as documented in GitHub Webhooks docs. Verify against @actions/github's WebhookPayload typings during implementation; if the field shape differs, adjust the helper accordingly.
  • Expanding the allowed_contexts default to include discussion is treated as a non-breaking addition because the action still requires the workflow's on: trigger to fire discussion_comment for the new path to activate.
  • Naming the new context value "discussion" (singular, matching allowed_contexts) is consistent with the existing "issue" / "pull_request" vocabulary in feat(output): add number and context outputs, soft-deprecate issue_number #100.

References

  • feat(output): add number and context outputs, soft-deprecate issue_number #100 — output redesign that introduces number and context. This issue extends that vocabulary.
  • src/main.ts:8 — validContexts set.
  • src/main.ts:11-14 — current eventName rejection.
  • src/main.ts:26 — current isPr computation.
  • src/main.ts:58-60 — current output emission site.
  • action.yaml:14 — current allowed_contexts default.
  • action.yaml:25-28 — current outputs declaration.
  • GitHub Webhooks: discussion_comment.

Activity

  1. github-actions commented on May 16, 2026

    @github-actions

    🎉 This issue has been resolved in version 2.2.0 🎉

    The release is available on GitHub release

    Your semantic-release bot 📦🚀

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions