DEV Community

Daniel Ioni
Daniel Ioni

Posted on

How We Built a Safe GitHub Bounty Lifecycle for MyZubster

How We Built a Safe GitHub Bounty Lifecycle for MyZubster

MyZubster is evolving into a distributed ecosystem of repositories, services, automation, hardware projects, AI components, and contributor workflows.

As the number of repositories and contributors increased, one problem became increasingly important:

How do we automate bounty workflows without accidentally treating a GitHub event as proof of payment, verification, or settlement?

We recently completed an important part of that architecture: a real-time GitHub bounty lifecycle system.

And we tested it end-to-end.


The lifecycle

We use an explicit bounty lifecycle instead of assuming that an issue, pull request, or merge means a bounty has been completed.

The lifecycle is roughly:


text
PROPOSED
  ↓
VALIDATED
  ↓
APPROVED
  ↓
FUNDED
  ↓
ACTIVE
  ↓
SUBMITTED
  ↓
UNDER_REVIEW
  ↓
VERIFIED
  ↓
REWARD_RECORDED
  ↓
SETTLEMENT_PENDING
  ↓
SETTLED
The important part is that GitHub automation only controls a limited part of this flow.

Today, GitHub can automatically move a bounty through:

APPROVED
   ↓ assignment
ACTIVE
   ↓ linked PR
SUBMITTED
   ↓ review
UNDER_REVIEW

And then automation stops.

GitHub Webhooks Across the Ecosystem

We configured repository webhooks across 17 first-party MyZubster repositories.

The subscribed events are:

issues
pull_request
pull_request_review

The central endpoint is:

POST /api/github-bounties/webhook

The backend is Node.js / Express and validates GitHub webhook signatures using:

X-Hub-Signature-256

with an HMAC-SHA256 secret.

Unsigned requests are rejected.

For example:

POST /api/github-bounties/webhook
→ HTTP 401

while valid GitHub webhook deliveries receive a normal application response.

A Useful Production Bug: PM2 Had a Stale Secret

One of the most interesting parts of the deployment was a real production debugging problem.

GitHub was delivering webhook events correctly, but every delivery returned:

401 Unauthorized

Cloudflare was healthy.

The public API was healthy.

The webhook route was healthy.

GitHub deliveries were reaching the server.

But signature verification failed.

We eventually compared the webhook secret loaded from .env with the value injected into the running PM2 process.

The result:

dotenvSecret: SET
pm2InjectedSecret: SET
sameSecret: false

That was the bug.

PM2 was still running with an older environment variable.

After restarting the application with the updated environment:

pm2 restart myzubster --update-env

GitHub's webhook ping changed to:

code: 200
status: active
message: OK

A small configuration mismatch had effectively disabled the entire real-time automation layer.

This was also a useful reminder:

Secrets are not only a storage problem. They are also a process lifecycle problem.

End-to-End Test

Once the webhook secret was synchronized, we ran a real GitHub lifecycle test.

We created a temporary bounty with:

type:bounty
status:approved
reward:none
evidence:required

Using reward:none was intentional: the test should exercise lifecycle automation without involving any financial state.

Step 1 — Assignment

The issue was assigned to a contributor.

GitHub sent:

issues.assigned

The backend changed:

status:approved

to:

status:active

Success.

Step 2 — Pull Request

We created a non-draft pull request containing:

Fixes #523

The webhook processor detected the linked bounty and changed:

status:active

to:

status:submitted

Success.

Step 3 — Review

A pull request review was submitted.

The bounty changed from:

status:submitted

to:

status:review

which is normalized by the backend as:

under_review

Success.

The complete real-time path was therefore validated:

APPROVED
   ↓
ACTIVE
   ↓
SUBMITTED
   ↓
UNDER_REVIEW
What Does NOT Happen Automatically

This is probably the most important design decision.

A GitHub event does not automatically produce:

status:verified
status:reward-recorded
settlement:*

A merge does not automatically mean:

bounty verified

and it definitely does not mean:

payment completed

The system does not treat repository activity as financial evidence.

MYZ Is an Internal Platform Ledger Unit

Another distinction we make explicitly:

MYZ is currently an internal platform accounting/reward unit.

We do not describe current MYZ rewards as blockchain payouts.

Likewise, external assets such as XMR or other tokens require independent settlement verification before they can be considered paid or settled.

This means:

GitHub merge
    ≠ bounty verification


bounty verification
    ≠ reward recording


reward recording
    ≠ external settlement

Keeping those concepts separate makes the automation much safer.

GitHub as Source of Workflow Truth

Lifecycle labels are intentionally explicit.

Examples include:

status:proposed
status:validated
status:approved
status:funded
status:active
status:submitted
status:review
status:verified
status:reward-recorded

Reward declarations are also explicit:

reward:myz
reward:xmr
reward:token
reward:none

Review policies can include:

review:manual
review:multi

and sensitive work can be marked:

sensitivity:high

This allows automation to reason from explicit repository state instead of guessing.

Next Step: Safe Auto-Merge

The next evolution is already in the MyZubster roadmap:

safe auto-merge for eligible bounty pull requests.

But this will not be a generic "bot approves and merges everything" system.

An eligible PR should require conditions such as:

CI green
required checks complete
minimum approved reviews satisfied
no requested changes
no blocking labels
no sensitivity:high
no review:manual

For multi-review bounties:

review:multi

we plan to require at least:

2 independent approvals

before automatic merge becomes possible.

Branch protection and repository rulesets should provide another enforcement layer.

Merge Still Does Not Mean Payment

Even after safe auto-merge is implemented, the architecture will keep the same hard boundary:

AUTO MERGE
    ↓
code integrated
    ↓
STOP

It must not automatically perform:

VERIFIED
REWARD_RECORDED
SETTLED

Those remain separate evidence and financial operations.

In other words:

Merge != verification != reward != settlement.

Why This Matters

Automation is easy when everything is treated as the same event.

The dangerous version looks like this:

PR merged
   ↓
bounty complete
   ↓
credit reward
   ↓
mark paid

It is convenient.

It is also very difficult to audit safely.

We prefer:

GitHub activity
      ↓
workflow state
      ↓
review / evidence
      ↓
verification
      ↓
reward accounting
      ↓
independent settlement verification

It is more explicit, but every transition has a meaning.

Current Status

At this point we have validated:

GitHub webhook connectivity through Cloudflare
HMAC webhook authentication
repository webhook deployment across 17 first-party repositories
GitHub issue assignment → active
linked pull request → submitted
pull request review → under_review
protection against automatic verification
protection against automatic reward recording
protection against automatic settlement

The temporary E2E test artifacts were then closed and cleaned up without merging the test PR.

What's Next

The next milestones for this part of MyZubster are:

1. Safe CI + review gated auto-merge
2. Unified bounty board
3. Real multi-review / Super Bounty backend
4. Explicit MYZ internal reward ledger integration
5. Signed public history / evidence snapshots
6. Independent settlement verification

The goal is not maximum automation.

The goal is automation with explicit trust boundaries.

If you're building GitHub-based bounty systems, contributor economies, autonomous agents, or open-source reward infrastructure, I'd be very interested in comparing architectures.

How much of the workflow would you automate before requiring a human or independent verification step?

#opensource #github #automation #nodejs
Enter fullscreen mode Exit fullscreen mode

Top comments (0)