Fixes #268 — repos stuck in mirroring state after transient network errors, with no failure entry in the activity log
Hoists let migrateSucceeded above the try block in mirrorGithubRepoToGitea and mirrorGitHubRepoToGiteaOrg so the catch block can actually read it
Adds regression tests that fail without the fix with the exact production error message
Root cause
let migrateSucceeded = false; was declared inside the try block. let is block-scoped, so the catch block at the bottom of the same function couldn't see it. When any operation inside try threw (timeout to Gitea, 4xx/5xx, etc.), the catch block crashed with ReferenceError: migrateSucceeded is not definedbefore it could:
update the repo to status: "failed"
clear mirroredLocation
write a "failed" activity-log entry
re-throw the original error
So the repo was permanently stuck in mirroring, no failure log appeared, and the user-visible error on retry was the misleading Failed to mirror repository: migrateSucceeded is not defined instead of the real cause.
This was visible in elpastorios's logs on the issue:
Error while mirroring repository <REPO>: Network error: The operation timed out.
Retrying repository <REPO> (attempt 1): Failed to mirror repository: migrateSucceeded is not defined
The first line is the catch's console.error (runs before the bad reference); the second is the wrapper from mirrorGitHubOrgRepoToGiteaOrg's outer catch at gitea.ts:1857.
TypeScript was already flagging Cannot find name 'migrateSucceeded' at the catch lines — but esbuild strips types during build, so the bug shipped.
The trigger — Gitea's /repos/migrate timing out client-side while continuing in the background — still leaves orphan repos that retries see and rename to repo-1, repo-2 (the duplicates in elpastorios's screenshot). That's a separate fix; options include probing Gitea after a timeout, raising the migrate timeout, or making generateUniqueRepoName aware of orphans owned by this user. Worth following up on but out of scope here — this PR stops the bleeding so transient errors no longer leave repos permanently stuck.
Test plan
New regression tests in src/lib/gitea-mirror-failure-recovery.test.ts cover both mirrorGithubRepoToGitea and mirrorGitHubRepoToGiteaOrg. They force httpPost to reject and assert:
the thrown error preserves the original message (not migrateSucceeded is not defined)
the DB is updated with status: "failed" and mirroredLocation: ""
createMirrorJob is called with status: "failed"
Verified tests fail on the buggy code with Received: "migrateSucceeded is not defined" — exact production error
bun test — 233 pass, 0 fail across 39 files
Manual verification on a Gitea instance: trigger a mirror against an unreachable Gitea URL, confirm the repo lands in failed and a failure entry appears in Activity Logs
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.
## 📋 Pull Request Information
**Original PR:** https://github.com/RayLabsHQ/gitea-mirror/pull/280
**Author:** [@arunavo4](https://github.com/arunavo4)
**Created:** 5/3/2026
**Status:** ✅ Merged
**Merged:** 5/4/2026
**Merged by:** [@arunavo4](https://github.com/arunavo4)
**Base:** `main` ← **Head:** `fix/issue-268-stuck-mirroring`
---
### 📝 Commits (2)
- [`4351f4e`](https://github.com/RayLabsHQ/gitea-mirror/commit/4351f4e233ce9b0a28e3bca4859b2bf28044ab4b) fix: hoist migrateSucceeded above try so catch can update DB on failure (fixes #268)
- [`bf0a2cd`](https://github.com/RayLabsHQ/gitea-mirror/commit/bf0a2cd54aa9cf6f99f3922c237b7cf365c40a6d) test: replace integration test with structural source check (#268)
### 📊 Changes
**2 files changed** (+140 additions, -6 deletions)
<details>
<summary>View changed files</summary>
➕ `src/lib/gitea-mirror-failure-recovery.test.ts` (+132 -0)
📝 `src/lib/gitea.ts` (+8 -6)
</details>
### 📄 Description
## Summary
- Fixes [#268](https://github.com/RayLabsHQ/gitea-mirror/issues/268) — repos stuck in `mirroring` state after transient network errors, with no failure entry in the activity log
- Hoists `let migrateSucceeded` above the `try` block in `mirrorGithubRepoToGitea` and `mirrorGitHubRepoToGiteaOrg` so the `catch` block can actually read it
- Adds regression tests that fail without the fix with the exact production error message
## Root cause
`let migrateSucceeded = false;` was declared *inside* the `try` block. `let` is block-scoped, so the `catch` block at the bottom of the same function couldn't see it. When any operation inside `try` threw (timeout to Gitea, 4xx/5xx, etc.), the catch block crashed with `ReferenceError: migrateSucceeded is not defined` *before* it could:
- update the repo to `status: "failed"`
- clear `mirroredLocation`
- write a `"failed"` activity-log entry
- re-throw the original error
So the repo was permanently stuck in `mirroring`, no failure log appeared, and the user-visible error on retry was the misleading `Failed to mirror repository: migrateSucceeded is not defined` instead of the real cause.
This was visible in elpastorios's logs on the issue:
```
Error while mirroring repository <REPO>: Network error: The operation timed out.
Retrying repository <REPO> (attempt 1): Failed to mirror repository: migrateSucceeded is not defined
```
The first line is the catch's `console.error` (runs before the bad reference); the second is the wrapper from `mirrorGitHubOrgRepoToGiteaOrg`'s outer catch at `gitea.ts:1857`.
TypeScript was already flagging `Cannot find name 'migrateSucceeded'` at the catch lines — but esbuild strips types during build, so the bug shipped.
Introduced in #236.
## What this fix does NOT cover
The *trigger* — Gitea's `/repos/migrate` timing out client-side while continuing in the background — still leaves orphan repos that retries see and rename to `repo-1`, `repo-2` (the duplicates in elpastorios's screenshot). That's a separate fix; options include probing Gitea after a timeout, raising the migrate timeout, or making `generateUniqueRepoName` aware of orphans owned by this user. Worth following up on but out of scope here — this PR stops the bleeding so transient errors no longer leave repos permanently stuck.
## Test plan
- [x] New regression tests in `src/lib/gitea-mirror-failure-recovery.test.ts` cover both `mirrorGithubRepoToGitea` and `mirrorGitHubRepoToGiteaOrg`. They force `httpPost` to reject and assert:
- the thrown error preserves the original message (not `migrateSucceeded is not defined`)
- the DB is updated with `status: "failed"` and `mirroredLocation: ""`
- `createMirrorJob` is called with `status: "failed"`
- [x] Verified tests fail on the buggy code with `Received: "migrateSucceeded is not defined"` — exact production error
- [x] `bun test` — 233 pass, 0 fail across 39 files
- [ ] Manual verification on a Gitea instance: trigger a mirror against an unreachable Gitea URL, confirm the repo lands in `failed` and a failure entry appears in Activity Logs
---
<sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
📋 Pull Request Information
Original PR: https://github.com/RayLabsHQ/gitea-mirror/pull/280
Author: @arunavo4
Created: 5/3/2026
Status: ✅ Merged
Merged: 5/4/2026
Merged by: @arunavo4
Base:
main← Head:fix/issue-268-stuck-mirroring📝 Commits (2)
4351f4efix: hoist migrateSucceeded above try so catch can update DB on failure (fixes #268)bf0a2cdtest: replace integration test with structural source check (#268)📊 Changes
2 files changed (+140 additions, -6 deletions)
View changed files
➕
src/lib/gitea-mirror-failure-recovery.test.ts(+132 -0)📝
src/lib/gitea.ts(+8 -6)📄 Description
Summary
mirroringstate after transient network errors, with no failure entry in the activity loglet migrateSucceededabove thetryblock inmirrorGithubRepoToGiteaandmirrorGitHubRepoToGiteaOrgso thecatchblock can actually read itRoot cause
let migrateSucceeded = false;was declared inside thetryblock.letis block-scoped, so thecatchblock at the bottom of the same function couldn't see it. When any operation insidetrythrew (timeout to Gitea, 4xx/5xx, etc.), the catch block crashed withReferenceError: migrateSucceeded is not definedbefore it could:status: "failed"mirroredLocation"failed"activity-log entrySo the repo was permanently stuck in
mirroring, no failure log appeared, and the user-visible error on retry was the misleadingFailed to mirror repository: migrateSucceeded is not definedinstead of the real cause.This was visible in elpastorios's logs on the issue:
The first line is the catch's
console.error(runs before the bad reference); the second is the wrapper frommirrorGitHubOrgRepoToGiteaOrg's outer catch atgitea.ts:1857.TypeScript was already flagging
Cannot find name 'migrateSucceeded'at the catch lines — but esbuild strips types during build, so the bug shipped.Introduced in #236.
What this fix does NOT cover
The trigger — Gitea's
/repos/migratetiming out client-side while continuing in the background — still leaves orphan repos that retries see and rename torepo-1,repo-2(the duplicates in elpastorios's screenshot). That's a separate fix; options include probing Gitea after a timeout, raising the migrate timeout, or makinggenerateUniqueRepoNameaware of orphans owned by this user. Worth following up on but out of scope here — this PR stops the bleeding so transient errors no longer leave repos permanently stuck.Test plan
src/lib/gitea-mirror-failure-recovery.test.tscover bothmirrorGithubRepoToGiteaandmirrorGitHubRepoToGiteaOrg. They forcehttpPostto reject and assert:migrateSucceeded is not defined)status: "failed"andmirroredLocation: ""createMirrorJobis called withstatus: "failed"Received: "migrateSucceeded is not defined"— exact production errorbun test— 233 pass, 0 fail across 39 filesfailedand a failure entry appears in Activity Logs🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.