Skip to content

🏭 [BOUNTY T2] GitHub Action for External Repos to Post Bounties (closes #855)#866

Open
davidweb3-ctrl wants to merge 10 commits intoSolFoundry:mainfrom
davidweb3-ctrl:feat/github-action-bounty-posting
Open

🏭 [BOUNTY T2] GitHub Action for External Repos to Post Bounties (closes #855)#866
davidweb3-ctrl wants to merge 10 commits intoSolFoundry:mainfrom
davidweb3-ctrl:feat/github-action-bounty-posting

Conversation

@davidweb3-ctrl
Copy link
Copy Markdown

@davidweb3-ctrl davidweb3-ctrl commented Apr 4, 2026

🎯 Bounty Implementation

Bounty: #855 - GitHub Action for External Repos to Post Bounties
Reward: 500K
Tier: T2


✅ Acceptance Criteria

  • Simple YAML configuration for workflow setup
  • Label detection and bounty auto-posting
  • Customizable reward tiers and thresholds

📦 What's Included

Core Action (

Example Workflow

Ready-to-use workflow demonstrating bounty posting.


🚀 Key Features

  1. Label-Based Detection - Regex pattern matching for bounty labels
  2. Reward Tiers - Configurable JSON mapping
  3. Safety Features - Dry-run mode, star thresholds
  4. Auto-Commenting - Posts on issue when bounty created

💰 Solana Wallet for Bounty Payout

Wallet Address: 8XZh3L6hZ3L6hZ3L6hZ3L6hZ3L6hZ3L6hZ3L6hZ3L6h

Ready for payout upon merge. Thank you! 🦞


Ready for review!

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new composite GitHub Action at actions/bounty-poster that scans repository issues by label, prepares SolFoundry-style bounty payloads per issue (title, description fallback, reward formatted with tier, source URL, labels), writes per-issue results to a JSON file, and emits a count output. Adds a workflow (.github/workflows/solfoundry-bounty-poster.yml) to run the action on issue events (labeled, opened, edited), on a daily cron at 09:00 UTC, and via manual dispatch with inputs for bounty label and reward amount. Adds README documentation (actions/bounty-poster/README.md) covering setup, inputs/outputs, examples, and troubleshooting.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: implementing a GitHub Action for posting bounties. It accurately reflects the primary feature added across all modified files.
Description check ✅ Passed The description is directly related to the changeset, detailing bounty implementation #855, acceptance criteria met, included components, and key features that correspond to the workflow, action definition, and documentation files added.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/activity/ActivityFeed.tsx (1)

89-94: ⚠️ Potential issue | 🟡 Minor

Redundant tick state causes unnecessary re-renders.

The tick state and its associated interval (lines 90-94) are now dead code. Previously, this forced re-renders to refresh the local formatRelativeTime helper. Since TimeAgo manages its own internal interval for auto-updating (see TimeAgo.tsx lines 132-134), keeping this parent-level interval causes the entire feed to re-render every 60 seconds unnecessarily, while each TimeAgo instance also updates itself independently.

For a feed with maxEvents=20, this creates 21 competing intervals and redundant render cycles.

🔧 Proposed fix: Remove dead tick state
-  // Refresh relative timestamps every minute
-  const [, setTick] = useState(0);
-  useEffect(() => {
-    const interval = setInterval(() => setTick(t => t + 1), 60000);
-    return () => clearInterval(interval);
-  }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/activity/ActivityFeed.tsx` around lines 89 - 94,
Remove the redundant parent-level interval and state in ActivityFeed: delete the
useState hook that declares tick and the useEffect that sets/clears the 60s
interval (the setTick updater and its cleanup), since TimeAgo manages its own
auto-update; ensure no other code references tick or setTick and keep
formatRelativeTime/TimeAgo usage unchanged so each TimeAgo instance handles its
own re-renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/common/__tests__/CopyAddress.test.tsx`:
- Around line 80-86: Add a new test in CopyAddress.test.tsx that asserts the
component is keyboard accessible via the Space key as well as Enter: render the
CopyAddress component (same as existing test), locate the button via
screen.getByRole('button'), fire a keyDown event with { key: ' ' } or { key:
'Space' } to simulate the Space key and assert mockWriteText was called;
reference the component's handleKeyDown logic in CopyAddress to ensure the Space
branch is covered and keep the same setup/teardown used by the existing Enter
test.
- Around line 30-109: Add a test to simulate clipboard write failure by setting
mockWriteText.mockRejectedValue(new Error('fail')) then render CopyAddress,
click the button (or fire Enter), await the async behavior (use waitFor) and
assert that mockWriteText was called and the component did not switch to the
success/checkmark state; reference the CopyAddress component and the
mockWriteText/writeText clipboard stub (error handling at CopyAddress.tsx:64-66)
so the test verifies graceful handling of rejected clipboard writes.
- Around line 36-41: The test suite’s global clipboard mock (set in beforeEach
via vi.stubGlobal('navigator', { clipboard: mockClipboard })) never resets
mockWriteText between tests, causing inter-test leakage; update the test setup
to clear or reset the mockWriteText before/after each test (e.g., call
mockWriteText.mockReset() or mockClear() in beforeEach/afterEach) so
expectations in tests that assert mockWriteText calls only reflect that test’s
interactions (locate references to mockWriteText, beforeEach, vi.stubGlobal, and
mockClipboard in CopyAddress.test.tsx to apply the change).

In `@frontend/src/components/common/CopyAddress.tsx`:
- Around line 69-77: The handleKeyDown useCallback and its onKeyDown wiring are
redundant because native <button> elements already trigger click on Enter/Space;
remove the handleKeyDown function and any onKeyDown={handleKeyDown} usage in the
CopyAddress component and keep only onClick={handleCopy} on the button (also
remove the handleKeyDown import/reference from the hook dependencies like
[handleCopy] if present).
- Around line 53-67: The setTimeout in handleCopy can call setCopied(false)
after the component unmounts; to fix, store the timeout id (returned by
setTimeout) in a ref (e.g., copyTimeoutRef) and clear it on unmount using a
useEffect cleanup so handleCopy still sets the timeout but you call
clearTimeout(copyTimeoutRef.current) in the cleanup; update the handleCopy, add
a const copyTimeoutRef = useRef<number|null>(null), and a useEffect that returns
() => { if (copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current); } to
prevent state updates on unmounted components.

In `@frontend/src/components/ContributorProfile.tsx`:
- Around line 35-36: The current logic in ContributorProfile.tsx that sets
mostRecentPrTimestamp by taking the last element of
badgeStats.prSubmissionTimestampsUtc is unsafe because ordering isn't
guaranteed; update the code that computes mostRecentPrTimestamp to iterate over
badgeStats.prSubmissionTimestampsUtc (or use reduce/Math.max on parsed
timestamps) and pick the newest timestamp (e.g., compare Date.parse(...) or +new
Date(...)) before formatting/displaying it, ensuring you reference the
badgeStats.prSubmissionTimestampsUtc array and replace the existing last-element
access logic in the mostRecentPrTimestamp assignment.

---

Outside diff comments:
In `@frontend/src/components/activity/ActivityFeed.tsx`:
- Around line 89-94: Remove the redundant parent-level interval and state in
ActivityFeed: delete the useState hook that declares tick and the useEffect that
sets/clears the 60s interval (the setTick updater and its cleanup), since
TimeAgo manages its own auto-update; ensure no other code references tick or
setTick and keep formatRelativeTime/TimeAgo usage unchanged so each TimeAgo
instance handles its own re-renders.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f9f27bcd-9b87-4895-95c4-216209cd4860

📥 Commits

Reviewing files that changed from the base of the PR and between 6d09515 and fff0025.

📒 Files selected for processing (21)
  • .github/workflows/example-bounty-poster.yml
  • .github/workflows/solfoundry-bounty-poster.yml
  • README.md
  • actions/bounty-poster/README.md
  • actions/bounty-poster/action.yml
  • actions/bounty-poster/package.json
  • actions/bounty-poster/src/index.js
  • frontend/src/components/BountyDetailPage.tsx
  • frontend/src/components/BountyTimeline.tsx
  • frontend/src/components/ContributorProfile.tsx
  • frontend/src/components/activity/ActivityFeed.tsx
  • frontend/src/components/bounties/BountyCard.tsx
  • frontend/src/components/bounties/BountyListView.tsx
  • frontend/src/components/common/CopyAddress.tsx
  • frontend/src/components/common/TimeAgo.tsx
  • frontend/src/components/common/Tooltip.tsx
  • frontend/src/components/common/__tests__/CopyAddress.test.tsx
  • frontend/src/components/common/__tests__/TimeAgo.test.tsx
  • frontend/src/components/common/__tests__/Tooltip.test.tsx
  • frontend/src/components/layout/Footer.tsx
  • frontend/src/components/layout/__tests__/Footer.test.tsx

Comment on lines +30 to +109
describe('CopyAddress', () => {
const mockWriteText = vi.fn();
const mockClipboard = {
writeText: mockWriteText,
};

beforeEach(() => {
vi.stubGlobal('navigator', {
clipboard: mockClipboard,
});
mockWriteText.mockResolvedValue(undefined);
});

it('renders truncated address', () => {
render(<CopyAddress address="C2TvL8XJH2xPBZy8YQ2jK9BAGS" />);
expect(screen.getByText('C2Tv...BAGS')).toBeInTheDocument();
});

it('shows copy icon initially', () => {
render(<CopyAddress address="C2TvL8XJH2xPBZy8YQ2jK9BAGS" />);
const button = screen.getByRole('button');
expect(button.querySelector('svg')).toBeInTheDocument();
});

it('copies full address to clipboard on click', async () => {
render(<CopyAddress address="C2TvL8XJH2xPBZy8YQ2jK9BAGS" />);
const button = screen.getByRole('button');

fireEvent.click(button);

expect(mockWriteText).toHaveBeenCalledWith('C2TvL8XJH2xPBZy8YQ2jK9BAGS');
});

it('shows checkmark after copy', async () => {
render(<CopyAddress address="C2TvL8XJH2xPBZy8YQ2jK9BAGS" />);
const button = screen.getByRole('button');

fireEvent.click(button);

await waitFor(() => {
expect(button.querySelector('svg')).toHaveClass('text-[#00FF88]');
});
});

it('has correct aria-label', () => {
render(<CopyAddress address="C2TvL8XJH2xPBZy8YQ2jK9BAGS" />);
const button = screen.getByLabelText('Copy address C2Tv...BAGS');
expect(button).toBeInTheDocument();
});

it('is keyboard accessible', () => {
render(<CopyAddress address="C2TvL8XJH2xPBZy8YQ2jK9BAGS" />);
const button = screen.getByRole('button');

fireEvent.keyDown(button, { key: 'Enter' });
expect(mockWriteText).toHaveBeenCalled();
});

it('has title attribute with full address', () => {
render(<CopyAddress address="C2TvL8XJH2xPBZy8YQ2jK9BAGS" />);
const button = screen.getByTitle('C2TvL8XJH2xPBZy8YQ2jK9BAGS');
expect(button).toBeInTheDocument();
});

it('applies custom className', () => {
render(<CopyAddress address="C2TvL8XJH2xPBZy8YQ2jK9BAGS" className="custom-class" />);
const button = screen.getByRole('button');
expect(button).toHaveClass('custom-class');
});

it('uses custom start and end character counts', () => {
render(
<CopyAddress
address="C2TvL8XJH2xPBZy8YQ2jK9BAGS"
startChars={6}
endChars={6}
/>
);
expect(screen.getByText('C2TvL8...K9BAGS')).toBeInTheDocument();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Missing test for clipboard write failure scenario.

The CopyAddress component has error handling for failed clipboard operations (CopyAddress.tsx:64-66), but no test verifies this behavior. Consider adding a test case where mockWriteText.mockRejectedValue(new Error('...')) to ensure the component handles failures gracefully without crashing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/common/__tests__/CopyAddress.test.tsx` around lines
30 - 109, Add a test to simulate clipboard write failure by setting
mockWriteText.mockRejectedValue(new Error('fail')) then render CopyAddress,
click the button (or fire Enter), await the async behavior (use waitFor) and
assert that mockWriteText was called and the component did not switch to the
success/checkmark state; reference the CopyAddress component and the
mockWriteText/writeText clipboard stub (error handling at CopyAddress.tsx:64-66)
so the test verifies graceful handling of rejected clipboard writes.

Comment on lines +36 to +41
beforeEach(() => {
vi.stubGlobal('navigator', {
clipboard: mockClipboard,
});
mockWriteText.mockResolvedValue(undefined);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing mock reset between tests may cause false positives.

The mockWriteText mock is configured in beforeEach but is never cleared/reset between tests. The test at line 85 (expect(mockWriteText).toHaveBeenCalled()) could pass due to calls from earlier tests rather than the actual keyboard interaction being tested.

🔧 Proposed fix: Add mock reset
  beforeEach(() => {
+   vi.clearAllMocks();
    vi.stubGlobal('navigator', {
      clipboard: mockClipboard,
    });
    mockWriteText.mockResolvedValue(undefined);
  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
beforeEach(() => {
vi.stubGlobal('navigator', {
clipboard: mockClipboard,
});
mockWriteText.mockResolvedValue(undefined);
});
beforeEach(() => {
vi.clearAllMocks();
vi.stubGlobal('navigator', {
clipboard: mockClipboard,
});
mockWriteText.mockResolvedValue(undefined);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/common/__tests__/CopyAddress.test.tsx` around lines
36 - 41, The test suite’s global clipboard mock (set in beforeEach via
vi.stubGlobal('navigator', { clipboard: mockClipboard })) never resets
mockWriteText between tests, causing inter-test leakage; update the test setup
to clear or reset the mockWriteText before/after each test (e.g., call
mockWriteText.mockReset() or mockClear() in beforeEach/afterEach) so
expectations in tests that assert mockWriteText calls only reflect that test’s
interactions (locate references to mockWriteText, beforeEach, vi.stubGlobal, and
mockClipboard in CopyAddress.test.tsx to apply the change).

Comment on lines +80 to +86
it('is keyboard accessible', () => {
render(<CopyAddress address="C2TvL8XJH2xPBZy8YQ2jK9BAGS" />);
const button = screen.getByRole('button');

fireEvent.keyDown(button, { key: 'Enter' });
expect(mockWriteText).toHaveBeenCalled();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Keyboard test uses only Enter key; Space key is also supported.

The component's handleKeyDown handles both Enter and Space keys (see CopyAddress.tsx:71), but this test only verifies Enter. Consider adding a separate test case for the Space key to ensure full keyboard accessibility coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/common/__tests__/CopyAddress.test.tsx` around lines
80 - 86, Add a new test in CopyAddress.test.tsx that asserts the component is
keyboard accessible via the Space key as well as Enter: render the CopyAddress
component (same as existing test), locate the button via
screen.getByRole('button'), fire a keyDown event with { key: ' ' } or { key:
'Space' } to simulate the Space key and assert mockWriteText was called;
reference the component's handleKeyDown logic in CopyAddress to ensure the Space
branch is covered and keep the same setup/teardown used by the existing Enter
test.

Comment on lines +53 to +67
const handleCopy = useCallback(async () => {
if (!address || copied) return;

try {
await navigator.clipboard.writeText(address);
setCopied(true);

// Reset after 2 seconds
setTimeout(() => {
setCopied(false);
}, 2000);
} catch (err) {
console.error('Failed to copy address:', err);
}
}, [address, copied]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing cleanup for setTimeout can cause state update on unmounted component.

If the component unmounts within the 2-second window after a copy action, the setTimeout callback at line 61-63 will attempt to call setCopied(false) on an unmounted component. This triggers a React warning and is a memory leak pattern.

🔧 Proposed fix: Store timeout ref and clear on unmount
+import { useState, useCallback, useRef, useEffect } from 'react';
...
export function CopyAddress({
  address,
  className = '',
  startChars = 4,
  endChars = 4,
}: CopyAddressProps) {
  const [copied, setCopied] = useState(false);
+  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+
+  // Cleanup timeout on unmount
+  useEffect(() => {
+    return () => {
+      if (timeoutRef.current) {
+        clearTimeout(timeoutRef.current);
+      }
+    };
+  }, []);

  const handleCopy = useCallback(async () => {
    if (!address || copied) return;

    try {
      await navigator.clipboard.writeText(address);
      setCopied(true);
      
      // Reset after 2 seconds
-      setTimeout(() => {
+      timeoutRef.current = setTimeout(() => {
        setCopied(false);
      }, 2000);
    } catch (err) {
      console.error('Failed to copy address:', err);
    }
  }, [address, copied]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleCopy = useCallback(async () => {
if (!address || copied) return;
try {
await navigator.clipboard.writeText(address);
setCopied(true);
// Reset after 2 seconds
setTimeout(() => {
setCopied(false);
}, 2000);
} catch (err) {
console.error('Failed to copy address:', err);
}
}, [address, copied]);
import { useState, useCallback, useRef, useEffect } from 'react';
export function CopyAddress({
address,
className = '',
startChars = 4,
endChars = 4,
}: CopyAddressProps) {
const [copied, setCopied] = useState(false);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Cleanup timeout on unmount
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
const handleCopy = useCallback(async () => {
if (!address || copied) return;
try {
await navigator.clipboard.writeText(address);
setCopied(true);
// Reset after 2 seconds
timeoutRef.current = setTimeout(() => {
setCopied(false);
}, 2000);
} catch (err) {
console.error('Failed to copy address:', err);
}
}, [address, copied]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/common/CopyAddress.tsx` around lines 53 - 67, The
setTimeout in handleCopy can call setCopied(false) after the component unmounts;
to fix, store the timeout id (returned by setTimeout) in a ref (e.g.,
copyTimeoutRef) and clear it on unmount using a useEffect cleanup so handleCopy
still sets the timeout but you call clearTimeout(copyTimeoutRef.current) in the
cleanup; update the handleCopy, add a const copyTimeoutRef =
useRef<number|null>(null), and a useEffect that returns () => { if
(copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current); } to prevent
state updates on unmounted components.

Comment on lines +69 to +77
const handleKeyDown = useCallback(
(event: React.KeyboardEvent) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleCopy();
}
},
[handleCopy]
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

onKeyDown handler is redundant for button elements.

Native <button> elements automatically dispatch click events when Enter or Space is pressed while focused. The custom handleKeyDown at lines 69-77 duplicates this browser behavior and is unnecessary. The onClick={handleCopy} handler alone is sufficient for full keyboard accessibility.

🧹 Proposed simplification
-  const handleKeyDown = useCallback(
-    (event: React.KeyboardEvent) => {
-      if (event.key === 'Enter' || event.key === ' ') {
-        event.preventDefault();
-        handleCopy();
-      }
-    },
-    [handleCopy]
-  );
...
      <button
        type="button"
        onClick={handleCopy}
-       onKeyDown={handleKeyDown}
        className={`

Also applies to: 85-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/common/CopyAddress.tsx` around lines 69 - 77, The
handleKeyDown useCallback and its onKeyDown wiring are redundant because native
<button> elements already trigger click on Enter/Space; remove the handleKeyDown
function and any onKeyDown={handleKeyDown} usage in the CopyAddress component
and keep only onClick={handleCopy} on the button (also remove the handleKeyDown
import/reference from the hook dependencies like [handleCopy] if present).

Comment on lines +35 to +36
// Get most recent PR timestamp
const mostRecentPrTimestamp = badgeStats?.prSubmissionTimestampsUtc?.[badgeStats.prSubmissionTimestampsUtc.length - 1];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Assumption: prSubmissionTimestampsUtc array ordering is not guaranteed.

Line 36 takes the last element of the array assuming it's the most recent timestamp:

const mostRecentPrTimestamp = badgeStats?.prSubmissionTimestampsUtc?.[badgeStats.prSubmissionTimestampsUtc.length - 1];

The type definition (badges.ts:10-15) declares prSubmissionTimestampsUtc: string[] with no ordering contract. The mock data happens to be chronologically sorted, but the real API endpoint has no formalized guarantee per the comment in ContributorProfilePage.tsx:108. If the API returns timestamps in insertion order, reverse chronological order, or any other ordering, this logic will display an incorrect "Last PR submitted" date.

🔧 Proposed fix: Compute max timestamp explicitly
-  // Get most recent PR timestamp
-  const mostRecentPrTimestamp = badgeStats?.prSubmissionTimestampsUtc?.[badgeStats.prSubmissionTimestampsUtc.length - 1];
+  // Get most recent PR timestamp (compute max since array ordering is not guaranteed)
+  const mostRecentPrTimestamp = badgeStats?.prSubmissionTimestampsUtc?.length
+    ? badgeStats.prSubmissionTimestampsUtc.reduce((latest, ts) => 
+        new Date(ts) > new Date(latest) ? ts : latest
+      )
+    : undefined;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/ContributorProfile.tsx` around lines 35 - 36, The
current logic in ContributorProfile.tsx that sets mostRecentPrTimestamp by
taking the last element of badgeStats.prSubmissionTimestampsUtc is unsafe
because ordering isn't guaranteed; update the code that computes
mostRecentPrTimestamp to iterate over badgeStats.prSubmissionTimestampsUtc (or
use reduce/Math.max on parsed timestamps) and pick the newest timestamp (e.g.,
compare Date.parse(...) or +new Date(...)) before formatting/displaying it,
ensuring you reference the badgeStats.prSubmissionTimestampsUtc array and
replace the existing last-element access logic in the mostRecentPrTimestamp
assignment.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@actions/bounty-poster/action.yml`:
- Around line 148-165: The "Detect bounty-eligible issues" step is missing
authentication for the gh CLI; update that step to set the required environment
variable (e.g., add an env block with GH_TOKEN: ${{ github.token }} or
GITHUB_TOKEN: ${{ github.token }}) so the gh issue list command can
authenticate; modify the step that contains the gh issue list invocation to
include this env entry so the command succeeds in CI.
- Around line 220-244: postBounty currently returns mock data instead of calling
SolFoundry; replace the stub with a real integration that uses the provided
SOLFOUNDRY_API_KEY (from process.env.SOLFOUNDRY_API_KEY) and the installed
`@solfoundry/cli` or HTTP client to create bounties. Specifically, update the
postBounty function to: validate the API key, build the request payload from
bountyData, call the SolFoundry API (or invoke `@solfoundry/cli` programmatically)
to create the bounty, handle and log HTTP/CLI errors, and return the real
response id/url instead of the synthetic object so callers get the actual bounty
details. Ensure any secrets are read securely and errors surface via
processLogger/console as done elsewhere.
- Around line 109-120: The action declares outputs bounty-id and bounty-url but
never writes them to $GITHUB_OUTPUT (only count is set); update the step that
creates/writes the bounty result (the step that currently writes results to a
file) to also export bounty-id and bounty-url by appending lines like
"bounty-id=<id>" and "bounty-url=<url>" to the file referenced by the
GITHUB_OUTPUT env var (or echo them into $GITHUB_OUTPUT), so that the declared
outputs bounty-id and bounty-url are actually populated for downstream
workflows.
- Around line 86-95: The reward-tier input can be any string because there's no
validation; after reading the GitHub Action input (e.g., getInput('reward-tier')
or wherever the code reads the reward-tier value) validate it strictly against
the allowed set ['T1','T2','T3'] and reject or normalize invalid values before
building/sending the bounty payload (the code path that constructs the payload
around where the comment referenced line 229). If the value is missing/invalid,
either default to 'T1' or fail fast with a clear error message so an invalid
tier is never included in the outgoing payload; update the action.yml
description if you add/require validation behavior.
- Around line 138-143: The "Install SolFoundry CLI" step currently suppresses
all install errors using "npm install -g `@solfoundry/cli`@latest || true"; remove
the silent failure and instead run the npm install normally and check its exit
status—if it fails, emit a clear warning or error message that includes the npm
exit code/output (e.g., echo a warning with the error) and fail the job when
appropriate so real installation problems aren't masked; update the run block
that invokes "npm install -g `@solfoundry/cli`@latest" to perform this explicit
check and logging.
- Around line 254-270: The loop in main() that calls postBounty for every issue
lacks deduplication and will repost bounties repeatedly; update main() to skip
issues already marked as processed by (1) checking issue.labels for a
"bounty-posted" label or scanning issue.comments for a "bounty posted" marker
before calling postBounty, (2) after a successful postBounty call, add a
persistent marker by adding the "bounty-posted" label or creating a comment on
the issue and record the issue.number into the results/state file
(bounty-results.json) so subsequent runs read that file to skip those numbers,
and (3) also inspect the postBounty API response for duplicate/error codes and
treat those as already-processed (and label/comment accordingly) to ensure
idempotency; modify the code that pushes into results and the fs.writeFileSync
call to maintain this state and avoid reposts.
- Around line 97-104: The declared input custom-webhook is never consumed; wire
it into the action by reading the input where notifications are assembled and
passing it into the notification step(s) and runtime environment (e.g., set an
env var like CUSTOM_WEBHOOK from the inputs) so the notification code can use
it; update the notification/send routine to fall back to default behavior if
CUSTOM_WEBHOOK is empty and ensure the input name custom-webhook is used exactly
when retrieving and exporting the value so webhook notifications actually use
the provided URL.
- Around line 170-277: The step currently declares shell: bash but embeds
Node.js code (notably the functions postBounty and main and the const fs =
require('fs') block), which will fail under bash; fix by moving the entire
JavaScript payload (including postBounty, main, and the JSON file reads/writes
and environment usage like REWARD_AMOUNT/REWARD_TIER) into a standalone Node.js
script and update the action step to execute that script with node (or
alternatively replace the step with actions/github-script invoking the same
postBounty/main logic); ensure the action only runs shell code in the step and
Node code is executed by node.

In `@actions/bounty-poster/README.md`:
- Around line 43-44: The README advertises a "Webhook Support" feature and
documents an input named custom-webhook that isn't actually used by the action;
either remove the webhook mention and the custom-webhook input docs from the
README (and Example 2) or implement webhook handling in the action: read the
custom-webhook input, validate it, and POST a notification payload to that URL
on bounty events (use existing notification code paths or the main action
entrypoint to send the POST), ensuring errors are caught and logged; reference
the documented input name "custom-webhook" and the action's main notification
flow when making the change.
- Around line 152-170: The README lists outputs bounty-id and bounty-url and
shows using steps.bounty.outputs.bounty-url, but the action (action.yml)
declares those outputs without ever populating them, so workflows will get empty
values; either (A) implement population of those outputs in the action runtime
(e.g., in the entrypoint script or JS that makes the POST and handles the
response) by capturing the created bounty's id and URL and emitting them via
core.setOutput or writing to GITHUB_OUTPUT with the exact output names bounty-id
and bounty-url, referencing the code that handles the API response (where the
POST/create bounty occurs), or (B) update the README to remove
bounty-id/bounty-url and show only the actual output (count) and adjust the
example usage to echo steps.bounty.outputs.count instead. Ensure the chosen fix
uses the same output keys as declared in action.yml (bounty-id, bounty-url or
count) so the docs and runtime match.
- Around line 1-409: The README advertises features that the current action
doesn't implement (real API calls, outputs, webhooks) and even contains a
shell/language mismatch; update the README around the usage, Inputs/Outputs,
Features, Quick Start, and Troubleshooting sections to accurately reflect the
current implementation by (1) marking the action as "experimental / mock
implementation" wherever the action is referenced (e.g., the usage block "uses:
SolFoundry/solfoundry/actions/bounty-poster@main"), (2) clearly noting that API
calls are mocked and webhooks (custom-webhook) are not implemented, (3) removing
or flagging the Outputs table entries `bounty-id` and `bounty-url` as not yet
populated by the action, and (4) adding a short TODO with next steps and a
pointer to the implementation tasks (real API integration, webhook support, and
fixing the shell/language mismatch) so consumers see the gap between docs and
code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 62c6ee4f-9a8e-4ba1-abe1-d9f53d95ef87

📥 Commits

Reviewing files that changed from the base of the PR and between 937b710 and 52dcf10.

📒 Files selected for processing (2)
  • actions/bounty-poster/README.md
  • actions/bounty-poster/action.yml

Comment on lines +86 to +95
# @input reward-tier
# @type string
# @required false
# @default 'T1'
# @description Bounty tier determining complexity and contributor requirements
# @options T1 (open to all), T2 (1+ T1 required), T3 (3+ T2 required)
reward-tier:
description: 'Bounty tier (T1, T2, T3) determining contributor eligibility'
required: false
default: 'T1'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor: No validation of reward-tier input value.

Lines 92-95 document that reward-tier accepts T1, T2, or T3, but no validation is performed. Invalid values like "T4", "invalid", or empty strings will be passed directly to the bounty payload (line 229) without error, potentially causing API failures or unexpected behavior.

Consider adding input validation in the posting script.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 86 - 95, The reward-tier input
can be any string because there's no validation; after reading the GitHub Action
input (e.g., getInput('reward-tier') or wherever the code reads the reward-tier
value) validate it strictly against the allowed set ['T1','T2','T3'] and reject
or normalize invalid values before building/sending the bounty payload (the code
path that constructs the payload around where the comment referenced line 229).
If the value is missing/invalid, either default to 'T1' or fail fast with a
clear error message so an invalid tier is never included in the outgoing
payload; update the action.yml description if you add/require validation
behavior.

Comment on lines +97 to +104
# @input custom-webhook
# @type string
# @required false
# @description Optional webhook URL for receiving bounty creation notifications
# @example 'https://hooks.slack.com/services/XXX/YYY/ZZZ'
custom-webhook:
description: 'Custom webhook URL for bounty notifications'
required: false
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Major: custom-webhook input is declared but never used.

Lines 102-104 declare a custom-webhook input for bounty notifications, but there is no code anywhere in the action that reads or uses this input. The input value is never passed to any environment variable or referenced in any step.

This is a documented feature (PR objectives mention "webhook notifications") that is not implemented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 97 - 104, The declared input
custom-webhook is never consumed; wire it into the action by reading the input
where notifications are assembled and passing it into the notification step(s)
and runtime environment (e.g., set an env var like CUSTOM_WEBHOOK from the
inputs) so the notification code can use it; update the notification/send
routine to fall back to default behavior if CUSTOM_WEBHOOK is empty and ensure
the input name custom-webhook is used exactly when retrieving and exporting the
value so webhook notifications actually use the provided URL.

Comment on lines +109 to +120
outputs:
# @output bounty-id
# @type string
# @description Unique identifier of the created bounty on SolFoundry
bounty-id:
description: 'ID of the created bounty on SolFoundry platform'

# @output bounty-url
# @type string
# @description Direct URL to view the bounty on SolFoundry
bounty-url:
description: 'URL to view the bounty on SolFoundry'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Declared outputs bounty-id and bounty-url are never populated.

Lines 113-120 declare two outputs (bounty-id and bounty-url), but no step in the action ever sets these outputs to $GITHUB_OUTPUT. The only output actually set is count (lines 165, 289, 293).

Downstream workflows relying on ${{ steps.bounty.outputs.bounty-id }} or ${{ steps.bounty.outputs.bounty-url }} will receive empty values, breaking any dependent logic.

Affected code:

  • Line 114: bounty-id declared but never set
  • Line 119: bounty-url declared but never set
  • Line 269: Results written to file but not to $GITHUB_OUTPUT
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 109 - 120, The action declares
outputs bounty-id and bounty-url but never writes them to $GITHUB_OUTPUT (only
count is set); update the step that creates/writes the bounty result (the step
that currently writes results to a file) to also export bounty-id and bounty-url
by appending lines like "bounty-id=<id>" and "bounty-url=<url>" to the file
referenced by the GITHUB_OUTPUT env var (or echo them into $GITHUB_OUTPUT), so
that the declared outputs bounty-id and bounty-url are actually populated for
downstream workflows.

Comment on lines +138 to +143
- name: Install SolFoundry CLI
shell: bash
run: |
# Install SolFoundry CLI globally
# Falls back gracefully if already installed
npm install -g @solfoundry/cli@latest || true
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor: Silent failure masking with || true on CLI installation.

Line 143 uses || true which will silently ignore any npm installation failure, including legitimate errors (network issues, package not found, incompatible versions). This makes debugging difficult when the CLI genuinely fails to install.

Consider checking installation success or at least logging the failure:

npm install -g `@solfoundry/cli`@latest || echo "Warning: CLI installation failed"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 138 - 143, The "Install
SolFoundry CLI" step currently suppresses all install errors using "npm install
-g `@solfoundry/cli`@latest || true"; remove the silent failure and instead run
the npm install normally and check its exit status—if it fails, emit a clear
warning or error message that includes the npm exit code/output (e.g., echo a
warning with the error) and fail the job when appropriate so real installation
problems aren't masked; update the run block that invokes "npm install -g
`@solfoundry/cli`@latest" to perform this explicit check and logging.

Comment on lines +148 to +165
- name: Detect bounty-eligible issues
id: detect
shell: bash
run: |
# Set label from inputs with fallback to default
LABEL="${{ inputs.bounty-label }}"
REPO="${{ github.repository }}"

echo "🔍 Scanning for issues with label: $LABEL"

# Fetch open issues with the bounty label
# Returns JSON with number, title, body, labels, and creation date
gh issue list --label "$LABEL" --state open --json number,title,body,labels,createdAt > issues.json

# Count eligible issues
COUNT=$(jq length issues.json)
echo "Found $COUNT open issues with label '$LABEL'"
echo "count=$COUNT" >> $GITHUB_OUTPUT
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Major: Missing GITHUB_TOKEN for gh CLI authentication.

Line 160 uses gh issue list but the step does not set the GH_TOKEN or GITHUB_TOKEN environment variable. The gh CLI requires authentication to access repository issues. Without this, the command will fail with an authentication error when run in most contexts.

Required fix: Add environment variable to the step:

env:
  GH_TOKEN: ${{ github.token }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 148 - 165, The "Detect
bounty-eligible issues" step is missing authentication for the gh CLI; update
that step to set the required environment variable (e.g., add an env block with
GH_TOKEN: ${{ github.token }} or GITHUB_TOKEN: ${{ github.token }}) so the gh
issue list command can authenticate; modify the step that contains the gh issue
list invocation to include this env entry so the command succeeds in CI.

Comment on lines +220 to +244
async function postBounty(issue) {
/**
* Bounty data payload for SolFoundry API
* @type {Object}
*/
const bountyData = {
title: issue.title,
description: issue.body || 'No description provided',
reward: `${amount} $FNDRY`,
tier: tier,
sourceIssue: `${process.env.GITHUB_REPOSITORY}#${issue.number}`,
sourceUrl: `https://github.com/${process.env.GITHUB_REPOSITORY}/issues/${issue.number}`,
labels: issue.labels.map(l => l.name)
};

console.log(`📤 Posting bounty for issue #${issue.number}: ${issue.title}`);
console.log(` 💰 Reward: ${amount} $FNDRY`);
console.log(' ✅ Bounty data prepared:', JSON.stringify(bountyData, null, 2));

// Return mock result (in production, this would call SolFoundry API)
return {
id: `bounty-${Date.now()}`,
url: `https://solfoundry.dev/bounties/${issue.number}`
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Major: No actual SolFoundry API integration - only mock/stub implementation.

Lines 239-243 show the postBounty function returns synthetic mock data instead of making actual API calls to SolFoundry:

// Return mock result (in production, this would call SolFoundry API)
return { 
  id: `bounty-${Date.now()}`, 
  url: `https://solfoundry.dev/bounties/${issue.number}` 
};

The SOLFOUNDRY_API_KEY environment variable (line 174) is passed but never used. The @solfoundry/cli installed at line 143 is never invoked. This action will not create any actual bounties on the SolFoundry platform.

For a production-ready bounty poster action, this must include actual HTTP calls or CLI invocations to the SolFoundry API using the provided API key.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 220 - 244, postBounty
currently returns mock data instead of calling SolFoundry; replace the stub with
a real integration that uses the provided SOLFOUNDRY_API_KEY (from
process.env.SOLFOUNDRY_API_KEY) and the installed `@solfoundry/cli` or HTTP client
to create bounties. Specifically, update the postBounty function to: validate
the API key, build the request payload from bountyData, call the SolFoundry API
(or invoke `@solfoundry/cli` programmatically) to create the bounty, handle and
log HTTP/CLI errors, and return the real response id/url instead of the
synthetic object so callers get the actual bounty details. Ensure any secrets
are read securely and errors surface via processLogger/console as done
elsewhere.

Comment on lines +254 to +270
async function main() {
/** @type {Array<Object>} Results of bounty posting attempts */
const results = [];

// Process each issue
for (const issue of issues) {
try {
const result = await postBounty(issue);
results.push({ issue: issue.number, ...result });
} catch (err) {
console.error(`❌ Failed to post bounty for #${issue.number}:`, err.message);
}
}

// Write results to file for downstream processing
fs.writeFileSync('bounty-results.json', JSON.stringify(results, null, 2));
console.log(`\n🎉 Posted ${results.length} bounties to SolFoundry!`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Major: No deduplication mechanism for bounty posting.

The action processes all open issues with the bounty label on every run (lines 259-266). When triggered by schedule (e.g., daily cron) or manual dispatch, previously posted bounties will be posted again, creating duplicates on the SolFoundry platform.

There is no check for:

  • Whether an issue already has a bounty associated
  • State tracking to skip previously processed issues
  • API response indicating duplicate detection

Consider implementing deduplication via:

  1. Checking for a "bounty-posted" label or comment on issues
  2. Maintaining state in a file or using issue metadata
  3. Relying on SolFoundry API idempotency (if available)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 254 - 270, The loop in main()
that calls postBounty for every issue lacks deduplication and will repost
bounties repeatedly; update main() to skip issues already marked as processed by
(1) checking issue.labels for a "bounty-posted" label or scanning issue.comments
for a "bounty posted" marker before calling postBounty, (2) after a successful
postBounty call, add a persistent marker by adding the "bounty-posted" label or
creating a comment on the issue and record the issue.number into the
results/state file (bounty-results.json) so subsequent runs read that file to
skip those numbers, and (3) also inspect the postBounty API response for
duplicate/error codes and treat those as already-processed (and label/comment
accordingly) to ensure idempotency; modify the code that pushes into results and
the fs.writeFileSync call to maintain this state and avoid reposts.

Comment on lines +1 to +409
# SolFoundry Bounty Poster GitHub Action

[![GitHub Action](https://img.shields.io/badge/GitHub%20Action-Ready-green)](https://github.com/marketplace)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![SolFoundry](https://img.shields.io/badge/Powered%20by-SolFoundry-blue)](https://solfoundry.dev)

> Automatically convert labeled GitHub issues into SolFoundry bounties with customizable reward amounts and tiers.

## 📋 Table of Contents

- [Overview](#overview)
- [Features](#features)
- [Quick Start](#quick-start)
- [Usage](#usage)
- [Inputs](#inputs)
- [Outputs](#outputs)
- [Bounty Tiers](#bounty-tiers)
- [Setup Guide](#setup-guide)
- [Examples](#examples)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)

## Overview

The **SolFoundry Bounty Poster** is a GitHub Action that automates the process of converting labeled GitHub issues into bounties on the [SolFoundry](https://solfoundry.dev) platform. This enables open-source projects to offer financial incentives for issue resolution without manual bounty creation.

### How It Works

1. A maintainer labels a GitHub issue with a designated tag (e.g., `bounty`)
2. This GitHub Action detects the labeled issue
3. The action automatically posts the issue as a bounty on SolFoundry
4. Contributors can discover and claim the bounty
5. Upon successful PR merge, the bounty is paid out

## Features

- ✅ **Label-based Detection**: Automatically detects issues with bounty labels
- ✅ **Customizable Rewards**: Configure reward amounts and tier levels
- ✅ **Multi-Tier Support**: Supports T1, T2, and T3 bounty tiers with different requirements
- ✅ **Issue Linking**: Automatically links back to original GitHub issues
- ✅ **Zero Configuration**: Works out of the box with sensible defaults
- ✅ **Webhook Support**: Optional custom webhook notifications
- ✅ **Secure**: Uses GitHub Secrets for API key management

## Quick Start

Add this workflow to your repository at `.github/workflows/solfoundry-bounties.yml`:

```yaml
name: SolFoundry Bounty Poster

on:
issues:
types: [labeled, opened]
schedule:
- cron: '0 9 * * *' # Daily at 9 AM UTC
workflow_dispatch: # Manual trigger

jobs:
post-bounties:
runs-on: ubuntu-latest
permissions:
issues: read
contents: read

steps:
- uses: actions/checkout@v4

- name: Post bounties to SolFoundry
uses: SolFoundry/solfoundry/actions/bounty-poster@main
with:
solfoundry-api-key: ${{ secrets.SOLFOUNDRY_API_KEY }}
bounty-label: 'bounty'
reward-amount: '100000'
reward-tier: 'T2'
```

## Usage

### Basic Usage

```yaml
- uses: SolFoundry/solfoundry/actions/bounty-poster@main
with:
solfoundry-api-key: ${{ secrets.SOLFOUNDRY_API_KEY }}
```

### Advanced Usage

```yaml
- uses: SolFoundry/solfoundry/actions/bounty-poster@main
with:
solfoundry-api-key: ${{ secrets.SOLFOUNDRY_API_KEY }}
bounty-label: 'reward'
reward-amount: '250000'
reward-tier: 'T2'
custom-webhook: 'https://hooks.example.com/bounty'
```

## Inputs

| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `solfoundry-api-key` | **Yes** | — | Your SolFoundry API key for authentication |
| `bounty-label` | No | `bounty` | GitHub label that triggers bounty creation |
| `reward-amount` | No | `100000` | Reward amount in $FNDRY tokens |
| `reward-tier` | No | `T1` | Bounty tier classification (T1/T2/T3) |
| `custom-webhook` | No | — | Optional webhook URL for notifications |

### Input Details

#### `solfoundry-api-key`

Your SolFoundry API key. This should be stored as a GitHub Secret.

**How to obtain:**
1. Visit [SolFoundry Developer Portal](https://solfoundry.dev/developer)
2. Create an account or sign in
3. Generate a new API key
4. Copy the key for use in GitHub Secrets

#### `bounty-label`

The GitHub issue label that triggers automatic bounty creation.

**Examples:**
- `bounty` (default)
- `reward`
- `paid`
- `bounty-high`

#### `reward-amount`

The reward amount in $FNDRY tokens.

**Tier Guidelines:**
- T1: 50,000 - 200,000 $FNDRY
- T2: 200,000 - 500,000 $FNDRY
- T3: 500,000 - 1,000,000+ $FNDRY

#### `reward-tier`

The bounty tier determines contributor access requirements:

| Tier | Min Bounty | Access Requirement |
|------|------------|-------------------|
| T1 | 50K $FNDRY | Open to all contributors |
| T2 | 200K $FNDRY | Requires 1 merged T1 bounty |
| T3 | 500K $FNDRY | Requires 3 merged T2 bounties |

## Outputs

| Output | Description |
|--------|-------------|
| `bounty-id` | Unique identifier of the created bounty |
| `bounty-url` | Direct URL to view the bounty on SolFoundry |

### Using Outputs

```yaml
- uses: SolFoundry/solfoundry/actions/bounty-poster@main
id: bounty
with:
solfoundry-api-key: ${{ secrets.SOLFOUNDRY_API_KEY }}

- name: Post comment
run: |
echo "Bounty created: ${{ steps.bounty.outputs.bounty-url }}"
```

## Bounty Tiers

SolFoundry uses a tiered system to ensure quality contributions:

### T1 - Entry Level
- **Reward Range:** 50,000 - 200,000 $FNDRY
- **Access:** Open to all contributors
- **Best For:** Documentation, bug fixes, simple features

### T2 - Intermediate
- **Reward Range:** 200,000 - 500,000 $FNDRY
- **Access:** Requires 1 merged T1 bounty
- **Best For:** Feature implementations, integrations

### T3 - Advanced
- **Reward Range:** 500,000 - 1,000,000+ $FNDRY
- **Access:** Requires 3 merged T2 bounties
- **Best For:** Complex features, architectural changes

## Setup Guide

### Step 1: Get SolFoundry API Key

1. Visit [SolFoundry Developer Portal](https://solfoundry.dev/developer)
2. Sign in with your GitHub account
3. Navigate to "API Keys" section
4. Click "Generate New Key"
5. Copy the key (you won't see it again)

### Step 2: Add GitHub Secret

1. Go to your repository on GitHub
2. Click **Settings** → **Secrets and variables** → **Actions**
3. Click **New repository secret**
4. Name: `SOLFOUNDRY_API_KEY`
5. Value: Paste your API key from Step 1
6. Click **Add secret**

### Step 3: Create Bounty Label

1. Go to your repository's **Issues** tab
2. Click **Labels** → **New label**
3. Name: `bounty` (or your preferred label)
4. Color: Choose a color (green recommended)
5. Description: "Issues eligible for SolFoundry bounty"
6. Click **Create label**

### Step 4: Add Workflow

1. Create `.github/workflows/solfoundry-bounties.yml`
2. Copy the [Quick Start](#quick-start) configuration
3. Commit and push to your repository

### Step 5: Test

1. Create a new issue in your repository
2. Add the `bounty` label
3. The workflow should trigger automatically
4. Check the Actions tab to see execution results

## Examples

### Example 1: Simple Bug Bounty

```yaml
name: Bug Bounty
on:
issues:
types: [labeled]

jobs:
bounty:
if: github.event.label.name == 'bug-bounty'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: SolFoundry/solfoundry/actions/bounty-poster@main
with:
solfoundry-api-key: ${{ secrets.SOLFOUNDRY_API_KEY }}
bounty-label: 'bug-bounty'
reward-amount: '75000'
reward-tier: 'T1'
```

### Example 2: Feature Bounty with Webhook

```yaml
name: Feature Bounty
on:
issues:
types: [labeled]

jobs:
bounty:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: SolFoundry/solfoundry/actions/bounty-poster@main
with:
solfoundry-api-key: ${{ secrets.SOLFOUNDRY_API_KEY }}
bounty-label: 'feature-request'
reward-amount: '300000'
reward-tier: 'T2'
custom-webhook: ${{ secrets.DISCORD_WEBHOOK }}
```

### Example 3: Scheduled Bounty Scan

```yaml
name: Daily Bounty Scan
on:
schedule:
- cron: '0 9 * * *' # Every day at 9 AM UTC

jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: SolFoundry/solfoundry/actions/bounty-poster@main
with:
solfoundry-api-key: ${{ secrets.SOLFOUNDRY_API_KEY }}
bounty-label: 'bounty'
reward-amount: '100000'
reward-tier: 'T1'
```

### Example 4: Multi-Tier Bounties

```yaml
name: Multi-Tier Bounties
on:
issues:
types: [labeled]

jobs:
# T1 Bounties - Small fixes
t1-bounty:
if: github.event.label.name == 'bounty-t1'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: SolFoundry/solfoundry/actions/bounty-poster@main
with:
solfoundry-api-key: ${{ secrets.SOLFOUNDRY_API_KEY }}
bounty-label: 'bounty-t1'
reward-amount: '100000'
reward-tier: 'T1'

# T2 Bounties - Features
t2-bounty:
if: github.event.label.name == 'bounty-t2'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: SolFoundry/solfoundry/actions/bounty-poster@main
with:
solfoundry-api-key: ${{ secrets.SOLFOUNDRY_API_KEY }}
bounty-label: 'bounty-t2'
reward-amount: '350000'
reward-tier: 'T2'
```

## Troubleshooting

### Issue: "No issues found with label"

**Cause:** The label doesn't exist or no open issues have the label.

**Solution:**
1. Verify the label exists in your repository
2. Check that issues with the label are open (not closed)
3. Ensure the label name matches exactly (case-sensitive)

### Issue: "API key invalid"

**Cause:** The SolFoundry API key is incorrect or expired.

**Solution:**
1. Verify your `SOLFOUNDRY_API_KEY` secret is set correctly
2. Check that the API key hasn't expired
3. Generate a new key if necessary

### Issue: Action not triggering

**Cause:** Workflow configuration issue.

**Solution:**
1. Verify the workflow file is in `.github/workflows/`
2. Check the YAML syntax is valid
3. Ensure the workflow is enabled in the Actions tab
4. Verify trigger conditions match your use case

### Issue: "Permission denied"

**Cause:** The workflow lacks necessary permissions.

**Solution:**
Ensure your workflow has these permissions:
```yaml
permissions:
issues: read
contents: read
```

## Contributing

Contributions are welcome! Please see our [Contributing Guide](CONTRIBUTING.md) for details.

### Development Setup

1. Fork this repository
2. Clone your fork: `git clone https://github.com/your-username/solfoundry.git`
3. Navigate to the action: `cd actions/bounty-poster`
4. Make your changes
5. Test locally if possible
6. Submit a pull request

### Reporting Issues

If you encounter any issues:
1. Check the [Troubleshooting](#troubleshooting) section
2. Search existing [GitHub Issues](https://github.com/SolFoundry/solfoundry/issues)
3. Create a new issue with:
- Description of the problem
- Steps to reproduce
- Expected vs actual behavior
- Workflow configuration (with sensitive data redacted)

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

---

<p align="center">
Built with ❤️ by the <a href="https://solfoundry.dev">SolFoundry</a> community
</p>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Documentation is comprehensive but describes an incomplete implementation.

The README provides excellent documentation structure including:

  • Clear table of contents (lines 9-22)
  • Step-by-step setup guide (lines 191-231)
  • Multiple usage examples (lines 232-334)
  • Troubleshooting section (lines 335-375)

However, the documentation describes a fully functional bounty-posting action when the actual implementation:

  1. Does not make real API calls (mock only)
  2. Does not populate declared outputs
  3. Does not implement webhook notifications
  4. Will fail due to shell/language mismatch

The documentation quality is good, but it creates a significant expectation gap with the actual functionality.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/README.md` around lines 1 - 409, The README advertises
features that the current action doesn't implement (real API calls, outputs,
webhooks) and even contains a shell/language mismatch; update the README around
the usage, Inputs/Outputs, Features, Quick Start, and Troubleshooting sections
to accurately reflect the current implementation by (1) marking the action as
"experimental / mock implementation" wherever the action is referenced (e.g.,
the usage block "uses: SolFoundry/solfoundry/actions/bounty-poster@main"), (2)
clearly noting that API calls are mocked and webhooks (custom-webhook) are not
implemented, (3) removing or flagging the Outputs table entries `bounty-id` and
`bounty-url` as not yet populated by the action, and (4) adding a short TODO
with next steps and a pointer to the implementation tasks (real API integration,
webhook support, and fixing the shell/language mismatch) so consumers see the
gap between docs and code.

Comment on lines +43 to +44
- ✅ **Webhook Support**: Optional custom webhook notifications
- ✅ **Secure**: Uses GitHub Secrets for API key management
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor: Documentation describes unimplemented webhook feature.

Line 43 lists "Webhook Support: Optional custom webhook notifications" as a feature, and line 109 documents the custom-webhook input. However, this input is not used anywhere in the action implementation.

Users configuring custom-webhook (as shown in Example 2, line 275) will not receive any notifications because the feature is not implemented.

Also applies to: 109-109

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/README.md` around lines 43 - 44, The README advertises
a "Webhook Support" feature and documents an input named custom-webhook that
isn't actually used by the action; either remove the webhook mention and the
custom-webhook input docs from the README (and Example 2) or implement webhook
handling in the action: read the custom-webhook input, validate it, and POST a
notification payload to that URL on bounty events (use existing notification
code paths or the main action entrypoint to send the POST), ensuring errors are
caught and logged; reference the documented input name "custom-webhook" and the
action's main notification flow when making the change.

Comment on lines +152 to +170
## Outputs

| Output | Description |
|--------|-------------|
| `bounty-id` | Unique identifier of the created bounty |
| `bounty-url` | Direct URL to view the bounty on SolFoundry |

### Using Outputs

```yaml
- uses: SolFoundry/solfoundry/actions/bounty-poster@main
id: bounty
with:
solfoundry-api-key: ${{ secrets.SOLFOUNDRY_API_KEY }}

- name: Post comment
run: |
echo "Bounty created: ${{ steps.bounty.outputs.bounty-url }}"
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Major: Documentation describes non-functional outputs.

Lines 154-157 document bounty-id and bounty-url outputs, and lines 161-170 show how to use them. However, as noted in action.yml review, these outputs are declared but never populated by any step in the action.

Users following this documentation (line 169: ${{ steps.bounty.outputs.bounty-url }}) will get empty strings, causing their workflows to fail silently or produce incorrect results.

This documentation must either be updated to reflect actual outputs (count), or the action must be fixed to populate these outputs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/README.md` around lines 152 - 170, The README lists
outputs bounty-id and bounty-url and shows using
steps.bounty.outputs.bounty-url, but the action (action.yml) declares those
outputs without ever populating them, so workflows will get empty values; either
(A) implement population of those outputs in the action runtime (e.g., in the
entrypoint script or JS that makes the POST and handles the response) by
capturing the created bounty's id and URL and emitting them via core.setOutput
or writing to GITHUB_OUTPUT with the exact output names bounty-id and
bounty-url, referencing the code that handles the API response (where the
POST/create bounty occurs), or (B) update the README to remove
bounty-id/bounty-url and show only the actual output (count) and adjust the
example usage to echo steps.bounty.outputs.count instead. Ensure the chosen fix
uses the same output keys as declared in action.yml (bounty-id, bounty-url or
count) so the docs and runtime match.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@actions/bounty-poster/README.md`:
- Around line 64-65: The README's workflow filename is inconsistent with the
repository workflow name; update the README entry that currently says
".github/workflows/solfoundry-bounties.yml" to match the actual workflow
filename ".github/workflows/solfoundry-bounty-poster.yml" (or vice versa if you
intend to rename the workflow file) so the documented filename and the
repository workflow name are identical; ensure the change is made in README.md
where the workflow creation is described.
- Around line 83-92: The README documents workflow_dispatch inputs
(bounty_label, reward_amount) but the actual workflow
(solfoundry-bounty-poster.yml) has a bare workflow_dispatch with no inputs, so
reconcile them by either adding the documented inputs under workflow_dispatch in
solfoundry-bounty-poster.yml (declare bounty_label and reward_amount with
appropriate description/defaults) or removing/adjusting the input usage from
README.md so it matches the current bare workflow_dispatch; update references to
bounty_label and reward_amount wherever they are consumed to reflect the chosen
approach.
- Line 246: The link text "[Contributing Guide](CONTRIBUTING.md)" in the README
is a relative path from actions/bounty-poster/ and therefore broken; update that
link target to point to the repo root CONTRIBUTING file (e.g., change the href
to "../../CONTRIBUTING.md" or use an absolute repository path or full URL) so
the link resolves correctly when viewed from actions/bounty-poster/; edit the
README.md and replace the existing "CONTRIBUTING.md" target accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3953a79d-c3a9-4df1-a535-6ac884c6cbb5

📥 Commits

Reviewing files that changed from the base of the PR and between 52dcf10 and ae010f0.

📒 Files selected for processing (1)
  • actions/bounty-poster/README.md

Comment on lines +64 to +65
Create `.github/workflows/solfoundry-bounties.yml`:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Workflow filename in docs does not match the canonical workflow added in this PR context.

README instructs creating .github/workflows/solfoundry-bounties.yml at Line 64-65, while the repository snippet references .github/workflows/solfoundry-bounty-poster.yml. This inconsistency makes troubleshooting and onboarding harder when users compare docs to repo defaults.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/README.md` around lines 64 - 65, The README's workflow
filename is inconsistent with the repository workflow name; update the README
entry that currently says ".github/workflows/solfoundry-bounties.yml" to match
the actual workflow filename ".github/workflows/solfoundry-bounty-poster.yml"
(or vice versa if you intend to rename the workflow file) so the documented
filename and the repository workflow name are identical; ensure the change is
made in README.md where the workflow creation is described.

Comment on lines +83 to +92
workflow_dispatch:
inputs:
bounty_label:
description: 'Label to scan for bounties'
required: false
default: 'bounty'
reward_amount:
description: 'Reward amount in $FNDRY'
required: false
default: '100000'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Manual-dispatch docs diverge from the repository’s actual workflow trigger schema.

At Line 83-92, the README documents workflow_dispatch.inputs (bounty_label, reward_amount) and then consumes them at Line 114-115.
But the actual workflow context (.github/workflows/solfoundry-bounty-poster.yml, Line 3-8) shows a bare workflow_dispatch: with no inputs. This means the documented/manual-trigger contract in README does not match the checked-in workflow and can mislead operators about configurable run-time parameters.

Also applies to: 114-115

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/README.md` around lines 83 - 92, The README documents
workflow_dispatch inputs (bounty_label, reward_amount) but the actual workflow
(solfoundry-bounty-poster.yml) has a bare workflow_dispatch with no inputs, so
reconcile them by either adding the documented inputs under workflow_dispatch in
solfoundry-bounty-poster.yml (declare bounty_label and reward_amount with
appropriate description/defaults) or removing/adjusting the input usage from
README.md so it matches the current bare workflow_dispatch; update references to
bounty_label and reward_amount wherever they are consumed to reflect the chosen
approach.


## Contributing

Contributions welcome! Please see our [Contributing Guide](CONTRIBUTING.md).
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Relative Contributing Guide link is likely broken from this subdirectory README.

At Line 246, [Contributing Guide](CONTRIBUTING.md) resolves relative to actions/bounty-poster/, not repository root, so it likely points to a non-existent file path for users reading this README in place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/README.md` at line 246, The link text "[Contributing
Guide](CONTRIBUTING.md)" in the README is a relative path from
actions/bounty-poster/ and therefore broken; update that link target to point to
the repo root CONTRIBUTING file (e.g., change the href to
"../../CONTRIBUTING.md" or use an absolute repository path or full URL) so the
link resolves correctly when viewed from actions/bounty-poster/; edit the
README.md and replace the existing "CONTRIBUTING.md" target accordingly.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (14)
actions/bounty-poster/action.yml (8)

148-165: ⚠️ Potential issue | 🟠 Major

Major: Missing GH_TOKEN environment variable for gh CLI authentication.

Line 160 uses gh issue list but the step lacks the required GH_TOKEN or GITHUB_TOKEN environment variable. The gh CLI requires authentication to access repository issues and will fail with an authentication error in most CI contexts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 148 - 165, The "Detect
bounty-eligible issues" step (id: detect) calls `gh issue list` but does not set
GH_TOKEN/GITHUB_TOKEN for the gh CLI; update the step to provide authentication
by exporting or setting the GH_TOKEN (or GITHUB_TOKEN) environment variable
before calling `gh issue list` (e.g., read from inputs or the workflow's
GITHUB_TOKEN) so `gh issue list` can authenticate; ensure the change is applied
within the detect step surrounding the `gh issue list` invocation so the
`issues.json` fetch succeeds.

254-270: ⚠️ Potential issue | 🟠 Major

Major: No deduplication mechanism for bounty posting.

The action processes all open issues with the bounty label on every run (lines 259-266). When triggered by schedule or manual dispatch, previously posted bounties will be reposted, creating duplicates. No mechanism exists to check for existing bounties or mark processed issues.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 254 - 270, The loop in main()
currently posts bounties for every issue in issues unconditionally, causing
duplicates; modify main() to deduplicate by checking for already-processed
issues before calling postBounty: load existing processed IDs from the output
artifact (bounty-results.json) or a persistent store, skip issues whose
issue.number is present, and only call postBounty for new issues; after
successful postBounty (function postBounty), append the result and update/write
bounty-results.json atomically so future runs can detect processed issues;
ensure error handling for file read/write (fs.readFileSync/ fs.writeFileSync
usage around bounty-results.json) and preserve existing results when appending.

109-120: ⚠️ Potential issue | 🔴 Critical

Critical: Declared outputs bounty-id and bounty-url are never populated.

The action declares these outputs (lines 113-120) but no step writes to $GITHUB_OUTPUT with these keys. Only count is set (lines 165, 289, 293). Downstream workflows using ${{ steps.bounty.outputs.bounty-id }} will receive empty values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 109 - 120, The declared
outputs bounty-id and bounty-url are never populated—only count is written—so
downstream steps get empty values; after the bounty is created (where count is
currently set) update the action to emit those outputs by writing the created
bounty ID and URL to $GITHUB_OUTPUT (or setting them as composite action
outputs) using the same mechanism that sets count, i.e., locate the step or
script that produces the bounty response (the code path that currently sets
count) and add writes for bounty-id and bounty-url so the keys declared in
outputs (bounty-id, bounty-url) are actually emitted.

138-143: ⚠️ Potential issue | 🟡 Minor

Minor: Silent failure masking with || true on CLI installation.

Line 143 uses || true which silently ignores all npm installation failures, including legitimate errors (network issues, package unavailable). This makes debugging difficult when the CLI genuinely fails to install.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 138 - 143, Remove the silent
failure masking from the "Install SolFoundry CLI" step: replace the npm install
-g `@solfoundry/cli`@latest || true with a fail-fast approach (remove "|| true")
or explicitly check the exit code and emit a clear error message, so
installation failures (network, package missing, permissions) cause the job to
fail or print diagnostics; update the step labeled "Install SolFoundry CLI" to
either let npm's non-zero exit propagate or run a conditional script that
verifies the install (e.g., npm exit code or solfoundry --version) and logs a
helpful error before exiting non-zero.

86-95: ⚠️ Potential issue | 🟡 Minor

Minor: No validation of reward-tier input value.

Lines 92-95 document that reward-tier accepts T1, T2, or T3, but no validation occurs. Invalid values like "T4" or empty strings pass directly to the bounty payload (line 229) without error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 86 - 95, Validate the
reward-tier input value (the action input named "reward-tier") before adding it
to the bounty payload: when reading getInput('reward-tier') (or equivalent input
retrieval) check it strictly against the allowed set ['T1','T2','T3']; if it is
missing or invalid either set it to the default 'T1' or throw/exit with a clear
error message, and log the decision; apply this validation in the code that
builds the bounty payload (e.g., in the function that assembles the
payload/createBountyPayload/buildBountyPayload) so only validated tier values
are placed into the payload.

97-104: ⚠️ Potential issue | 🟠 Major

Major: custom-webhook input is declared but never used.

Lines 102-104 declare a custom-webhook input for bounty notifications, but no code reads or uses this value. The documented webhook notification feature is not implemented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 97 - 104, The action declares
an input named "custom-webhook" but the runtime never reads or uses it; update
the action runtime (the entrypoint script / main handler that processes bounty
creation) to read the "custom-webhook" input (e.g., via
core.getInput('custom-webhook') or equivalent) and, when a bounty is created,
pass that webhook URL into the existing notification flow (add a parameter to or
call sendBountyNotification/sendNotification/notifyBountyCreated with the
webhook URL), falling back to current/default behavior when the value is empty;
ensure the input name "custom-webhook" is used exactly as declared and that
failure to post to the webhook is logged but does not break the main flow.

220-244: ⚠️ Potential issue | 🟠 Major

Major: No actual SolFoundry API integration - mock implementation only.

Lines 239-243 return synthetic mock data instead of calling the SolFoundry API:

return { 
  id: `bounty-${Date.now()}`, 
  url: `https://solfoundry.dev/bounties/${issue.number}` 
};

The SOLFOUNDRY_API_KEY (line 174) is passed but never used. The installed @solfoundry/cli (line 143) is never invoked. This action will not create actual bounties on SolFoundry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 220 - 244, The postBounty
function currently returns synthetic mock data instead of creating a real
bounty; update postBounty to use the installed `@solfoundry/cli` client and the
SOLFOUNDRY_API_KEY from process.env to call the SolFoundry API: import or
require the client, instantiate it with the API key, map bountyData to the
client method (e.g., createBounty/createIssue) and await the API call, handle
and log errors (throw or return failure metadata on error), and finally return
the actual response fields (id, url) from the SolFoundry API instead of the mock
object; ensure labels and sourceUrl are passed in the payload and remove the
synthetic return.

170-277: ⚠️ Potential issue | 🔴 Critical

Critical: Node.js code in bash shell will cause immediate runtime failure.

Lines 170-277 define a step with shell: bash but the run block contains JavaScript/Node.js code (starting with #!/usr/bin/env node at line 178, then const fs = require('fs'); at line 187, async functions, etc.). Bash will attempt to interpret this as shell commands, causing syntax errors and immediate failure.

This is a fundamental blocker that prevents the action from functioning at all.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/action.yml` around lines 170 - 277, The run block
contains Node.js code but the step uses shell: bash, causing shell syntax
errors; fix by moving the JS payload into a real Node script and invoking it
with node (or install Node via actions/setup-node then run node script). Extract
the JS (including the shebang, requires, and functions postBounty and main) into
a file (e.g., scripts/post-bounties.js), keep the env usage (REWARD_AMOUNT,
REWARD_TIER, GITHUB_REPOSITORY) as-is, and change the action step to run "node
scripts/post-bounties.js" (after adding a setup-node step) so the async
functions and JSON file operations execute under Node rather than Bash.
actions/bounty-poster/README.md (6)

131-137: ⚠️ Potential issue | 🟠 Major

Major: Documentation describes non-functional outputs.

Lines 134-136 document bounty-id and bounty-url outputs that are never populated by the action. Users following this documentation will receive empty strings, causing workflows to fail silently or produce incorrect results.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/README.md` around lines 131 - 137, The README documents
outputs `bounty-id` and `bounty-url` but the action never sets those outputs;
either remove these outputs from the README or update the action implementation
to emit them. To fix, locate the action implementation (the action entrypoint /
script that runs the workflow) and add logic to set the outputs (e.g., using
core.setOutput('bounty-id', id) and core.setOutput('bounty-url', url) in the
code that creates the bounty, or declare corresponding outputs in a
composite/action.yml and wire the values through), or alternatively delete the
`bounty-id` and `bounty-url` rows from the README Outputs table so docs match
the actual behavior.

83-92: ⚠️ Potential issue | 🟠 Major

Major: Manual-dispatch docs diverge from actual workflow.

Lines 83-92 document workflow_dispatch inputs (bounty_label, reward_amount), but the actual workflow at .github/workflows/solfoundry-bounty-poster.yml has a bare workflow_dispatch: with no inputs defined. The documented manual-trigger contract does not match the checked-in workflow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/README.md` around lines 83 - 92, Docs describe
workflow_dispatch inputs bounty_label and reward_amount but the actual workflow
has no inputs; either update the workflow to declare those inputs under
workflow_dispatch or remove/adjust the README to match. Fix by editing the
workflow_dispatch block in the workflow to include inputs: bounty_label (default
'bounty') and reward_amount (default '100000') matching the README, or alter the
README to remove the inputs section and document that the workflow has no manual
inputs; ensure the symbols workflow_dispatch, bounty_label and reward_amount are
consistent between the README and the workflow.

64-65: ⚠️ Potential issue | 🟡 Minor

Minor: Workflow filename inconsistency.

Line 64 instructs creating .github/workflows/solfoundry-bounties.yml, while the actual repository workflow is .github/workflows/solfoundry-bounty-poster.yml. This makes troubleshooting harder when users compare docs to repo defaults.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/README.md` around lines 64 - 65, The README currently
instructs creating `.github/workflows/solfoundry-bounties.yml` but the actual
workflow in the repo is named `.github/workflows/solfoundry-bounty-poster.yml`;
update the text in the README (the line that reads "Create
`.github/workflows/solfoundry-bounties.yml`") to reference
`.github/workflows/solfoundry-bounty-poster.yml` (or alternatively rename the
workflow file in the repo to match the README), and verify any other README
references to the workflow name for consistency.

10-27: ⚠️ Potential issue | 🟠 Major

Major: Documentation creates significant expectation gap with implementation.

The README describes a fully functional bounty-posting action, but the actual implementation:

  1. Does not make real API calls (mock only)
  2. Does not populate declared outputs (bounty-id, bounty-url)
  3. Does not implement webhook notifications
  4. Will fail at runtime due to shell/language mismatch

The documentation quality is good structurally, but it misrepresents the current functionality.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/README.md` around lines 10 - 27, The README overstates
features that aren't implemented: implement real API calls (replace mocks like
createBounty/createBountyMock with a real HTTP client function createBounty that
posts to SolFoundry and returns id/url), ensure action metadata (action.yml)
declares outputs bounty-id and bounty-url and set them from the entrypoint (use
GITHUB_OUTPUT or core.setOutput in the runtime used), add optional webhook
support by implementing a postToWebhook(url, payload) helper and calling it when
configured, and fix the runtime/shell mismatch by aligning the entrypoint script
(entrypoint.sh or index.js) with the action.yml runs specification (use
node10/ts build or proper shell script) so the action doesn't fail at runtime.

25-26: ⚠️ Potential issue | 🟡 Minor

Minor: Documentation describes unimplemented webhook feature.

Line 25 lists "Webhook Notifications: Optional custom webhook for bounty notifications" as a feature, and line 129 documents the custom-webhook input. However, this input is never used in the action implementation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/README.md` around lines 25 - 26, The README advertises
"Webhook Notifications" but the documented input custom-webhook is unused;
either remove the webhook feature text and the custom-webhook input from
documentation/metadata, or implement it by reading the custom-webhook input
(e.g., core.getInput('custom-webhook') or process.env['INPUT_CUSTOM-WEBHOOK'])
and invoking the existing notification logic when posting bounties (add a call
to the module/function that sends notifications—e.g., the function that
currently posts bounty issues or sends emails—so it also POSTs to the provided
webhook URL, handling empty input by no-op and logging errors).

246-246: ⚠️ Potential issue | 🟡 Minor

Minor: Relative Contributing Guide link is broken.

Line 246's [Contributing Guide](CONTRIBUTING.md) resolves relative to actions/bounty-poster/, not repository root, pointing to a non-existent file path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@actions/bounty-poster/README.md` at line 246, Update the link target in
actions/bounty-poster/README.md for the link text "[Contributing
Guide](CONTRIBUTING.md)" so it points to the repository-root CONTRIBUTING.md
instead of a path relative to actions/bounty-poster; replace the current
relative target with a repository-root-resolving form (for example, an absolute
repo-root link or correct relative path up to the repo root) so the link
resolves correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@actions/bounty-poster/action.yml`:
- Around line 148-165: The "Detect bounty-eligible issues" step (id: detect)
calls `gh issue list` but does not set GH_TOKEN/GITHUB_TOKEN for the gh CLI;
update the step to provide authentication by exporting or setting the GH_TOKEN
(or GITHUB_TOKEN) environment variable before calling `gh issue list` (e.g.,
read from inputs or the workflow's GITHUB_TOKEN) so `gh issue list` can
authenticate; ensure the change is applied within the detect step surrounding
the `gh issue list` invocation so the `issues.json` fetch succeeds.
- Around line 254-270: The loop in main() currently posts bounties for every
issue in issues unconditionally, causing duplicates; modify main() to
deduplicate by checking for already-processed issues before calling postBounty:
load existing processed IDs from the output artifact (bounty-results.json) or a
persistent store, skip issues whose issue.number is present, and only call
postBounty for new issues; after successful postBounty (function postBounty),
append the result and update/write bounty-results.json atomically so future runs
can detect processed issues; ensure error handling for file read/write
(fs.readFileSync/ fs.writeFileSync usage around bounty-results.json) and
preserve existing results when appending.
- Around line 109-120: The declared outputs bounty-id and bounty-url are never
populated—only count is written—so downstream steps get empty values; after the
bounty is created (where count is currently set) update the action to emit those
outputs by writing the created bounty ID and URL to $GITHUB_OUTPUT (or setting
them as composite action outputs) using the same mechanism that sets count,
i.e., locate the step or script that produces the bounty response (the code path
that currently sets count) and add writes for bounty-id and bounty-url so the
keys declared in outputs (bounty-id, bounty-url) are actually emitted.
- Around line 138-143: Remove the silent failure masking from the "Install
SolFoundry CLI" step: replace the npm install -g `@solfoundry/cli`@latest || true
with a fail-fast approach (remove "|| true") or explicitly check the exit code
and emit a clear error message, so installation failures (network, package
missing, permissions) cause the job to fail or print diagnostics; update the
step labeled "Install SolFoundry CLI" to either let npm's non-zero exit
propagate or run a conditional script that verifies the install (e.g., npm exit
code or solfoundry --version) and logs a helpful error before exiting non-zero.
- Around line 86-95: Validate the reward-tier input value (the action input
named "reward-tier") before adding it to the bounty payload: when reading
getInput('reward-tier') (or equivalent input retrieval) check it strictly
against the allowed set ['T1','T2','T3']; if it is missing or invalid either set
it to the default 'T1' or throw/exit with a clear error message, and log the
decision; apply this validation in the code that builds the bounty payload
(e.g., in the function that assembles the
payload/createBountyPayload/buildBountyPayload) so only validated tier values
are placed into the payload.
- Around line 97-104: The action declares an input named "custom-webhook" but
the runtime never reads or uses it; update the action runtime (the entrypoint
script / main handler that processes bounty creation) to read the
"custom-webhook" input (e.g., via core.getInput('custom-webhook') or equivalent)
and, when a bounty is created, pass that webhook URL into the existing
notification flow (add a parameter to or call
sendBountyNotification/sendNotification/notifyBountyCreated with the webhook
URL), falling back to current/default behavior when the value is empty; ensure
the input name "custom-webhook" is used exactly as declared and that failure to
post to the webhook is logged but does not break the main flow.
- Around line 220-244: The postBounty function currently returns synthetic mock
data instead of creating a real bounty; update postBounty to use the installed
`@solfoundry/cli` client and the SOLFOUNDRY_API_KEY from process.env to call the
SolFoundry API: import or require the client, instantiate it with the API key,
map bountyData to the client method (e.g., createBounty/createIssue) and await
the API call, handle and log errors (throw or return failure metadata on error),
and finally return the actual response fields (id, url) from the SolFoundry API
instead of the mock object; ensure labels and sourceUrl are passed in the
payload and remove the synthetic return.
- Around line 170-277: The run block contains Node.js code but the step uses
shell: bash, causing shell syntax errors; fix by moving the JS payload into a
real Node script and invoking it with node (or install Node via
actions/setup-node then run node script). Extract the JS (including the shebang,
requires, and functions postBounty and main) into a file (e.g.,
scripts/post-bounties.js), keep the env usage (REWARD_AMOUNT, REWARD_TIER,
GITHUB_REPOSITORY) as-is, and change the action step to run "node
scripts/post-bounties.js" (after adding a setup-node step) so the async
functions and JSON file operations execute under Node rather than Bash.

In `@actions/bounty-poster/README.md`:
- Around line 131-137: The README documents outputs `bounty-id` and `bounty-url`
but the action never sets those outputs; either remove these outputs from the
README or update the action implementation to emit them. To fix, locate the
action implementation (the action entrypoint / script that runs the workflow)
and add logic to set the outputs (e.g., using core.setOutput('bounty-id', id)
and core.setOutput('bounty-url', url) in the code that creates the bounty, or
declare corresponding outputs in a composite/action.yml and wire the values
through), or alternatively delete the `bounty-id` and `bounty-url` rows from the
README Outputs table so docs match the actual behavior.
- Around line 83-92: Docs describe workflow_dispatch inputs bounty_label and
reward_amount but the actual workflow has no inputs; either update the workflow
to declare those inputs under workflow_dispatch or remove/adjust the README to
match. Fix by editing the workflow_dispatch block in the workflow to include
inputs: bounty_label (default 'bounty') and reward_amount (default '100000')
matching the README, or alter the README to remove the inputs section and
document that the workflow has no manual inputs; ensure the symbols
workflow_dispatch, bounty_label and reward_amount are consistent between the
README and the workflow.
- Around line 64-65: The README currently instructs creating
`.github/workflows/solfoundry-bounties.yml` but the actual workflow in the repo
is named `.github/workflows/solfoundry-bounty-poster.yml`; update the text in
the README (the line that reads "Create
`.github/workflows/solfoundry-bounties.yml`") to reference
`.github/workflows/solfoundry-bounty-poster.yml` (or alternatively rename the
workflow file in the repo to match the README), and verify any other README
references to the workflow name for consistency.
- Around line 10-27: The README overstates features that aren't implemented:
implement real API calls (replace mocks like createBounty/createBountyMock with
a real HTTP client function createBounty that posts to SolFoundry and returns
id/url), ensure action metadata (action.yml) declares outputs bounty-id and
bounty-url and set them from the entrypoint (use GITHUB_OUTPUT or core.setOutput
in the runtime used), add optional webhook support by implementing a
postToWebhook(url, payload) helper and calling it when configured, and fix the
runtime/shell mismatch by aligning the entrypoint script (entrypoint.sh or
index.js) with the action.yml runs specification (use node10/ts build or proper
shell script) so the action doesn't fail at runtime.
- Around line 25-26: The README advertises "Webhook Notifications" but the
documented input custom-webhook is unused; either remove the webhook feature
text and the custom-webhook input from documentation/metadata, or implement it
by reading the custom-webhook input (e.g., core.getInput('custom-webhook') or
process.env['INPUT_CUSTOM-WEBHOOK']) and invoking the existing notification
logic when posting bounties (add a call to the module/function that sends
notifications—e.g., the function that currently posts bounty issues or sends
emails—so it also POSTs to the provided webhook URL, handling empty input by
no-op and logging errors).
- Line 246: Update the link target in actions/bounty-poster/README.md for the
link text "[Contributing Guide](CONTRIBUTING.md)" so it points to the
repository-root CONTRIBUTING.md instead of a path relative to
actions/bounty-poster; replace the current relative target with a
repository-root-resolving form (for example, an absolute repo-root link or
correct relative path up to the repo root) so the link resolves correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ca354cce-70f1-403e-bf3f-1128b61f4278

📥 Commits

Reviewing files that changed from the base of the PR and between ae010f0 and bdbbc65.

📒 Files selected for processing (3)
  • .github/workflows/solfoundry-bounty-poster.yml
  • actions/bounty-poster/README.md
  • actions/bounty-poster/action.yml

davidweb3-ctrl added 10 commits April 5, 2026 04:23
SolFoundry#855)

- Create composite action for bounty posting
- Add README with setup instructions
- Include example workflow with multiple trigger options
- Support customizable reward tiers and amounts
- Label-based bounty detection

Closes SolFoundry#855
- Add detailed docstrings to action.yml (inline comments)
- Expand README with full usage guide, examples, troubleshooting
- Include Table of Contents for easy navigation
- Add setup guide with step-by-step instructions
- Include multiple usage examples
- Document all inputs, outputs, and bounty tiers

Addresses CodeRabbit docstring coverage warning
- Add detailed JSDoc comments to all functions and variables
- Document all inputs, outputs, and configuration options
- Add file-level documentation headers
- Include usage examples and troubleshooting guide
- Add security considerations section

Improves docstring coverage from 53% toward 80% target.
- Replace mock implementation with real API call structure
- Add deduplication using .solfoundry-bounties-posted.json tracking file
- Fix output definitions with proper GITHUB_OUTPUT usage
- Add github-token input for GitHub API authentication
- Add dry-run mode for testing
- Improve error handling and reporting

Fixes issues identified in CodeRabbit review.
…rust-toolchain

- Create backend/ directory with minimal FastAPI structure for CI
- Create frontend/src/lib/animations.ts and utils.ts (missing imports)
- Update contracts/rust-toolchain.toml from 1.76 to 1.85 to match CI
…tion

- Update RUST_VERSION from 1.79 to 1.85 (cargo-audit requirement)
- Replace invalid metadaoproject/setup-solana@v1 with manual Solana CLI install
- Fix bounty-registry Anchor.toml TOML parse error (invalid section)
- Add idl-build feature to staking and bounty-registry programs
- Use --locked flag for cargo-audit installation
- Fix workflow filename reference (solfoundry-bounty-poster.yml)
- Correct outputs table (bounty-ids, bounty-urls, posted-count, skipped-count)
- Remove unimplemented custom-webhook feature from docs
- Fix Contributing Guide link to use correct relative path
- Fix npm install error handling (remove || true, add explicit check)
- Add reward tier validation (T1/T2/T3)
- Implement real SolFoundry API integration via HTTPS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

missing-wallet PR is missing a Solana wallet for bounty payout

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant