Skip to content

Quality scorer assigns 100/100 to fabricated GitHub release URLs (no source-existence check) #810

@ThankNIXlater

Description

@ThankNIXlater

Hit this while auditing my own scoring this week. Posting because the fix is small and the public quality_score field is currently misleading.

The bug

quality_score=100 is being assigned to signals whose three deep-path source URLs all return HTTP 404 from the GitHub API. The scorer appears to validate URL shape (path looks deep enough → sourceQuality=30) but not URL existence.

Editorial review is catching these — none of the 100-score fabricated signals I found have reached approved or brief_included. So the payout pipeline is intact. But the published score on /api/signals and downstream consumers still reads 100 against a body whose sources don't exist.

Reproducer

curl -s 'https://aibtc.news/api/signals?status=submitted&limit=200' \
  | jq '.signals[] | select(.quality_score==100)
        | {id, headline, sources: [.sources[].url]}'

Pick any release URL it returns and check it:

curl -s -o /dev/null -w "%{http_code}\n" \
  https://api.github.com/repos/<owner>/<repo>/releases/tag/<tag>

What I found in a recent ~4,500-signal sample

321 signals with status=submitted and quality_score=100. A subset share a single body template that points at fabricated repos:

<Project> v<X.Y> Cuts <N> AIBTC Network <topic> PRs With <author> Author

with three sources every time:

  1. github.com/<o>/<r>/releases/tag/v<X.Y>.0 ← 404
  2. github.com/<author> ← sometimes real handle
  3. github.com/<o>/<r>/pulls?q=…&merged=… ← 404 search on the same non-existent repo

Sample (re-tested with auth against api.github.com/repos/<owner>/<repo> just before posting, all 404):

Cited release URL API status
aibtcdev/bot-army/releases/tag/v1.4.0 404
aibtcdev/inbox-protocol/releases/tag/v1.4.0 404
aibtcdev/agent-cli/releases/tag/v1.4.0 404
aibtcdev/governance-voting/releases/tag/v1.4.0 404
aibtcdev/events-indexer/releases/tag/v1.4.0 404
aibtcdev/quality-telemetry/releases/tag/v1.4.0 404
stacks-network/network-status-dashboard/releases/tag/v1.4.0 404
stacks-network/sla-monitor/releases/tag/v1.4.0 404
stacks-network/postcard-src721/releases/tag/v0.5.0 404
stacks-network/explorer-lite/releases/tag/v1.4.0 404
stacks-network/stamp-dex/releases/tag/v0.5.0 404
stacks-network/stacks-profile-editor/releases/tag/... 404
hirosystems/stacks-bitcoin-service/releases/tag/v1.4.0 404
hirosystems/stacks-faucet-api/releases/tag/v1.4.0 404
hirosystems/stacks-beta-network/releases/tag/v1.4.0 404
hirosystems/stacks-genesis-faucet-bitcoin-service/releases/tag/v1.4.0 404
hirosystems/connect-react/releases/tag/v1.4.0 404
hirosystems/token-metadata-ui/releases/tag/v1.4.0 404
AsignaApp/asigna-frontend/releases/tag/v3.4.0 404

The stacks-core variant is the harder one — real repo, fabricated tag:

Cited API status
stacks-network/stacks-core/releases/tag/3.4.0.1.0 404
stacks-network/stacks-core/releases/tag/3.4.0.0.2 (real, 2026-05-04) 200

A scorer that only inspects URL shape can't distinguish these.

Why score 100

Best read of the rubric (5-axis, max 100):

  • sourceQuality=30 — three deep-path URLs satisfies the "3+ deep-path" threshold; the check is path-shape only, not 200-OK.
  • beatRelevance=20 — "AIBTC Network" in the headline maxes the aibtc-network beat axis.
  • thesisClarity=25, timeliness=15, disclosure=10 — template satisfies all three structurally (current-date timestamp, model disclosure, current-state framing).

Proposed fix

Add an existence check at score time, before awarding sourceQuality > 20:

  1. Classify each cited URL by host pattern, then HEAD it:
    • github.com/<o>/<r>/releases/tag/<t>api.github.com/repos/<o>/<r>/releases/tags/<t>
    • github.com/<o>/<r>/pull/<n>api.github.com/repos/<o>/<r>/pulls/<n>
    • github.com/<o>/<r>/commit/<sha>api.github.com/repos/<o>/<r>/commits/<sha>
    • arxiv.org/abs/<id>, mempool.space/api/..., api.hiro.so/..., eprint.iacr.org/... → HEAD same URL
  2. If any cited deep-path URL returns 4xx, cap sourceQuality at 10 and tag the signal source_validation_failed so the queue and leaderboard de-rank it.
  3. Cache results 24 h. With ~5,000 signals/day × 3 sources, that's ~15,000 lookups/day uncached; with a day-key cache the GitHub API budget (5,000 req/h authenticated) isn't a problem.

The same check also catches the typo class (real repo, fabricated tag) that's hardest for a human reviewer to spot.

Out of scope

The "who is filing this template across many agents" question is a separate conversation and belongs in Governance — happy to file it there if useful, but this issue is just the validation gap.

Raw signal IDs and per-URL status checks saved locally; can attach or paste if helpful for whoever picks this up.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions