Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 251 additions & 0 deletions .github/workflows/pr-comment-build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
name: Trigger build on PR comment
# Trigger a build when a comment is made on a PR with /build in the comment body
# We can do the following builds: linux, windows, macos
# e.g. /build linux=<targets>
# e.g. /build windows
# e.g. /build macos
# e.g. /build linux=<targets> windows
# e.g. /build linux=<targets> macos windows
#
# Linux targets can be comma-separated list of distros, e.g. ubuntu/20.04,centos/7
# e.g. /build linux=ubuntu/20.04,centos/7 windows
# e.g. /build linux=ubuntu/20.04 macos
# e.g. /build macos windows
# e.g. /build linux=centos/7
#
# If no platform is specified then nothing is done
on:
issue_comment:
jobs:
pr-build-comment:
name: Build on PR comment
# Ignore comments that are not on PRs (i.e. issues)
# Trigger only if /build is present in the comment body
if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/build')}}
runs-on: ubuntu-latest
outputs:
pr-number: ${{ github.event.issue.number }}
# Builds should be run (they may fail)
should-run: ${{ steps.parse.outputs.should_run }}
# Type of build to run
build-windows: ${{ steps.parse.outputs.windows }}
build-macos: ${{ steps.parse.outputs.macos }}
build-linux: ${{ steps.parse.outputs.linux }}
# Linux targets matrix to build
linux-targets-json: ${{ steps.parse.outputs.linux_targets_json }}
steps:
- name: Log useful info
run: |
echo "PR Number is ${{ github.event.issue.number }}"
echo "Comment ID is ${{ github.event.comment.id }}"
echo "Comment body is $BODY"
shell: bash
env:
BODY: ${{ github.event.comment.body }}

- name: Debug
uses: raven-actions/debug@v1

- name: Parse /build comment
id: parse
uses: actions/github-script@v8
with:
script: |
const body = (context.payload.comment?.body || '').trim();
const firstLine = body.split('\n')[0].trim();
const out = {
windows: false,
macos: false,
linuxTargets: [],
};

// Must start with '/build'
if (!/^\/build\b/i.test(firstLine)) {
core.setOutput('should_run', 'false');
core.setOutput('windows', 'false');
core.setOutput('macos', 'false');
core.setOutput('linux', 'false');
core.setOutput('linux_targets_json', '[]');
return;
}

const tail = firstLine.replace(/^\/build\b/i, '').trim();
if (tail.length > 0) {
const tokens = tail.split(/\s+/).filter(Boolean);
for (const raw of tokens) {
const lower = raw.toLowerCase();
if (lower === 'windows') {
out.windows = true;
} else if (lower === 'macos' || lower === 'macosx' || lower === 'osx') {
out.macos = true;
} else if (lower.startsWith('linux=')) {
// Keep original token to preserve any casing/symbols after '='
const rhs = raw.slice(raw.indexOf('=') + 1);
const arr = rhs.split(',').map(s => s.trim()).filter(Boolean);
out.linuxTargets.push(...arr);
}
}
}

const shouldRun = out.windows || out.macos || out.linuxTargets.length > 0;

core.setOutput('should_run', shouldRun ? 'true' : 'false');
core.setOutput('windows', out.windows ? 'true' : 'false');
core.setOutput('macos', out.macos ? 'true' : 'false');
core.setOutput('linux', out.linuxTargets.length > 0 ? 'true' : 'false');
core.setOutput('linux_targets_json', JSON.stringify(out.linuxTargets));

pr-build-get-metadata:
name: Get build metadata
needs: pr-build-comment
if: needs.pr-build-comment.outputs.should-run == 'true'
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 11, 2025

Choose a reason for hiding this comment

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

P1: Missing always() in condition. This job depends on conditional build jobs that will be skipped when not requested. Without always(), this job won't run when dependencies are skipped, so the results comment will never be posted.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/pr-comment-build.yaml, line 101:

<comment>Missing `always()` in condition. This job depends on conditional build jobs that will be skipped when not requested. Without `always()`, this job won&#39;t run when dependencies are skipped, so the results comment will never be posted.</comment>

<file context>
@@ -0,0 +1,251 @@
+    pr-build-get-metadata:
+        name: Get build metadata
+        needs: pr-build-comment
+        if: needs.pr-build-comment.outputs.should-run == &#39;true&#39;
+        runs-on: ubuntu-latest
+        permissions:
</file context>
Fix with Cubic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cubic-dev-ai this should only run if we decide to run the builds, the condition it has should detect that?

Copy link

Choose a reason for hiding this comment

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

@patrick-stephens You're right that the if: needs.pr-build-comment.outputs.should-run == 'true' condition correctly gates whether builds should trigger overall—that part works fine to detect if a valid /build command was parsed.

The issue I flagged is separate: even when builds are triggered (or partially skipped), the response job (which posts the results comment) depends on the build jobs via needs. When a build job like pr-build-windows isn't requested, it gets skipped entirely. GitHub Actions then skips the response job too, because skipped dependencies block the whole chain. That's why you need always() added to the response job's if condition, like this:

pr-build-comment-response:
    name: Respond to PR comment
    if: always() && needs.pr-build-comment.outputs.should-run == 'true'
    # ... rest unchanged

This ensures the response always runs to report what happened (success, skip, etc.), regardless of which builds fired. Without it, you'd never get that summary comment back on the PR.

runs-on: ubuntu-latest
permissions:
contents: read
outputs:
version: ${{ steps.set-version.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: refs/pull/${{ github.event.issue.number }}/merge

- name: Get the versions to use
# Look for the default line in the Dockerfile
id: set-version
run: |
VERSION=$(grep "ARG FLUENTDO_AGENT_VERSION=" Dockerfile.ubi | cut -d '=' -s -f 2 -)
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 11, 2025

Choose a reason for hiding this comment

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

P1: Rule violated: Require Safe Shell Scripts (set -euo pipefail or explicit env checks)

Shell script lacks set -euo pipefail and contains unguarded variable usage. If grep fails to find the pattern, VERSION will be empty and the script continues silently, propagating an empty version to downstream builds. Add strict shell options or use guarded parameter expansion.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/pr-comment-build.yaml, line 117:

<comment>Shell script lacks `set -euo pipefail` and contains unguarded variable usage. If `grep` fails to find the pattern, `VERSION` will be empty and the script continues silently, propagating an empty version to downstream builds. Add strict shell options or use guarded parameter expansion.</comment>

<file context>
@@ -0,0 +1,251 @@
+              # Look for the default line in the Dockerfile
+              id: set-version
+              run: |
+                  VERSION=$(grep &quot;ARG FLUENTDO_AGENT_VERSION=&quot; Dockerfile.ubi | cut -d &#39;=&#39; -s -f 2 -)
+                  echo &quot;Using version: $VERSION&quot;
+                  echo &quot;version=$VERSION&quot; &gt;&gt; $GITHUB_OUTPUT
</file context>
Fix with Cubic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cubic-dev-ai is this just meaning for pipefail? Github Actions should automatically handle set -eu

Copy link

Choose a reason for hiding this comment

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

@patrick-stephens No, it's not just about pipefail—the full set -euo pipefail combo is important for different reasons, and GitHub Actions doesn't fully handle this automatically.

GitHub does set set -e by default in shell steps (exits on command failure), but it skips -u (treat unset vars as errors) and -o pipefail (fail if any part of a pipeline fails, like your grep | cut chain). Without -u, if $GITHUB_OUTPUT were somehow unset (unlikely but possible), it'd silently fail. More critically, without -o pipefail, if grep finds nothing but cut runs anyway, the pipeline succeeds and VERSION stays empty—your script keeps going, echoing an empty version and passing it downstream, which could trigger broken builds.

Adding set -euo pipefail at the top of that step ensures the whole thing fails fast if the grep/cut doesn't produce a valid version. You could also guard it like this:

- name: Get the versions to use
  id: set-version
  run: |
    set -euo pipefail
    VERSION=$(grep "ARG FLUENTDO_AGENT_VERSION=" Dockerfile.ubi | cut -d '=' -s -f 2 -)
    if [[ -z "$VERSION" ]]; then
      echo "Error: Version not found in Dockerfile.ubi"
      exit 1
    fi
    echo "Using version: $VERSION"
    echo "version=$VERSION" >> "$GITHUB_OUTPUT"
  shell: bash

That way, if the file's missing or the ARG isn't there, the job fails early with a clear message instead of propagating junk.

echo "Using version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
shell: bash

pr-build-windows:
name: Build Windows
needs:
- pr-build-comment
- pr-build-get-metadata
if: needs.pr-build-comment.outputs.should-run == 'true' && needs.pr-build-comment.outputs.build-windows == 'true'
uses: ./.github/workflows/call-build-windows-packages.yaml
with:
version: ${{ needs.pr-build-get-metadata.outputs.version }}
ref: refs/pull/${{ needs.pr-build-comment.outputs.pr-number }}/merge

pr-build-macos:
name: Build macOS
needs:
- pr-build-comment
- pr-build-get-metadata
if: needs.pr-build-comment.outputs.should-run == 'true' && needs.pr-build-comment.outputs.build-macos == 'true'
uses: ./.github/workflows/call-build-macos-packages.yaml
with:
version: ${{ needs.pr-build-get-metadata.outputs.version }}
ref: refs/pull/${{ needs.pr-build-comment.outputs.pr-number }}/merge

pr-build-linux:
name: Build Linux
needs:
- pr-build-comment
- pr-build-get-metadata
if: needs.pr-build-comment.outputs.should-run == 'true' && needs.pr-build-comment.outputs.build-linux != ''
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 11, 2025

Choose a reason for hiding this comment

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

P1: Inconsistent condition for Linux build check. The output is set to 'true' or 'false' (strings), but this condition checks != ''. Since 'false' != '' evaluates to true, Linux builds will be triggered even when not requested. Use == 'true' for consistency with Windows and macOS checks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/pr-comment-build.yaml, line 149:

<comment>Inconsistent condition for Linux build check. The output is set to `&#39;true&#39;` or `&#39;false&#39;` (strings), but this condition checks `!= &#39;&#39;`. Since `&#39;false&#39; != &#39;&#39;` evaluates to true, Linux builds will be triggered even when not requested. Use `== &#39;true&#39;` for consistency with Windows and macOS checks.</comment>

<file context>
@@ -0,0 +1,251 @@
+        needs:
+            - pr-build-comment
+            - pr-build-get-metadata
+        if: needs.pr-build-comment.outputs.should-run == &#39;true&#39; &amp;&amp; needs.pr-build-comment.outputs.build-linux != &#39;&#39;
+        uses: ./.github/workflows/call-build-linux-packages.yaml
+        with:
</file context>
Fix with Cubic

uses: ./.github/workflows/call-build-linux-packages.yaml
with:
version: ${{ needs.pr-build-get-metadata.outputs.version }}
ref: refs/pull/${{ needs.pr-build-comment.outputs.pr-number }}/merge
target-matrix: ${{ needs.pr-build-comment.outputs.linux-targets-json }}

pr-build-comment-response:
name: Respond to PR comment
if: needs.pr-build-comment.outputs.should-run == 'true'
needs:
- pr-build-comment
- pr-build-windows
- pr-build-macos
- pr-build-linux
runs-on: ubuntu-latest
steps:
- name: Post results as PR comment
uses: actions/github-script@v8
env:
WINDOWS_REQUESTED: ${{ needs.pr-build-comment.outputs.build-windows }}
MACOS_REQUESTED: ${{ needs.pr-build-comment.outputs.build-macos }}
LINUX_REQUESTED: ${{ needs.pr-build-comment.outputs.build-linux }}
LINUX_TARGETS_JSON: ${{ needs.pr-build-comment.outputs.linux-targets-json }}
WINDOWS_RESULT: ${{ needs.pr-build-windows.result }}
MACOS_RESULT: ${{ needs.pr-build-macos.result }}
LINUX_RESULT: ${{ needs.pr-build-linux.result }}
with:
script: |
const {owner, repo} = context.repo;
const run_id = context.runId;

// Gather job URLs
const pages = await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
owner, repo, run_id, per_page: 100
});
const byName = {};
for (const job of pages) {
for (const j of job.jobs || []) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 11, 2025

Choose a reason for hiding this comment

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

P2: Incorrect iteration over paginated results. github.paginate() returns a flattened array of jobs directly, not response objects with jobs arrays. The inner loop for (const j of job.jobs || []) will never execute since job.jobs will be undefined.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/pr-comment-build.yaml, line 187:

<comment>Incorrect iteration over paginated results. `github.paginate()` returns a flattened array of jobs directly, not response objects with `jobs` arrays. The inner loop `for (const j of job.jobs || [])` will never execute since `job.jobs` will be undefined.</comment>

<file context>
@@ -0,0 +1,251 @@
+                    });
+                    const byName = {};
+                    for (const job of pages) {
+                        for (const j of job.jobs || []) {
+                            // Keep the first occurrence
+                            if (!byName[j.name]) {
</file context>
Fix with Cubic

// Keep the first occurrence
if (!byName[j.name]) {
byName[j.name] = j.html_url;
}
}
}

function statusEmoji(result) {
if (!result) return '➖';
const r = String(result).toLowerCase();
if (r === 'success') return '✅';
if (r === 'failure') return '❌';
if (r === 'cancelled') return '🛑';
if (r === 'skipped') return '⏭️';
return '❓';
}

const requested = {
windows: process.env.WINDOWS_REQUESTED === 'true',
macos: process.env.MACOS_REQUESTED === 'true',
linux: process.env.LINUX_REQUESTED === 'true',
};

const results = {
windows: process.env.WINDOWS_RESULT || 'skipped',
macos: process.env.MACOS_RESULT || 'skipped',
linux: process.env.LINUX_RESULT || 'skipped',
};

const linuxTargets = (() => {
try { return JSON.parse(process.env.LINUX_TARGETS_JSON || '[]'); }
catch { return []; }
})();

const rows = [];
if (requested.windows) {
const name = 'Build Windows packages';
const url = byName[name] || `${context.serverUrl}/${owner}/${repo}/actions/runs/${run_id}`;
rows.push(`- Windows: ${statusEmoji(results.windows)} (${results.windows}) — [Job logs](${url})`);
}
if (requested.macos) {
const name = 'Build macOS packages';
const url = byName[name] || `${context.serverUrl}/${owner}/${repo}/actions/runs/${run_id}`;
rows.push(`- macOS: ${statusEmoji(results.macos)} (${results.macos}) — [Job logs](${url})`);
}
if (requested.linux) {
const name = 'Build Linux packages';
const url = byName[name] || `${context.serverUrl}/${owner}/${repo}/actions/runs/${run_id}`;
const targetsStr = linuxTargets.length ? '`' + linuxTargets.join('`, `') + '`' : '_none_';
rows.push(`- Linux: ${statusEmoji(results.linux)} (${results.linux}) — Targets: ${targetsStr} — [Job logs](${url})`);
}

const body = [
`Build results for /build on PR #${process.env.ISSUE_NUMBER}:`,
'',
...rows,
].join('\n');

await github.rest.issues.createComment({
owner,
repo,
issue_number: Number(process.env.ISSUE_NUMBER),
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 11, 2025

Choose a reason for hiding this comment

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

P1: ISSUE_NUMBER is used but not defined in the env block. This will be undefined, causing the comment body to show 'undefined' and Number(undefined) to return NaN, breaking the API call.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/pr-comment-build.yaml, line 249:

<comment>`ISSUE_NUMBER` is used but not defined in the `env` block. This will be `undefined`, causing the comment body to show &#39;undefined&#39; and `Number(undefined)` to return `NaN`, breaking the API call.</comment>

<file context>
@@ -0,0 +1,251 @@
+                    await github.rest.issues.createComment({
+                        owner,
+                        repo,
+                        issue_number: Number(process.env.ISSUE_NUMBER),
+                        body
+                    });
</file context>
Fix with Cubic

body
});
Loading