Skip to content

feat: group-by-day for create-issue to prevent same-day duplicate reports#22725

Merged
pelikhan merged 5 commits intomainfrom
copilot/deep-report-make-contribution-check-idempotent
Mar 24, 2026
Merged

feat: group-by-day for create-issue to prevent same-day duplicate reports#22725
pelikhan merged 5 commits intomainfrom
copilot/deep-report-make-contribution-check-idempotent

Conversation

Copy link
Contributor

Copilot AI commented Mar 24, 2026

Scheduled workflows running multiple times per day (e.g. contribution-check every 4 hours) produce duplicate same-day report issues — the second run creates a new issue and closes the first, leaving two same-date issues in history.

Changes

New group-by-day option for create-issue

Before creating an issue, searches for an existing open issue with the same workflow-id marker (or close-older-key if configured) created today (UTC). If found, posts the new content as a comment on the existing issue instead of creating a new one. Falls back to normal creation if the pre-check fails.

safe-outputs:
  create-issue:
    title-prefix: "[Contribution Check Report]"
    labels: [contribution-report]
    close-older-issues: true
    group-by-day: true   # ← post as comment on same-day issue if one exists
    expires: 1d

Infrastructure

  • create_issue.cjs — group-by-day logic using searchOlderIssues + addIssueComment; warns at init if neither close-older-key nor GH_AW_WORKFLOW_ID is set
  • close_older_issues.cjs — exposes created_at in searchOlderIssues return value (needed for date comparison)
  • Go compiler (create_issue.go, compiler_safe_outputs_config.go) — new GroupByDay *string field wired through to handler config
  • JSON schemagroup-by-day added to create-issue schema properties

Workflow

  • contribution-check.mdgroup-by-day: true enabled; lock file recompiled

Tests & Docs

  • 6 new JS tests covering: comment posted on same-day issue, create on different day, no existing issues, pre-check error fallback, disabled-by-default, max-count not consumed when grouped
  • safe-outputs.md and frontmatter-full.md updated with new option documentation

📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.

…orts

Add `idempotent: true` option to the `create-issue` safe-output handler.
When enabled, the handler searches for an existing open issue created today
(UTC) with the same workflow-id or close-older-key marker before creating
a new one. If found, creation is silently skipped and the max-count slot
is not consumed.

This prevents same-day duplicate issues from occurring when a scheduled
workflow (like contribution-check) runs multiple times per day.

Also enable `idempotent: true` in contribution-check.md workflow to fix
the duplicate report issue described in the issue.

Changes:
- pkg/workflow/create_issue.go: add Idempotent field to CreateIssuesConfig
- pkg/workflow/compiler_safe_outputs_config.go: wire idempotent to handler config
- pkg/parser/schemas/main_workflow_schema.json: add idempotent to schema
- actions/setup/js/create_issue.cjs: implement dedupe-before-create logic
- actions/setup/js/close_older_issues.cjs: expose created_at in search results
- actions/setup/js/create_issue.test.cjs: add 6 tests for idempotent mode
- .github/workflows/contribution-check.md: enable idempotent: true
- docs: document new idempotent option in safe-outputs and frontmatter-full

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/081eb637-d376-4a2e-8ac0-e58c24da8a84
Copilot AI changed the title [WIP] Implement idempotency in Contribution Check reports feat: idempotent create-issue to prevent same-day duplicate reports Mar 24, 2026
Copilot AI requested a review from pelikhan March 24, 2026 17:07
@pelikhan
Copy link
Contributor

@copilot rename the new feature group by day. If a previous issue exists, post the new content as a comment on the existing issue instead of creating a new issue.

@github-actions
Copy link
Contributor

Hey @Copilot 👋 — great work on the idempotent create-issue feature! Preventing same-day duplicate contribution-check reports is a real quality-of-life improvement for the workflow history.

This PR is well-structured, focused, and thoroughly tested — the 6 new JS test cases in create_issue.test.cjs cover all the important scenarios (skip on same-day, create on different day, no existing issues, pre-check error fallback, disabled-by-default, max-count not consumed). The cross-layer implementation (JS handler, Go compiler, JSON schema, docs, and workflow recompile) looks cohesive and complete.

The PR looks ready for maintainer review. 🎉

Generated by Contribution Check ·

Per feedback, rename the `idempotent` feature to `group-by-day` and change
the behavior: instead of skipping issue creation when a same-day issue is
found, post the new content as a comment on the existing issue so all
same-day runs are grouped in one place.

- Rename config key: idempotent -> group-by-day (Go, JS, schema, docs)
- Change behavior: post body as comment on existing today-issue
- Return { grouped: true, existingIssueNumber, commentUrl } on grouping
- Update contribution-check.md to use group-by-day: true
- Update tests and documentation

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/45df665e-9d93-459c-8451-8aec175aba33
Copy link
Contributor Author

Copilot AI commented Mar 24, 2026

@copilot rename the new feature group by day. If a previous issue exists, post the new content as a comment on the existing issue instead of creating a new issue.

Done in 4911a61. Renamed idempotentgroup-by-day and changed the behavior: when a same-day open issue is found, the new content is posted as a comment on that issue instead of being skipped. The contribution-check.md workflow is updated accordingly.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha -bool -buildtags 9083269/b331/vet.cfg -errorsas -ifaceassert -nilfunc /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -uns�� -unreachable=false /tmp/go-build2909083269/b202/vet.cfg /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet get --local r: $owner, name:--show-toplevel /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha 8fl6aaGAo .cfg 64/pkg/tool/linux_amd64/vet l owner=github -f 64/pkg/tool/linux_amd64/vet api om/stretchr/testify@v1.11.1/require/doc.go om/stretchr/testify@v1.11.1/require/forward_requirements.go ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet l owner=github DiscussionsEnabl--show-toplevel ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha te &#39;**/*.cjs&#39; &#39;**/*.ts&#39; &#39;**/*.jsremote.origin.url (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha -bool -buildtags /usr/bin/git -errorsas -ifaceassert -nilfunc git rev-�� --show-toplevel -tests /usr/bin/git --verify copilot/deep-reprev-parse repository(owne--show-toplevel git (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel x_amd64/vet /usr/bin/git k/gh-aw/gh-aw/.ggit **/*.cjs x_amd64/vet git rev-�� --show-toplevel x_amd64/vet /usr/bin/git k/gh-aw/gh-aw/.ggit (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha xterm-color (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha /home/REDACTED/work/gh-aw/gh-aw/.github/workflows rev-parse 9083269/b159/vet.cfg ./../pkg/workflogit r bin/git /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/compile -o /tmp/go-build2909083269/b362/_pkg_.a -trimpath 9083269/b276/vet.cfg -p github.com/stretrev-parse -lang=go1.17 /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha --show-toplevel bash /usr/bin/git info: () =&gt; {},git (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha se 9083269/b115/vet.cfg /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet remote.origin.urgit (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha xterm-color (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha graphql -f 9083269/b154/vet.cfg -f owner=github -f /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -uns�� -unreachable=false /tmp/go-build2909083269/b027/vet.cfg 9083269/b287/vet.cfg (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha /home/REDACTED/work/gh-aw/gh-aw/.github/workflows config /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet remote.origin.urgit ame $name) { has--show-toplevel /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -uns�� -unreachable=false /tmp/go-build2909083269/b026/vet.cfg 9083269/b291/vet.cfg (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha /home/REDACTED/work/gh-aw/gh-aw/.github/workflows config 9083269/b158/vet.cfg remote.origin.urgit tions/setup/js/crev-parse git /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -uns�� -unreachable=false /tmp/go-build2909083269/b025/vet.cfg 9083269/b147/vet.cfg (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha g_.a (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha (http block)
  • https://api.github.com/repos/github/gh-aw
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw --jq .visibility th .prettierignoremote.origin.url (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.0.0 --jq .object.sha k/gh-aw/gh-aw/.github/workflows .cfg 64/pkg/tool/linux_amd64/vet l (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.2.3
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.2.3 --jq .object.sha --noprofile .cfg 64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha /a.out HEAD ortcfg.link nore tIlHZtiE0O8y-vwA-unsafeptr=false (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/# --jq .object.sha (http block)
  • https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha --abbrev-ref HEAD repository(owner: $owner, name:-f nore (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title feat: idempotent create-issue to prevent same-day duplicate reports feat: group-by-day for create-issue to prevent same-day duplicate reports Mar 24, 2026
@pelikhan pelikhan marked this pull request as ready for review March 24, 2026 17:24
Copilot AI review requested due to automatic review settings March 24, 2026 17:24
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a group-by-day option to the create-issue safe-output handler to avoid creating/closing duplicate report issues when scheduled workflows run multiple times in the same day, by commenting onto an existing same-day issue instead.

Changes:

  • Implement group-by-day pre-check in the JS create_issue handler to comment on an existing same-day open issue instead of creating a new one.
  • Wire the new option through the Go compiler/config + JSON schema, and enable it for the contribution-check workflow (including lockfile regen).
  • Add JS tests and documentation describing the new behavior.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pkg/workflow/create_issue.go Adds group-by-day to the create-issue YAML config and bool preprocessing.
pkg/workflow/compiler_safe_outputs_config.go Passes group_by_day through to the JS handler config.
pkg/parser/schemas/main_workflow_schema.json Adds group-by-day to the create-issue schema.
actions/setup/js/create_issue.cjs Implements same-day dedupe via search + comment fallback.
actions/setup/js/close_older_issues.cjs Exposes created_at in searchOlderIssues results to support day comparison.
actions/setup/js/create_issue.test.cjs Adds test coverage for group-by-day scenarios.
docs/src/content/docs/reference/safe-outputs.md Documents group-by-day and behavior details.
docs/src/content/docs/reference/frontmatter-full.md Adds group-by-day to the full reference example/config docs.
.github/workflows/contribution-check.md Enables group-by-day: true for the contribution check report.
.github/workflows/contribution-check.lock.yml Regenerates compiled lock config including group_by_day.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 119 to 125
.map(item => ({
number: item.number,
title: item.title,
html_url: item.html_url,
labels: item.labels || [],
created_at: item.created_at,
}));
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

searchOlderIssues now includes created_at in its returned objects, but the function’s JSDoc return type still describes only {number,title,html_url,labels}. Update the documented return shape to include created_at so downstream callers (like group-by-day) are accurately reflected in typings/docs.

Copilot uses AI. Check for mistakes.
Comment on lines +498 to +514
const existingIssues = await searchOlderIssues(
github,
repoParts.owner,
repoParts.repo,
workflowId,
0, // no issue to exclude — this is a pre-creation check
callerWorkflowId,
closeOlderKey
);
const todayIssue = existingIssues.find(issue => {
const createdDate = issue.created_at ? String(issue.created_at).split("T")[0] : "";
return createdDate === today;
});
if (todayIssue) {
core.info(`Group-by-day: found open issue #${todayIssue.number} created today (${today}) — posting new content as a comment`);
const comment = await addIssueComment(github, repoParts.owner, repoParts.repo, todayIssue.number, body);
core.info(`Posted content as comment ${comment.html_url} on issue #${todayIssue.number}`);
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

Group-by-day uses the global github client for searchOlderIssues/addIssueComment, but the handler otherwise creates an authenticated githubClient (which may use a per-handler github-token for cross-repo operations). This can cause the pre-check/comment to run with the wrong token and fail or operate on the wrong permissions. Use githubClient consistently for the search and comment API calls here (and for any other GitHub API calls in this handler) so authentication matches the target repo configuration.

Copilot uses AI. Check for mistakes.
Comment on lines +490 to +496
// Group-by-day check: if enabled, search for an existing open issue created today.
// When found, post the new content as a comment on the existing issue instead of
// creating a duplicate. This groups multiple same-day runs into a single issue.
// NOTE: processedCount was already incremented above. If we post as a comment, we undo
// the increment here so the max-count slot is not consumed.
if (groupByDayEnabled && (closeOlderKey || workflowId)) {
const today = new Date().toISOString().split("T")[0]; // YYYY-MM-DD (UTC)
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

The max-count guard runs before the group-by-day pre-check. If processedCount >= maxCount (e.g., max: 1 and one issue was already created earlier in the same run), the handler returns early and never attempts group-by-day, even though grouping is intended not to consume a max slot. Consider moving the group-by-day lookup before the max-count check/increment, or only increment/apply max when an issue is actually created (not when grouping as a comment).

Copilot uses AI. Check for mistakes.
@pelikhan
Copy link
Contributor

@copilot review comments

@pelikhan
Copy link
Contributor

@copilot fix tests CI / js (pull_request)Failing after 20s
CI / js-integration-live-api (pull_request)
CI / js-integration-live-api (pull_request)Failing after 20s
CI / js-qmd-index (pull_request)
CI / js-qmd-index (pull_request)Failing after 21s

- Update searchOlderIssues JSDoc @returns to include created_at field
- Use githubClient (not global github) for searchOlderIssues and addIssueComment
  so custom github-token config is respected for cross-repo operations
- Move processedCount++ after group-by-day check so max-count slots are
  never consumed for grouped-as-comment paths (removes the processedCount--
  hack)

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/853fae40-1f00-4e50-bbb7-5decaeea89c9
Copy link
Contributor Author

Copilot AI commented Mar 24, 2026

@copilot review comments

Addressed all three review comments in 877520a:

  1. JSDoc: Updated searchOlderIssues @returns to include created_at: string in the documented shape.
  2. Wrong client: Replaced global github with githubClient in both searchOlderIssues and addIssueComment calls so the configured github-token is respected for cross-repo operations.
  3. Max-count ordering: Removed the processedCount-- hack. processedCount++ now only executes just before actual issue creation, after the group-by-day check. Grouped (comment) paths return early without ever touching the counter.

Copy link
Contributor Author

Copilot AI commented Mar 24, 2026

@copilot fix tests CI / js (pull_request)Failing after 20s
[CI / js-integration-live-api (pull_request)](https://g...

The CI failures were TypeScript type errors (Property 'created_at' does not exist) caused by the JSDoc @returns on searchOlderIssues not listing created_at. The JSDoc fix in 877520a adds created_at: string to the return type, which makes tsc --noEmit pass cleanly — no more TS2339 errors.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/github/gh-aw/contents/.github%2Fworkflows%2Faudit-workflows.md
    • Triggering command: /opt/hostedtoolcache/node/24.14.0/x64/bin/node /opt/hostedtoolcache/node/24.14.0/x64/bin/node --experimental-import-meta-resolve --require /home/REDACTED/work/gh-aw/gh-aw/actions/setup/js/node_modules/vitest/suppress-warnings.cjs --conditions node --conditions development /home/REDACTED/work/gh-aw/gh-aw/actions/setup/js/node_modules/vitest/dist/workers/forks.js (http block)
  • invalid.example.invalid
    • Triggering command: /usr/lib/git-core/git-remote-https /usr/lib/git-core/git-remote-https origin https://invalid.example.invalid/nonexistent-repo.git e/git init�� de/node/bin/git git tions/setup/js/node_modules/.bin/git user.email test@example.comcheckout /git git bran�� -M main k/gh-aw/gh-aw/actions/setup/js/node_modules/.bin/git /tmp/bare-incremgit gin/feature-branadd cal/bin/git git (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@pelikhan pelikhan merged commit 6d3594e into main Mar 24, 2026
@pelikhan pelikhan deleted the copilot/deep-report-make-contribution-check-idempotent branch March 24, 2026 18:03
Copilot stopped work on behalf of pelikhan due to an error March 24, 2026 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[deep-report] Make Contribution Check reports idempotent to prevent duplicate open/closed issues

3 participants