Skip to content

CMG-1093 | Contentful migration got stuck some time - #1122

Open
chetan-contentstack wants to merge 1 commit into
devfrom
bugfix/cmg-1093-contentful-migration-issue
Open

CMG-1093 | Contentful migration got stuck some time#1122
chetan-contentstack wants to merge 1 commit into
devfrom
bugfix/cmg-1093-contentful-migration-issue

Conversation

@chetan-contentstack

Copy link
Copy Markdown

🔗 Jira Ticket

CMG-1093 — Delta migration | v1.0.0 | Contentful migration got stuck some time


📋 PR Type

  • ✨ Feature
  • 🐛 Bug Fix
  • 🔥 Hotfix
  • ♻️ Refactor
  • 🧹 Chore / Dependency Update
  • 📝 Documentation

📝 Description

What changed?

  • writeOneFile now uses fs.promises.writeFile and is properly awaited. It was previously calling the callback-based fs.writeFile in a fire-and-forget pattern, so the outer await writeOneFile(...) returned before the file was actually written to disk.
  • In createAssets, added fs.promises.mkdir(assetsSave, { recursive: true }) before writing the assets schema/failed files, so the destination directory is guaranteed to exist.

Why?

During delta Contentful migrations the process would occasionally hang or leave a partial state. Root cause: the assets step kicked off a non-awaited write and moved on, and if the assetsSave directory did not yet exist the callback error was swallowed via console.error, leaving downstream steps waiting on a file that was never written. Both issues are fixed here.


🧩 Affected Areas

  • api — Node.js backend
  • ui — React frontend
  • upload-api — Upload API server
  • docker / docker-compose
  • CI / GitHub Actions workflows
  • Environment variables / config
  • Other:

🧪 How to Test

  1. Run a Contentful delta migration on a project that produced the hang in v1.0.0 (any project with assets works).
  2. Watch the API logs and the assets/ output directory as the assets step runs.
  3. Confirm the migration proceeds past the assets step without stalling, and that assets/<project>/assets.json (and the failed-assets file, if any) are written before the next step begins.

Expected result: Migration completes end-to-end; the assets directory is created if missing, assets.json is fully written before subsequent steps run, and no Error writing file: 3 messages appear in logs.


📸 Screenshots / Recordings

N/A — backend-only fix, no UI changes.


🔗 Related PRs / Dependencies

  • None

✅ Author Checklist

  • Branch follows naming convention: bugfix/cmg-1093-contentful-migration-issue
  • Jira ticket linked above
  • Self-reviewed the diff — no debug logs, commented-out code, or TODOs left in
  • .env / example.env updated if new environment variables were added — N/A
  • No sensitive credentials or secrets committed
  • Existing tests pass locally (npm test)
  • New tests written (or not applicable — no unit test exists for this file path and the fix is a race-condition/IO ordering change best verified via the manual repro above)
  • README.md / docs updated if behaviour changed — N/A
  • Talisman pre-push scan passes (no secrets flagged)

👀 Reviewer Notes

Please focus on:

  • The writeOneFile change — confirm no other caller relies on the previous non-awaiting behaviour (grep shows all call sites already await it).
  • The mkdir placement in createAssets — it runs after Promise.all(tasks) and before the first writeOneFile in that block, so both the schema file and the failed-assets file land in an existing directory.

@github-actions

Copy link
Copy Markdown

🔒 Security Scan Results

ℹ️ Note: Only vulnerabilities with available fixes (upgrades or patches) are counted toward thresholds.

Check Type Count (with fixes) Without fixes Threshold Result
🔴 Critical Severity 0 0 10 ✅ Passed
🟠 High Severity 14 390 25 ✅ Passed
🟡 Medium Severity 13 12 500 ✅ Passed
🔵 Low Severity 1 0 1000 ✅ Passed

⏱️ SLA Breach Summary

✅ No SLA breaches detected. All vulnerabilities are within acceptable time thresholds.

Severity Breaches (with fixes) Breaches (no fixes) SLA Threshold (with/no fixes) Status
🔴 Critical 0 0 15 / 30 days ✅ Passed
🟠 High 0 0 30 / 120 days ✅ Passed
🟡 Medium 0 0 90 / 365 days ✅ Passed
🔵 Low 0 0 180 / 365 days ✅ Passed

ℹ️ Vulnerabilities Without Available Fixes (Informational Only)

The following vulnerabilities were detected but do not have fixes available (no upgrade or patch). These are excluded from failure thresholds:

  • Critical without fixes: 0
  • High without fixes: 390
  • Medium without fixes: 12
  • Low without fixes: 0

✅ BUILD PASSED - All security checks passed

@chetan-contentstack chetan-contentstack changed the title fix: improve file writing and ensure asset directory creation CMG-1093 | Contentful migration got stuck some time Jul 27, 2026

@umesh-more-cstk umesh-more-cstk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

COMMENT review - non-gating. The human decides the merge gate.

Verdict: clean, correct bug fix. I traced the root cause and both changes and they hold up.

Verified-good

  • writeOneFile (line 338): the old callback-based fs.writeFile was fire-and-forget, so await writeOneFile(...) resolved before the bytes hit disk. migration.service.ts awaits createAssets and then immediately runs createEntry (lines 523 -> 535, and 948), and the entries step reads assets.json to resolve asset references - a write/read race that matches the reported "stuck / partial state" symptom. Switching to await fs.promises.writeFile fixes it. writeOneFile is module-private (not exported), and all three call sites - 361 inside writeFile's try/catch, and 800 & 814 inside createAssets' try/catch - already handle a rejection, so the now-throwing behavior introduces no unhandled rejection.
  • mkdir(assetsSave, { recursive: true }) (line 797): necessary and correctly placed. Lines 800 & 814 write via writeOneFile directly, which does NOT mkdir (unlike the writeFile helper at line 355 used on line 816). assetsSave is otherwise only created as a side effect of a successful per-asset write (saveAsset, line 722, on the files/<id> subdir). If every asset fails or throws before any success, assetsSave would not exist and the two direct writeOneFile writes would ENOENT - exactly what this guards.

Non-blocking

  • question - error swallowing after the fix: see inline on line 800.
  • nit - no automated test accompanies an IO-ordering fix (author acknowledged this in the checklist). Given the race nature, a test asserting assets.json exists once createAssets resolves would lock in the fix and prevent regression.

Scope: diff is a single file in api, matching the stated Affected Area and the ticket. No unrelated churn.

console.error("Error writing file: 3", err);
}
});
await fs.promises.writeFile(indexPath, JSON.stringify(fileMeta));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Correct fix - verified. One intentional behavior change worth flagging for the record: writeOneFile now rejects on a write error instead of logging Error writing file: 3 and swallowing it. I checked all three callers (line 361 inside writeFile's try/catch, lines 800 and 814 inside createAssets' try/catch) and they all handle the rejection, so no unhandled promise rejection is introduced. No encoding arg is needed - fs.promises.writeFile defaults to utf8 for a string payload.

await fs.promises.mkdir(assetsSave, { recursive: true });
const assetMasterFolderPath = path.join(assetsSave, ASSETS_FAILED_FILE);

await writeOneFile(path.join(assetsSave, ASSETS_SCHEMA_FILE), assetData);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: With writeOneFile now rejecting on failure, a failed schema write here (or the index.json/failed-assets writes at 814/816) unwinds to the createAssets catch at ~line 825, which only logs via customLogger and does NOT re-throw. migration.service.ts then proceeds from await createAssets(...) (lines 523/948) straight into createEntry, which reads assets.json. readFile (line 322) returns undefined on a missing file rather than hanging, so the entries step likely degrades to broken references rather than a hard stall - but since this ticket is specifically about migrations getting stuck, can you confirm the entries step tolerates a missing/partial assets.json gracefully? If a write genuinely fails, silently continuing may just move the failure downstream; consider surfacing it (or marking the step failed) rather than log-and-continue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants