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

推荐订阅源

有赞技术团队
有赞技术团队
MyScale Blog
MyScale Blog
Cyberwarzone
Cyberwarzone
Schneier on Security
Schneier on Security
I
Intezer
Cisco Talos Blog
Cisco Talos Blog
Cloudbric
Cloudbric
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
NISL@THU
NISL@THU
博客园 - Franky
F
Fortinet All Blogs
Google DeepMind News
Google DeepMind News
Jina AI
Jina AI
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
N
News and Events Feed by Topic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
F
Full Disclosure
T
Troy Hunt's Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Project Zero
Project Zero
P
Palo Alto Networks Blog
Recorded Future
Recorded Future
美团技术团队
D
Docker
PCI Perspectives
PCI Perspectives
Microsoft Azure Blog
Microsoft Azure Blog
MongoDB | Blog
MongoDB | Blog
L
LINUX DO - 最新话题
Recent Announcements
Recent Announcements
Hacker News: Ask HN
Hacker News: Ask HN
人人都是产品经理
人人都是产品经理
月光博客
月光博客
D
DataBreaches.Net
The Hacker News
The Hacker News
爱范儿
爱范儿
V
Visual Studio Blog
Engineering at Meta
Engineering at Meta
SecWiki News
SecWiki News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
A
About on SuperTechFans
Latest news
Latest news
GbyAI
GbyAI
T
Tor Project blog
L
LINUX DO - 热门话题
Security Latest
Security Latest
博客园 - 聂微东
Y
Y Combinator Blog
AI
AI
M
MIT News - Artificial intelligence

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 Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Verify That Your GitHub Actions Deployment Actually Landed on the Server
hello world_leo · 2026-05-28 · via DEV Community

How to Verify?

GitHub Actions said ✅ Success. The server disagreed.

We kept running into a subtle CI/CD problem: after pushing to main, GitHub marked the deployment as successful, but checking the server revealed the old code was still there. No error. No alert. Just stale code running in production.

This article walks through the root cause, and the two-part fix we implemented: lock detection and commit SHA verification.


The Setup

Our deployment architecture uses a webhook-based approach (no SSH):

GitHub Actions → HTTP POST → deploy.php on server → runs deploy.sh

Why webhooks instead of SSH? Our hosting provider (Hypernode) IP-whitelists SSH access. GitHub Actions runs on dynamic Azure IPs that get blocked. Port 443 (HTTPS) has no such restriction.

The workflow looks like this:

- name: Trigger deploy on server
run: |
    response=$(curl -s -w "\n%{http_code}" \
    -X POST https://www.yoursite.nl/deploy.php \
    -H "X-Deploy-Token: ${{ secrets.DEPLOY_WEBHOOK_TOKEN }}" \
    --max-time 300)

    code=$(echo "$response" | tail -n 1)
    if [ "$code" != "200" ]; then
    echo "❌ Deployment failed (HTTP $code)"
    exit 1
    fi
    echo "✅ Deployment successful"

And deploy.php runs deploy.sh synchronously:

exec("bash {$script} 2>&1", $output, $exit);
echo implode("\n", $output) . "\n";
http_response_code($exit === 0 ? 200 : 500);

Looks solid, right? The exec() call is blocking — PHP waits for deploy.sh to finish before responding. So GitHub should always get an accurate result.

Mostly, yes. But there's one case where it silently lies.


The Root Cause: Silent Lock Skips

deploy.sh uses a lock file to prevent concurrent deployments:

LOCK_FILE="/tmp/mysite-deploy.lock"

if [ -f "$LOCK_FILE" ]; then
    log "ERROR: Deployment already in progress. Aborting."
    exit 1
fi

touch "$LOCK_FILE"
trap "rm -f $LOCK_FILE" EXIT

When a second push happens while a deploy is already running:

  1. Second deploy hits deploy.php
  2. deploy.sh finds lock file → exit 1
  3. deploy.php returns HTTP 500
  4. GitHub Actions sees 500 → ❌ marks as failed

That part is actually fine. But here's the subtle problem: the first deploy might fail mid-way (network hiccup on git fetch, OOM kill, timeout), leaving the lock file behind. Now every subsequent deploy silently skips, deploy.sh exits 1, deploy.php returns 500 and depending on how your workflow handles the response, you might see a misleading result.

Worse: even when deploys succeed, there was no proof of what commit actually landed. We had to manually SSH in and check file timestamps or grep for a known string. That's not sustainable.


The Fix: Two Parts

Part 1 — Return 423 When Locked

Instead of letting deploy.sh handle the lock silently, detect it in deploy.php first and return a meaningful HTTP status:

$lockFile = '/tmp/mysite-deploy.lock';
if (file_exists($lockFile)) {
    http_response_code(423);
    echo "LOCKED: Deployment already in progress. Try again shortly.\n";
    exit;
}

exec("bash {$script} 2>&1", $output, $exit);
echo implode("\n", $output) . "\n";
http_response_code($exit === 0 ? 200 : 500);

HTTP 423 Locked is semantically correct here — it's a standard status code meaning "the resource is currently locked."

Update the workflow to treat 423 as a distinct failure:

if [ "$code" = "423" ]; then
echo "⚠️  Deploy locked — previous deploy still running"
exit 1
fi
if [ "$code" != "200" ]; then
echo "❌ Deployment failed (HTTP $code)"
exit 1
fi

Now you get a clear signal in CI instead of a generic failure or false success.


Part 2 — Write a Version File, Verify the SHA

At the end of deploy.sh, after all steps complete successfully, write the deployed commit SHA to a public file:

# After git reset --hard, capture the SHA DEPLOYED_COMMIT=$(git rev-parse HEAD)
log "✓ Code updated (commit: $DEPLOYED_COMMIT)"

# ... composer install, yarn build, artisan commands ...

# At the very end, write version file
echo "$DEPLOYED_COMMIT $(date -u +%Y-%m-%dT%H:%M:%SZ)" > public/version.txt
log "✓ Version file written: $DEPLOYED_COMMIT"

This file is written after git clean (which would delete it) and before deploy.sh exits, so by the time deploy.php returns 200, version.txt is already on disk with the correct SHA.

Add it to .gitignore so it never gets accidentally committed: public/version.txt


Part 3 — Verify in GitHub Actions

Add a verification step after the webhook trigger:

- name: Trigger deploy on server
run: |
    # ... existing webhook call ...

- name: Verify deployment landed
run: |
    EXPECTED="${{ github.sha }}"
    echo "Expected SHA: $EXPECTED"

    for i in $(seq 1 20); do
    sleep 15
    DEPLOYED=$(curl -sf "https://www.yoursite.nl/version.txt" 2>/dev/null | awk '{print $1}')
    echo "Attempt $i/20: server=${DEPLOYED:-none}"
    if [ "$DEPLOYED" = "$EXPECTED" ]; then
        echo "✅ Deploy confirmed on server: $DEPLOYED"
        exit 0
    fi
    done

    echo "❌ Deploy verification FAILED after 5 minutes"
    echo "Expected: $EXPECTED"
    echo "Got:      ${DEPLOYED:-no response from server}"
    exit 1

This polls version.txt every 15 seconds for up to 5 minutes. Since our deploy.php uses synchronous exec(), the deploy is already finished by the time this step runs, so the first poll (15 seconds in) almost always matches immediately.


Results

Here's what the GitHub Actions log looks like after the fix:

Run EXPECTED="6f250fe1a2b9bb11ea826325a8a486b25279dfb1"
Waiting for deploy to complete on server...
Expected SHA: 6f250fe1a2b9bb11ea826325a8a486b25279dfb1
Attempt 1/20: server=6f250fe1a2b9bb11ea826325a8a486b25279dfb1
✅ Deploy confirmed on server: 6f250fe1a2b9bb11ea826325a8a486b25279dfb1

Confirmed on attempt 1 no waiting needed.

You can also verify manually any time:

curl -s https://www.yoursite.nl/version.txt
# 6f250fe1a2b9bb11ea826325a8a486b25279dfb1 2026-05-28T03:41:30Z


Why Not Just Use git log on the Server?

You could SSH in and run git log -1 — but that requires SSH access from CI (blocked for us), a separate monitoring job, or manual checks. The version.txt approach works over plain HTTPS with no
credentials, from anywhere, including your browser.

It also separates concerns: GitHub Actions verifies the outcome, not the process. Even if the internals of deploy.sh change, the verification contract stays the same "does the server report the right SHA?"


Summary

┌──────────────────────────────────────────────────┬───────────────────────────────────────┐
│                     Problem                      │                  Fix                  │
├──────────────────────────────────────────────────┼───────────────────────────────────────┤
│ No signal when deploy is locked                  │ deploy.php returns HTTP 423           │
├──────────────────────────────────────────────────┼───────────────────────────────────────┤
│ No proof of what commit landed                   │ deploy.sh writes public/version.txt   │
├──────────────────────────────────────────────────┼───────────────────────────────────────┤
│ GitHub shows success without server confirmation │ GitHub Actions polls and verifies SHA │
└──────────────────────────────────────────────────┴───────────────────────────────────────┘

Three small changes. Zero new dependencies. Works with any stack that can serve a static file over HTTP.