惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 【当耐特】
云风的 BLOG
云风的 BLOG
罗磊的独立博客
C
Check Point Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Blog — PlanetScale
Blog — PlanetScale
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
月光博客
月光博客
大猫的无限游戏
大猫的无限游戏
Google DeepMind News
Google DeepMind News
Engineering at Meta
Engineering at Meta
N
Netflix TechBlog - Medium
宝玉的分享
宝玉的分享
Recent Announcements
Recent Announcements
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园_首页
J
Java Code Geeks
Apple Machine Learning Research
Apple Machine Learning Research
人人都是产品经理
人人都是产品经理
爱范儿
爱范儿
I
InfoQ
Hugging Face - Blog
Hugging Face - Blog
T
Tailwind CSS Blog
B
Blog RSS Feed

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
When GitHub Actions Goes Silent: The Pending-Forever Bug ...
אחיה כהן · 2026-04-29 · via DEV Community

I have an open-source MCP server. I tag a release, push, GitHub Actions builds, npm publishes, MCP Registry updates. That's the contract. It worked for v2.7.6 through v2.8.4.

Then v2.8.5 didn't publish. Neither did v2.8.6. Or v2.9.0. Or v2.9.1. Or v2.9.2. Or v2.9.3.

Six releases stuck. Not failing — stuck. Yellow dot. Forever.

Here's what was actually happening. And how I got the releases out without GitHub Actions.

The symptom that doesn't match any docs

Every release event triggered the workflow. Every workflow showed up in the runs list. None of them ever started a job.

$ gh run view 25001890100 --json status,conclusion,jobs
{
  "status": "queued",
  "conclusion": null,
  "jobs": []
}

Enter fullscreen mode Exit fullscreen mode

No conclusion. No jobs. Empty pending_deployments. Not "waiting for approval". Not "in_progress". Not "failure". Just pending with no work scheduled — for 125 hours.

If you search "GitHub Actions stuck pending", you'll find a hundred forum posts. Every answer assumes one of:

  1. You hit the runner concurrency limit (3 for free-tier macos)
  2. You have a deployment environment requiring approval
  3. Your runs-on: label is unreachable
  4. You're using self-hosted runners with no online agents

None of those applied. My workflow was simple, no environments with required reviewers, runs-on: macos-latest, no self-hosted runners.

The thing GitHub doesn't tell you in the run UI

The runs list shows pending. The run detail page shows pending. The job list shows nothing. The "deployment" tab shows nothing.

But if you look at your billing dashboard, there's a different story:

Your account has used 100% of included macOS minutes for this billing period.

That's it. That's the entire diagnostic. There is no banner on the run page. The workflow doesn't fail with a clear error. It just sits in the queue forever — because the runner that would pick it up doesn't exist, and the queue doesn't time out events.

The minutes counter resets monthly. Until it does, every release event becomes another silent pending row.

Two facts that surprised me

Fact 1: macOS runners cost 10x more than Linux runners. Both runs-on: macos-latest and runs-on: macos-13 charge against your Actions minutes at a 10x multiplier. The free 2,000 minutes/month gets you 200 minutes of macOS — about 20 release builds if each takes 10 minutes.

Fact 2: Switching to Linux didn't fix it. I changed runs-on: macos-latest to runs-on: ubuntu-latest. Same symptom. 0 jobs queued, status "pending". Why?

The macOS minutes meter is one bucket. The Linux meter is another. When the macOS bucket emptied, my pending macOS runs were still in the queue, blocking new runs. Even after switching the workflow to ubuntu, the concurrency group in the YAML serialized everything:

concurrency:
  group: publish
  cancel-in-progress: false

Enter fullscreen mode Exit fullscreen mode

So new ubuntu runs queued behind old stuck macOS runs and never started.

The two-part fix

Part 1: workflow_dispatch with tag input

Adding a manual trigger lets you re-publish a tag whose release-event run got stuck, without deleting and recreating the GitHub Release:

on:
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      tag:
        description: "Tag to publish (e.g. v2.9.3)"
        required: true
        type: string

Enter fullscreen mode Exit fullscreen mode

In every step that needs the tag, fall back through both event types:

- uses: actions/checkout@v6
  with:
    ref: ${{ github.event.inputs.tag || github.ref_name }}

Enter fullscreen mode Exit fullscreen mode

That alone isn't enough — if the runner pool is still empty, the dispatched run also stalls. But it gives you a clean re-trigger path the moment runners are back.

Part 2: portable runner OS

The workflow downloaded mcp-publisher_darwin_${ARCH}.tar.gz — hardcoded "darwin". Switching to ubuntu broke that step. Generalize:

- name: Download mcp-publisher
  run: |
    OS=$(uname -s | tr '[:upper:]' '[:lower:]')
    ARCH=$(uname -m)
    if [ "$ARCH" = "x86_64" ]; then ARCH=amd64; fi
    if [ "$ARCH" = "aarch64" ]; then ARCH=arm64; fi
    curl -sL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_${OS}_${ARCH}.tar.gz" -o mcp-publisher.tar.gz
    tar -xzf mcp-publisher.tar.gz mcp-publisher

Enter fullscreen mode Exit fullscreen mode

Now the same step works on macOS-arm64, macOS-x86_64, ubuntu-x86_64, and any future runner.

The manual workaround that actually shipped the release

While the workflow stays stuck, here's how I got v2.9.3 to npm and the MCP Registry from my laptop:

npm: the easy part

git checkout v2.9.3
npm publish --provenance --access public

Enter fullscreen mode Exit fullscreen mode

--provenance requires a valid OIDC token, which only works inside GitHub Actions. Skip it locally:

npm publish --access public

Enter fullscreen mode Exit fullscreen mode

You lose the provenance attestation, but the package ships. Provenance is a nice-to-have, not a publish blocker.

MCP Registry: the trickier part

The MCP Registry's CLI authenticates interactively:

mcp-publisher login github
# Opens a browser, asks you to paste a code, etc.

Enter fullscreen mode Exit fullscreen mode

That's fine for humans. For a script — or for a Claude session running headless — you need non-interactive auth. The mcp-publisher binary accepts -token:

GH_TOKEN=$(gh auth token)
mcp-publisher login github -token "$GH_TOKEN"
mcp-publisher publish

Enter fullscreen mode Exit fullscreen mode

The gh CLI you already use for everything else? Its token works as your GitHub PAT for mcp-publisher. No browser, no copy-paste.

After running these, the MCP Registry's io.github.achiya-automation/safari-mcp v2.9.3 went from "stuck on v2.7.6 for 3 weeks" to isLatest: true in about 15 seconds.

What I'd tell past-me

  1. Check the billing dashboard *first* when an Actions run sits pending with no error. The run UI does not surface "you're out of minutes". The billing page does.
  2. Don't trust runs-on: ubuntu-latest to "just be cheaper" — it is, but if you've burned your macOS minutes on stalled runs, the queue can still serialize new ones behind dead ones via your concurrency: group.
  3. Keep a manual publish path documented. Both npm and the MCP Registry have non-interactive auth options. Write the bash one-liners somewhere your future self can find them at 2am.
  4. workflow_dispatch with a tag input is cheap insurance. It costs you 6 lines of YAML and saves you from needing to delete-and-recreate GitHub Releases when the release-event run gets corrupted.

FAQ

Why didn't a timeout-minutes: rescue me?
That's a job-level timeout. It applies once a job starts. A run that never starts a job has nothing to time out.

Couldn't I have used a self-hosted runner?
Yes — and that's the right answer for high-volume projects. For an OSS hobby project, self-hosted is operationally heavier than the manual publish path.

Doesn't --provenance matter for supply-chain security?
For widely-installed packages, yes. For an OSS project's own emergency-publish workaround, the trade-off is "ship the release without provenance" vs "ship nothing". Pick the first one and re-publish with provenance on the next clean release.

Could I have known about the billing limit before hitting it?
GitHub does send an email when you cross 75% of your minutes. The email goes to the address on your billing account, which may not be the address you watch. Worth setting up a filter.

What about Actions minutes for OSS public repos?
GitHub gives unlimited minutes to public repos using GitHub-hosted runners — but that's only for repos owned by organizations on the Free plan, with the runner type matching the included unlimited tier. For personal accounts and certain runner combinations, the standard quota applies. Check the actual numbers under Settings → Billing → Plans for your specific account type.


If you've hit a similar stuck-pending pattern with no error in the run UI — that's the bug. Check your minutes. Then ship from your laptop.

The repo (with the workflow that handles all this now) is safari-mcp on GitHub.