Magic Tools
Developer ToolsBy CooconAugust 5, 2026127 views4 min read

GitHub Actions Fails Quickly with Connection Reset: The Cross-Border Pitfall When Overseas Runners Pull Alibaba Cloud ACR

1. Problem Description

A routine push triggered a Docker image build, and GitHub Actions failed directly about 2 seconds after starting:

ERROR: failed to authorize: failed to fetch oauth token:
Post "https://dockerauth.cn-hangzhou.aliyuncs.com/auth": read: connection reset by peer

The log error points to this line in the Dockerfile:

FROM registry.cn-hangzhou.aliyuncs.com/<namespace>/node:22-pm2

The first instinct might go astray: is the base image tag wrong? Has the ACR password expired? Is there a problem with the recently changed Dockerfile?

None of these. The only criterion: with the same Dockerfile and credentials, a previous build succeeded, then one failed, with no related changes in between. The code hasn't changed but the result has, so the problem isn't in the code.

2. Environment

Item Details
CI GitHub Actions, GitHub-hosted runner (overseas data center)
Build docker/build-push-action, multi-platform (amd64 + arm64)
Image Repository Alibaba Cloud ACR (Hangzhou region)
Base Image Custom node:22-pm2, also stored in the same ACR

3. Root Cause

Breaking down the link:

  1. GitHub-hosted runner is overseas.
  2. FROM a private ACR image, buildx needs to fetch an OAuth token from dockerauth.cn-hangzhou.aliyuncs.com during the "load metadata" phase.
  3. This overseas → Alibaba Cloud Hangzhou cross-border TCP link has unstable quality, occasionally being directly RST by intermediate devices—manifesting as connection reset by peer.

So this is an intermittent network-layer failure, not an authentication configuration issue (authentication errors return 401/403 HTTP responses, not TCP connection resets), and certainly not a Dockerfile issue (the error points to the FROM line only because the failure occurs during the metadata parsing stage of that line).

A Diagnostic Mantra Worth Remembering

First, check at which second the failure occurs. Second-level failure = network / authentication link; minute-level failure might be compilation / dependency issue.

Failing in 2 seconds, dependencies haven't even started installing; spending time checking npm packages or code is counterproductive.

4. Fix: Add Automatic Retry in Workflow

The intermittent RST of cross-border links cannot be eliminated from our side, but its characteristic is that retries are likely to succeed. Modify the build step:

- name: Build and push
  id: build
  continue-on-error: true        # 第一次失败不终止 job
  uses: docker/build-push-action@v6
  with:
    context: .
    platforms: linux/amd64,linux/arm64
    push: true
    tags: ${{ env.IMAGE_TAGS }}

- name: Wait before retry
  if: steps.build.outcome == 'failure'
  run: sleep 30                  # 给链路一点恢复时间

- name: Build and push (retry)
  if: steps.build.outcome == 'failure'
  uses: docker/build-push-action@v6
  with:                          # ⚠️ 参数必须与上面逐字一致
    context: .
    platforms: linux/amd64,linux/arm64
    push: true
    tags: ${{ env.IMAGE_TAGS }}

Two practical notes:

  • GitHub Actions YAML does not support anchors (&anchor / *ref), so the with blocks of the two steps cannot be reused; they must be manually kept in verbatim sync—when changing parameters later, both places need to be changed, and it's best to leave comments in the file as reminders.

  • continue-on-error: true will make the step status show a green checkmark when the first step fails (the outer job does not fail), so rely on steps.build.outcome to determine the actual result; don't be fooled by the interface color.

After deploying this retry mechanism, similar intermittent resets are caught by the second build, and there have been no more occurrences of the entire pipeline turning red.

5. Structural Solution (Optional)

Retry is just a stopgap. To make the cross-border dependency of "overseas runners pulling domestic images" disappear entirely, the idea is to mirror the base image to an overseas registry that is stably accessible:

  1. Sync and push a copy of the custom base image to GHCR (ghcr.io)
  2. Change the FROM in Dockerfile to GHCR—pulling side no longer crosses borders
  3. Build artifacts are pushed to ACR as before (push-side cross-border writes are tested to be more stable than authentication pulls, and failures are caught by the retries above)

The cost is maintaining an extra image sync. If the intermittent frequency is not high, the retry solution is sufficient; implement this when the failure frequency affects the release rhythm.

6. Troubleshooting Checklist (Go Through in Order for Similar Errors)

  1. At which second does the failure occur? Second-level → network/authentication direction; minute-level → compilation/dependency direction
  2. Is the error at the TCP layer (connection reset / timeout) or HTTP layer (401/403/404)? The former checks the link, the latter checks credentials
  3. Has the same configuration succeeded before? If it succeeded before and there are no related changes → intermittent environmental issue, retry first
  4. The code line pointed to by the error (e.g., FROM) is just the position being processed at the time of failure, not necessarily an issue with that line

Related Articles

Blocking rm -rf in Claude Code: A Deny Rule Stopped 14 Deletes in Two Weeks, 7 Other Spellings Still Get Through, and a Hook That Closes the Gap

To keep Claude Code from deleting things by accident, I added a deny rule, Bash(rm -rf *), to my global settings. Across my sessions from October 7 to 21 it blocked 14 deletes. After each denial the model picked a new directory name 8 times and dropped the delete 6 times. It never tried another spelling. Then I replayed 22 delete commands verbatim against Claude Code 2.1.285 through a stub. rm -fr, rm -r -f, rm -Rf and rm -r deleted the directory in manual, auto and bypass mode alike. /bin/rm, find -delete and bash -c deleted it in manual and bypass mode. In auto mode, rm -fr inside the working directory skips the classifier because it 'would be allowed in acceptEdits mode'. A second, built-in dangerous-rm check fires on targets like $D/*, even in bypass mode, but loses track of a variable assignment when a heredoc comes earlier in the same command. The post ends with a PreToolUse hook that blocks every recursive spelling in all three modes and tells the model what to do instead.

claude-codehooks+5
pitfallsOct 21, 20268 min
445

The Self-Reinforcing Flywheel Is Harder Than It Looks: 40 Days of an LLM Auto-Tuning Loop

We ran an 'LLM proposes, code decides' auto-tuning loop on a daily WeChat newsletter for 40 days: 18 versions, 8 rollbacks. Three of those rollbacks overturned decisions a human had made, and one version whose knobs were identical to its parent still got rolled back. Using our real numbers, telling apart a 20% difference needs about 25,000 reads per version. At 60 reads a day, that is more than 400 days. This postmortem covers five reasons the flywheel never turned, and what actually moved the numbers.

wechatpostmortem+4
pitfallsOct 21, 20268 min
293

Dev Breakfast · 2026-10-21

Today's headline: A single "bonsoir, Elliot" makes the model leak its .env: a backdoor for under $50. Plus 4 more: DeepSeek open-sources its inference engine: V4.1 Flash only, and KV cache still missing; TorchTPU goes open source: PyTorch models can run directly on TPU; and more.

daily-intelOct 21, 20268 min
641

Dev Breakfast · 2026-10-20

Today's headline: Should You Merge AI Code You Don't Understand: Settle the Abstraction Layer Yourself First. Plus 4 more: tmux as a Remote Control: Multi-Session Claude Code and send-keys; headroom Goes Open Source: 95% Token Savings on JSON, Only 20% for Coding Agents; and more.

daily-intelOct 20, 20265 min
574

Published by Magic Tools