Skip to content

fix(import): Fail when there are no rows to import - #144

Merged
sheldon-workos merged 3 commits into
mainfrom
devin/1788961610-fail-on-empty-import
Sep 10, 2026
Merged

sheldon-workos merged 3 commits into
mainfrom
devin/1788961610-fail-on-empty-import

Conversation

@sheldon-workos

@sheldon-workos sheldon-workos commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Description

An export that silently produced nothing (see #143) flowed straight into import, which reported "0 users created" and exited 0. Nothing distinguished that from a real migration, so the failure was only noticed much later.

Both import entrypoints now refuse empty input:

  • import exits 1 when --csv has no data rows.
  • importPackage() throws when every entity CSV in the package is header-only, including the SSO handoff metadata CSVs (sso/custom_attribute_mappings.csv, sso/proxy_routes.csv), which the plan previously didn't track at all — so a handoff-only package still imports.

--allow-empty on both commands (allowEmpty on importPackage()) opts back into the old no-op, for callers that legitimately run against a possibly-empty file.

Note this is a change in exit-code behavior for anyone scripting around an empty CSV: what used to exit 0 now exits 1 without the new flag.

--plan is unaffected — it still reports Total rows: 0 and returns, since planning an empty file is a reasonable thing to ask for.

Checklist

  • I have run npm run lint, npm run typecheck, npm run build, and npm test locally.
  • I have updated the README or other docs if behavior changed.
  • I have added or updated tests if appropriate.

Link to Devin session: https://app.devin.ai/sessions/c4fc166fd5b24121aefd9b336f281e03
Open in Devin Desktop: https://app.devin.ai/desktop/session/c4fc166fd5b24121aefd9b336f281e03?variant=devin

@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Original prompt from sheldon

SYSTEM:
<latest_message>
Sheldon Vaughn (U01RQ6P1HSN) [ts=1788960537.932789]: Can you help me get this customer thread organized and help me work on answering it as well:

Hey! We're migrating our devportal from Auth0 to AuthKit and ran into something
with ``@WorkOS/migrations 2.5.1 Every export path gives us 0 users, though
doing it by hand works fine. Wanted to flag it in case it's a known issue.

Tenant: 194k users, no Auth0 Orgs. We only
need the ~30k password users, relying on JIT-by-email for Google/GitHub users.

What we tried:
• documented command, no flags → "Found 0 organisation(s)", 0 users (looks like
the default engine walks org-by-org, and we have none)
• --engine bulk-job → "22 user record(s) downloaded", 0 exported. The job
itself has 194,261
• --use-metadata → Auth0 400, "can only page through the first 1000 records"

One thing that might be worth knowing: since nothing throws, the empty CSV flows
straight into import, which reports "0 users created" and exits 0 — so it
reads as a successful migration. Took us a while to spot.

We've got a workaround in place, 28,117 rows, validation is clean, and the dry run looks right. A few questions if you have a minute:

  1. Can you confirm the cli behaviour, and is a fix in the works?
  2. Is there a supported export path for &gt;1000 users with no Auth0 Orgs that we missed?
  3. We have 490 users on pbkdf2 i=100000, under the 210k floor, is there a way to keep those passwords, or do they need to reset?

Thanks! (edited)
3 replies

Eslem Alzate [2:33 AM]
We think we found the cause of the bulk-job one: Auth0 serves the result
gzipped but without a Content-Encoding header, so fetch doesn't decompress and
downloadJobLocation's response.text() ends up decoding the raw gzip. The lines
then get JSON.parse'd in a try/catch, so a handful of junk records survive and
the rest quietly drop. Gunzipping the same job manually gives all 194,261 users,
so it might just be a matter of sniffing for the gzip magic bytes f... (3394 chars truncated...)

@devin-ai-integration devin-ai-integration Bot 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.

Devin Review found 2 potential issues.

Devin Review

Comment thread src/import-package/orchestrator.ts
Comment thread src/cli/commands/import.ts
@greptile-apps

greptile-apps Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge, with the empty-input behavior implemented consistently and no outstanding actionable findings.

Summary

  • Adds --allow-empty to both import commands as an explicit opt-out.
  • Rejects empty package imports while recognizing supported SSO handoff metadata.
  • Adds package-level coverage and documents the changed behavior.
  • Replaces the full-file precheck for single CSV imports with a first-row check.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Start import] --> B{Planning-only command?}
  B -->|Yes| C[Report row count and return]
  B -->|No| D{Input has data or handoff metadata?}
  D -->|Yes| E[Continue import]
  D -->|No| F{allow-empty enabled?}
  F -->|Yes| G[Complete as successful no-op]
  F -->|No| H[Fail with empty-input error]
Loading

Reviews (5) · Last reviewed commit: "Stop parsing the whole CSV to detect an ..."

Comment thread src/cli/commands/import.ts Outdated
Comment thread src/cli/commands/import.ts
@devin-ai-integration
devin-ai-integration Bot force-pushed the devin/1788961610-fail-on-empty-import branch 2 times, most recently from 1bfe10b to 82674c3 Compare September 10, 2026 16:50
sheldon-workos and others added 3 commits September 10, 2026 16:53
An export that produced nothing flowed straight through import, which
reported "0 users created" and exited 0 — reading as a successful
migration. Both import commands now exit non-zero when the input has no
data rows, with --allow-empty to opt back into the no-op.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
A package carrying only SSO metadata CSVs (custom attribute mappings or
proxy routes) was classified as empty. Track those rows in the plan and
exclude them from the empty check.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
countCSVRows read every row before the importer parsed the file again.
The guard only needs to know whether one data row exists.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot force-pushed the devin/1788961610-fail-on-empty-import branch from 82674c3 to 4ece77d Compare September 10, 2026 16:53
@sheldon-workos
sheldon-workos merged commit c221314 into main Sep 10, 2026
5 checks passed
sheldon-workos pushed a commit that referenced this pull request Sep 10, 2026
🤖 I have created a release *beep* *boop*
---


##
[2.6.0](v2.5.1...v2.6.0)
(2026-09-10)


### Features

* import SSO connections and fix migration rate limiting
([#141](#141))
([8987809](8987809))


### Bug Fixes

* **auth0:** Download bulk export job as bytes
([#143](#143))
([ab6f67e](ab6f67e))
* **import:** Fail when there are no rows to import
([#144](#144))
([c221314](c221314))
* **templates:** Emit a users template the validator accepts
([#145](#145))
([1ba723a](1ba723a))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants