Skip to content

fix: inline campaign discovery logic in campaign workflow step#20109

Merged
pelikhan merged 3 commits intomainfrom
copilot/fix-missing-module-error
Mar 8, 2026
Merged

fix: inline campaign discovery logic in campaign workflow step#20109
pelikhan merged 3 commits intomainfrom
copilot/fix-missing-module-error

Conversation

Copy link
Contributor

Copilot AI commented Mar 8, 2026

The compiled campaign workflow's "Precompute campaign discovery" step referenced /opt/gh-aw/actions/campaign_discovery.cjs, which is not needed as a built-in AW module. Instead, the discovery logic is inlined directly into the step script in the campaign workflow.

Changes

  • actions/setup/js/campaign_discovery.cjs (removed): Not needed as a standalone AW module
  • .github/workflows/security-alert-burndown.campaign.g.md (updated): Discovery step script now inlines the logic directly using github, core, fs, and path (all natively available in actions/github-script), rather than delegating to an external .cjs module
    • Searches GitHub for issues/PRs labeled with GH_AW_TRACKER_LABEL across GH_AW_DISCOVERY_REPOS, respecting GH_AW_MAX_DISCOVERY_ITEMS / GH_AW_MAX_DISCOVERY_PAGES budgets
    • Writes results to /tmp/gh-aw/campaign-discovery.json for the AI agent to read at runtime
    • Sets step outputs: items_count, campaign_id, discovery_file
  • .github/workflows/security-alert-burndown.campaign.g.lock.yml (recompiled): Reflects the inlined step script
  • actions/setup/README.md (updated): Corrected .cjs file count

[!WARNING]


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix error for missing module in setup script fix: add missing campaign_discovery.cjs module Mar 8, 2026
Copilot finished work on behalf of dsyme March 8, 2026 19:20
@pelikhan
Copy link
Contributor

pelikhan commented Mar 8, 2026

@dsyme is this an outdated file? setup.sh might just be outdated too.

@pelikhan
Copy link
Contributor

pelikhan commented Mar 8, 2026

@copilot investigate how this file is used

Copy link
Contributor Author

Copilot AI commented Mar 8, 2026

@copilot investigate how this file is used

Investigation complete. Here's how this file is used:

  1. Source: security-alert-burndown.campaign.g.md is an AI-generated orchestrator workflow file. It contains a steps: block in its frontmatter with a custom "Precompute campaign discovery" step that calls require('/opt/gh-aw/actions/campaign_discovery.cjs').

  2. Compilation: gh aw compile reads that .g.md file and compiles it into security-alert-burndown.campaign.g.lock.yml, which passes the step through verbatim into the agent job.

  3. Runtime: setup.sh copies all *.cjs files from actions/setup/js/ to the /opt/gh-aw/actions/ destination (via a glob loop). campaign_discovery.cjs simply wasn't there, so the runtime require() failed.

  4. Fix: Adding campaign_discovery.cjs to actions/setup/js/ is all that was needed — no changes to setup.sh required.

As for whether the .g.md file is "outdated" — it was generated by the campaign generator AI agent and references campaign_discovery.cjs as a built-in precompute utility. The fix was to provide that module so the orchestrator can precompute discovery data (search for labeled issues/PRs across repos) before the AI agent runs.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 8, 2026

Hey @Copilot 👋 — great catch on the missing campaign_discovery.cjs module! The fix is well-scoped and the PR description clearly explains the root cause (the compiled workflow in security-alert-burndown.campaign.g.lock.yml referenced /opt/gh-aw/actions/campaign_discovery.cjs, but the source file was never created in actions/setup/js/). The implementation itself looks solid.

One thing is missing before this can be considered complete:

  • Add a test file — every other .cjs module in actions/setup/js/ has a paired .test.cjs file (e.g. add_comment.cjs / add_comment.test.cjs, assign_issue.cjs / assign_issue.test.cjs, etc.). campaign_discovery.cjs is the only module in the directory without one. The CONTRIBUTING guidelines explicitly require agents to "Create tests for new functionality."

If you'd like a hand closing this gap, you can assign the following prompt to your coding agent:

Add a test file at actions/setup/js/campaign_discovery.test.cjs for the newly added campaign_discovery.cjs module.

Follow the exact same structure used in other test files in that directory (e.g. add_comment.test.cjs):
- Use vitest (describe/it/expect/beforeEach/afterEach)
- Set up mockCore, mockGithub, and global stubs in beforeEach; restore them in afterEach
- Import the module under test with require('./campaign_discovery.cjs')

Cover at least the following scenarios:
1. Happy path — valid env vars set, github.rest.issues.listForRepo returns results; verify core.setOutput is called with correct items_count, campaign_id, and discovery_file values.
2. Missing required env var (GH_AW_CAMPAIGN_ID not set) — verify core.setFailed is called with an ERR_CONFIG message.
3. Missing GH_AW_TRACKER_LABEL — verify core.setFailed with ERR_CONFIG.
4. Missing GH_AW_DISCOVERY_REPOS — verify core.setFailed with ERR_CONFIG.
5. Invalid GH_AW_MAX_DISCOVERY_ITEMS (non-integer) — verify core.setFailed with ERR_CONFIG.
6. API error on github.rest.issues.listForRepo — verify core.error is called with ERR_API prefix and execution continues without throwing.
7. Pagination budget respected — mock returns full pages and verify loop stops at GH_AW_MAX_DISCOVERY_ITEMS limit.

Mock the fs module to avoid writing real files during tests (use vi.mock or manual stubs consistent with patterns in the directory).

Generated by Contribution Check ·

@pelikhan
Copy link
Contributor

pelikhan commented Mar 8, 2026

@copilot This file is not needed in AW anymore. Remove it and integrate in the samples where needed

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 8, 2026

@copilot This file is not needed in AW anymore. Remove it and integrate in the samples where needed

Done in 2f80543. Removed campaign_discovery.cjs from actions/setup/js/ and inlined the discovery logic directly into the step script in security-alert-burndown.campaign.g.md. The .lock.yml has been recompiled to reflect the change — no more external module dependency.

Copilot AI changed the title fix: add missing campaign_discovery.cjs module fix: inline campaign discovery logic in campaign workflow step Mar 8, 2026
@pelikhan pelikhan marked this pull request as ready for review March 8, 2026 22:05
Copilot AI review requested due to automatic review settings March 8, 2026 22:05
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

This PR removes the standalone campaign_discovery.cjs module and inlines campaign discovery directly into the “Precompute campaign discovery” actions/github-script step for the security-alert-burndown campaign workflow.

Changes:

  • Inline campaign discovery logic into .github/workflows/security-alert-burndown.campaign.g.md (and recompiled lock workflow) instead of requiring /opt/gh-aw/actions/campaign_discovery.cjs.
  • Remove actions/setup/js/campaign_discovery.cjs as an unused standalone module.
  • Update actions/setup/README.md to reflect the current .cjs file count.

Reviewed changes

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

File Description
actions/setup/js/campaign_discovery.cjs Removed standalone campaign discovery module (logic now inlined in workflow step).
.github/workflows/security-alert-burndown.campaign.g.md Inlines campaign discovery logic into the actions/github-script step and writes discovery results to /tmp/gh-aw/campaign-discovery.json.
.github/workflows/security-alert-burndown.campaign.g.lock.yml Recompiled workflow reflecting the inlined discovery script.
actions/setup/README.md Updates documented counts for copied .cjs files and total files.

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

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +63 to +67
const campaignId = process.env.GH_AW_CAMPAIGN_ID;
const trackerLabel = process.env.GH_AW_TRACKER_LABEL;
const discoveryRepos = process.env.GH_AW_DISCOVERY_REPOS;
const cursorPath = process.env.GH_AW_CURSOR_PATH;
const maxItems = parseInt(process.env.GH_AW_MAX_DISCOVERY_ITEMS ?? "50", 10);
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

cursorPath is read from GH_AW_CURSOR_PATH but never used, and the discovery step does a full rescan every run. This conflicts with the workflow’s own instructions to use a durable cursor/checkpoint to avoid rescanning and to update the cursor file each run. Please either implement read/write of the cursor at cursorPath (e.g., boundary on updated_at + tie-breaker) or remove the cursor env var and the incremental-discovery requirements if they’re no longer applicable.

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +88

for (const repoPath of repos) {
const parts = repoPath.split("/");
if (parts.length !== 2) { core.warning(`Invalid repo format: "${repoPath}" — skipping`); continue; }
const [owner, repo] = parts;
let page = 1;
while (page <= maxPages && allItems.length < maxItems) {
const perPage = Math.min(30, maxItems - allItems.length);
try {
const response = await github.rest.issues.listForRepo({ owner, repo, labels: trackerLabel, state: "all", sort: "updated", direction: "desc", per_page: perPage, page });
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

GH_AW_MAX_DISCOVERY_PAGES is enforced per-repo (while (page <= maxPages) inside the repo loop). If GH_AW_DISCOVERY_REPOS contains multiple repos, the step can exceed the intended per-run page budget (e.g., 3 pages * N repos). Consider tracking a global page counter across all repos (or rename the variable / document that the budget is per-repo) to ensure the configured pagination budget is actually respected.

Suggested change
for (const repoPath of repos) {
const parts = repoPath.split("/");
if (parts.length !== 2) { core.warning(`Invalid repo format: "${repoPath}" — skipping`); continue; }
const [owner, repo] = parts;
let page = 1;
while (page <= maxPages && allItems.length < maxItems) {
const perPage = Math.min(30, maxItems - allItems.length);
try {
const response = await github.rest.issues.listForRepo({ owner, repo, labels: trackerLabel, state: "all", sort: "updated", direction: "desc", per_page: perPage, page });
let pagesUsed = 0;
for (const repoPath of repos) {
const parts = repoPath.split("/");
if (parts.length !== 2) { core.warning(`Invalid repo format: "${repoPath}" — skipping`); continue; }
const [owner, repo] = parts;
let page = 1;
while (allItems.length < maxItems && pagesUsed < maxPages) {
const perPage = Math.min(30, maxItems - allItems.length);
try {
const response = await github.rest.issues.listForRepo({ owner, repo, labels: trackerLabel, state: "all", sort: "updated", direction: "desc", per_page: perPage, page });
pagesUsed++;

Copilot uses AI. Check for mistakes.
try {
fs.mkdirSync(GH_AW_TMP_DIR, { recursive: true });
fs.writeFileSync(discoveryOutputPath, JSON.stringify(discoveryData, null, 2));
} catch (err) { core.warning(`Failed to write discovery data: ${err.message}`); }
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

If writing campaign-discovery.json fails, the step only logs a warning but still sets discovery_file to the (potentially non-existent) path and continues as success. Since downstream logic expects this file to exist, this should fail the step (e.g., core.setFailed(...) / rethrow) when the directory creation or write fails, or otherwise guarantee the file exists before setting outputs.

Suggested change
} catch (err) { core.warning(`Failed to write discovery data: ${err.message}`); }
} catch (err) {
core.setFailed(`Failed to write discovery data: ${err.message}`);
return;
}

Copilot uses AI. Check for mistakes.
@pelikhan pelikhan merged commit bb1f53d into main Mar 8, 2026
111 checks passed
@pelikhan pelikhan deleted the copilot/fix-missing-module-error branch March 8, 2026 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error: Unhandled error: Error: Cannot find module '/opt/gh-aw/actions/campaign_discovery.cjs'

4 participants