feat: add bulk-create script for importing multiple issues with media#18
feat: add bulk-create script for importing multiple issues with media#18dandaka wants to merge 1 commit into
Conversation
wrsmith108
left a comment
There was a problem hiding this comment.
Thanks for the contribution! Useful capability — bulk import with media attachments fills a real gap. Ran a multi-perspective review and surfaced a few items that I'd like addressed before merge.
Critical / High issues
1. package.json test script is a regression (drops 5 test files)
- "test": "node --test dist/__tests__/smoke.test.js",
+ "test": "node --test dist/__tests__/smoke.test.js dist/__tests__/bulk-create.test.js",Current main uses (or has been moving to) a dist/__tests__/*.test.js glob — please double-check. Replacing it with a hand-listed two-file set silently drops the other test files (lin-cli.test, retry.test, issue-description.test, lint-issues.test, validate-description.test).
Suggested fix: drop the package.json change entirely — the existing glob already picks up bulk-create.test.js once it's built.
2. No 429 / rate-limit handling
PR #23 (just merged) added scripts/lib/retry.ts (withRetry) specifically for this. bulk-create makes N × (label-lookup + state-lookup + fileUpload + PUT + createIssue) calls in a tight loop and ignores 429/5xx entirely. Any >5-ticket run on a busy org will fail mid-batch with no recovery.
Suggested fix: wrap each Linear API call in withRetry({ label: 'bulk-create:<op>' }). ~10 lines.
3. Whole-batch abort on per-ticket failure
for (const ticket of tickets) {
// ...
const asset = await uploadAsset(client, full); // throws → kills entire run
// ...
}An exception in uploadAsset or createIssue bubbles to top-level main().catch(). Remaining tickets never run, summary never prints, no resume token. The PR description mentions running this on 8 tickets with video — exactly the partial-failure profile.
Suggested fix: wrap the per-ticket loop body in try/catch, collect failures[] alongside results[], print both in the summary, exit non-zero iff any failure. Goes hand-in-hand with the failure-summary issue below.
4. Code duplication with create-issue-with-project.ts
lookupStateIdByName / lookupLabelIdsByName are near-verbatim copies of lookupStateByName / lookupLabelIds in create-issue-with-project.ts. scripts/lib/linear-utils.ts is the documented home for shared lookups (per CLAUDE.md).
Suggested fix: move both helpers into linear-utils.ts as findWorkflowStateIdByName / findLabelIdsByName, have both scripts import them. Then add withRetry in one place.
5. No re-run safety / idempotency
Re-running the same manifest creates duplicates. Combined with #3, the user's flow becomes: run → one ticket fails → fix the file → re-run → 7 duplicates of the originally-successful ones.
Suggested fix: add --dry-run (trivial). Real dedup against existing issues can be a follow-up issue.
6. Failure summary lists only successes
When 3 of 8 tickets fail, Created 5/8 shows the 5 wins but no list of which failed or why. With current abort-semantics, the summary isn't even reached.
Suggested fix: along with the per-ticket isolation fix in #3, print a === FAILURES === section with key, title, and error message per failure.
Medium issues
- Inconsistent failure handling:
created.issue == nullsoft-fails (continue), butclient.createIssue()rejection is fatal. Same logical failure, two outcomes. - Label case sensitivity: Linear's
infilter is case-sensitive, but the missing-list is computed case-insensitively. Result:Bugvsbugsilently drops the label, prints a warning, but creates the issue without it. The PR description says "warn not fail" but doesn't note this nuance. - Memory:
readFileSyncloads entire file (Linear's per-file cap is 25MB) into memory; sequential 8×25MB momentarily allocates ~200MB.createReadStreamor streaming viaBlobwould be cleaner. - Hardcoded
Cache-Controlheader: set, then overwritten byuf.headersloop — effectively a no-op default. Either remove or document. - Sibling script vs subcommand:
linear-ops.tsis the canonical CLI perCLAUDE.md. New top-level scripts dilute discoverability. Considerlinear-ops bulk-create(orbulk-import). - Fragile
parseArgs: fixedi += 2step assumes strict--key valuepairs. No--flag=value, no-h/--help, unknown flags silently ignored. Acceptable for now but worth a CLI lib swap eventually. - Strict mode: missing labels and unset
state_namewarn but proceed → exit code says success. Consider--strictflag mirroringcreate-issue-with-project.ts.
Low issues
- Smoke tests cover only arg-validation paths — no test for label/state lookup fallback, upload error handling, or partial-failure summary.
client.createIssue(input as never)casts away type safety. UseIssueCreateInput.- "bulk-create" overlaps semantically with
sync.ts(bulk-update).bulk-importwould disambiguate. - Mixed
console.log/console.error/process.stdout.write; no--quietor--jsonmode for scripting.
Recommendation
request-changes — useful capability, but the package.json regression, missing retry/backoff, abort-on-failure, and duplicated lookups need to land first. The fixes are scoped and self-contained. Happy to review again once those are in. Thanks for the work!
Creates N Linear issues from a manifest directory with per-ticket markdown descriptions and optional media files. Media is uploaded via Linear's fileUpload API and embedded in the issue description. Use case: bulk-importing customer feedback, retrospective action items, or custdev tickets that each need screenshots/recordings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3789e6c to
83b786b
Compare
|
Thanks for the thorough review! Rebased onto current main and addressed all requested changes. Here's a point-by-point summary: Critical / High — all addressed1. package.json test regression — Fixed. Dropped the hand-listed two-file set entirely. Now uses the 2. No 429 / rate-limit handling — Fixed. All Linear API calls ( 3. Whole-batch abort on per-ticket failure — Fixed. Per-ticket loop body is wrapped in try/catch. Failures are collected in a 4. Code duplication with create-issue-with-project.ts — Fixed. Extracted 5. No re-run safety / --dry-run — Fixed. Added 6. Failure summary lists only successes — Fixed as part of #3. Summary now shows both Medium — addressed
Residual notes (not blocking)
CI green, typecheck/lint clean, 88/88 tests pass. Ready for re-review. |
Summary
Adds
scripts/bulk-create.ts— a manifest-driven importer that creates N Linear issues in one team, each with its own markdown description and optional media files. Media is uploaded via Linear'sfileUploadAPI and embedded in the issue description (images inline, other files as links).Fills a gap between the existing scripts:
create-issue-with-project.tscreates one issue with flagssync.ts/linear-ops.tsupdate existing issues in bulkbulk-create.tscreates many new issues from a structured manifest, each with attached mediaUse case
Bulk-importing customer feedback, retrospective action items, or custdev tickets where each issue needs its own screenshots or recordings — the kind of input that's too repetitive for one-off
create-issuecalls and too rich for a flat CSV. Used it locally to create 8 Linear issues in a single run from Telegram feedback, each with 1–2 attached screenshots/videos embedded in the description.Usage
Manifest directory layout:
Config resolves by name, not UUID:
team_key→ looked up viafindTeamByKeyhelper (e.g."ENG")state_name→ optional workflow state (e.g."Triage")labels→ label names resolved per team; unknown names warn, not failConventions followed
getLinearClient()+findTeamByKey()fromscripts/lib/linear-utils.tsEXIT_CODESfromscripts/lib/exit-codes.tsfor distinct exit codes (1=missing key, 2=invalid args, 3=resource not found, 4=API error, 5=validation)rawRequestGraphQL, same pattern ascreate-issue-with-project.ts)scripts/build.mjsscripts/__tests__/bulk-create.test.tscovering arg parsing and exit codes; wired intonpm testVerification
Test plan
npm run typechecknpm run lintnpm run buildnpm test(13/13 pass)