Skip to content

fix(import): improve error messages and extract business logic#413

Open
rhuanbarreto wants to merge 5 commits into
mainfrom
fix/import-error-messages-and-refactor
Open

fix(import): improve error messages and extract business logic#413
rhuanbarreto wants to merge 5 commits into
mainfrom
fix/import-error-messages-and-refactor

Conversation

@rhuanbarreto

@rhuanbarreto rhuanbarreto commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace the generic "Cannot detect import target" error with three context-aware messages:
    • Official registry miss: lists all available packs so users can find the correct name
    • Path doesn't exist: tells the user to verify the path
    • Path exists but invalid: explains what a valid target looks like
  • Extract all business logic from src/commands/adr/import.ts into src/helpers/adr-import.ts (ARCH-001 compliance — command files should be thin I/O only)
  • Move findProjectRoot() inside the try-catch error boundary (ARCH-012 compliance)

Test plan

  • bun run validate passes (1300 tests, 39/39 ADR rules, lint/typecheck/format/knip/build clean)
  • New unit tests for rewriteAdrId, buildIdMap, updateImportsManifest, loadImportsManifest/saveImportsManifest, cleanupTempDirs (14 new tests)
  • Updated detectTarget tests for new error message patterns
  • Existing import command tests pass unchanged (import handler uses spyOn for registry functions)

Summary by CodeRabbit

  • New Features

    • Added a reusable ADR import helper with deterministic ID assignment, manifest persistence, source cloning/resolution, and frontmatter ID rewriting.
  • Refactor

    • ADR import command now delegates import workflow to the new helper and centralizes temp-directory cleanup.
  • Bug Fixes

    • Clearer, context-aware errors for missing packs/paths and safer import write/rollback behavior.
  • Tests

    • New and updated tests covering import helpers, ID rewriting/assignment, manifest persistence, cleanup, and target-detection errors.

…ommand

Replace the generic "Cannot detect import target" error with three
context-aware messages: pack not found (with available packs list),
path does not exist, and path exists but is not a valid target.

Extract all business logic from src/commands/adr/import.ts into
src/helpers/adr-import.ts (ARCH-001 compliance) and move the
findProjectRoot() check inside the try-catch (ARCH-012 compliance).

Closes #413

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 65826c42-bf27-487e-bdb1-019f6c882a69

📥 Commits

Reviewing files that changed from the base of the PR and between b716a00 and 3ca30af.

📒 Files selected for processing (1)
  • src/helpers/registry.ts

📝 Walkthrough

Walkthrough

Refactors the ADR import command to delegate cloning, ADR collection, ID remapping, file writes, and imports manifest updates to a new src/helpers/adr-import.ts. Enhances registry target detection with context-aware errors. Centralizes temporary-directory cleanup and adds tests for the new helpers and updated registry behavior.

Changes

ADR Import Helper Extraction

Layer / File(s) Summary
Registry Helper Error Context
src/helpers/registry.ts
Enhanced detectTarget() signature to accept optional sourceKind; added internal listAvailablePacks() and improved error messages for missing subpaths, missing official packs, and invalid targets.
ADR Import Helper Module
src/helpers/adr-import.ts
Added ResolvedImport, AdrToImport, IdMapping types; loadImportsManifest()/saveImportsManifest() for .archgate/imports.json; rewriteAdrId() replacing id: in YAML frontmatter; resolveAndCloneSources() with per-(repo,ref) clone caching; collectAdrsToImport() for pack or single-ADR collection; buildIdMap() for deterministic domain-prefixed IDs; writeImportedAdrs() with parallel preload/write and rollback; updateImportsManifest() grouping by source and packVersion; cleanupTempDirs() for recursive removal.
Refactored Import Command
src/commands/adr/import.ts
Replaced inline import logic with calls to adr-import helpers; moved .archgate/ project-init check into try; removed inline early-return cleanup and centralized cleanup in a finally calling cleanupTempDirs(); replaced local manifest aggregation with updateImportsManifest().
ADR Import Helper Tests
tests/helpers/adr-import.test.ts
Added tests covering rewriteAdrId (including Windows CRLF and missing frontmatter), buildIdMap sequencing and prefix mapping, updateImportsManifest grouping/preservation, load/save manifest round-trip, and cleanupTempDirs behavior.
Registry Helper Tests
tests/helpers/registry.test.ts
Updated tests to assert new detectTarget() error messages for invalid targets and missing subpaths; added an official-registry missing-pack test asserting the error lists available pack paths.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through code to split the chores apart,
Pulled imports out and gave each piece a start,
IDs get rewritten, manifests now neat,
Temp dirs vanish tidy — cleanup’s complete.
Tests hum softly, green and smart.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main changes: improving error messages (for import target detection) and extracting business logic (from command to helpers). It accurately reflects the PR's core objectives.
Docstring Coverage ✅ Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/import-error-messages-and-refactor

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 13, 2026

Copy link
Copy Markdown

Deploying archgate-cli with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3ca30af
Status: ✅  Deploy successful!
Preview URL: https://eaafa854.archgate-cli.pages.dev
Branch Preview URL: https://fix-import-error-messages-an.archgate-cli.pages.dev

View logs

@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Code Coverage

Metric Value
Lines 90.2% (6847 / 7592)
Threshold 90% minimum — met
Platforms Linux + Windows

Full HTML report available in workflow artifacts.

Per-directory breakdown
Directory Coverage Lines
src/commands/ 87.7% 1911 / 2180
src/engine/ 93.1% 1384 / 1486
src/formats/ 100.0% 141 / 141
src/helpers/ 90.1% 3411 / 3785

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/commands/adr/import.ts (1)

75-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Temp directories are not guaranteed to be cleaned on exception paths.

cleanupTempDirs(tempDirs) is called on success and selected early returns, but not when errors occur after cloning (e.g., during collectAdrsToImport, writeImportedAdrs, or manifest save). The catch block exits without cleanup.

Suggested fix
-    .action(async (sources, opts) => {
-      try {
+    .action(async (sources, opts) => {
+      let tempDirs: string[] = [];
+      try {
         const projectRoot = findProjectRoot();
         if (!projectRoot) {
           logError("No .archgate/ directory found. Run `archgate init` first.");
           await exitWith(1);
           return;
         }
@@
-        const { resolved, tempDirs } = await resolveAndCloneSources(sources);
+        const cloned = await resolveAndCloneSources(sources);
+        const { resolved } = cloned;
+        tempDirs = cloned.tempDirs;
@@
-          cleanupTempDirs(tempDirs);
           return;
         }
@@
-          cleanupTempDirs(tempDirs);
           return;
         }
@@
-            cleanupTempDirs(tempDirs);
             return;
           }
         }
@@
-        cleanupTempDirs(tempDirs);
-
         if (useJson) {
@@
       } catch (err) {
         if (err instanceof Error && err.name === "ExitPromptError") throw err;
         logError(err instanceof Error ? err.message : String(err));
         await exitWith(1);
+      } finally {
+        if (tempDirs.length > 0) cleanupTempDirs(tempDirs);
       }
     });

Also applies to: 135-136, 155-156, 176-176

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/adr/import.ts` around lines 75 - 83, The temp directories
returned by resolveAndCloneSources are not cleaned on error paths; wrap the work
that uses tempDirs (the calls to collectAdrsToImport, writeImportedAdrs,
manifest save, and any early returns) in a try/finally so
cleanupTempDirs(tempDirs) always runs: call resolveAndCloneSources(sources) to
get { resolved, tempDirs }, then do the processing inside try { ... } and place
cleanupTempDirs(tempDirs) in finally { ... }; ensure any early returns inside
the try still allow the finally to execute and remove any duplicate cleanup
calls elsewhere (remove the existing direct calls to cleanupTempDirs(tempDirs)
where they become redundant).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/helpers/adr-import.ts`:
- Around line 43-50: Replace the direct Zod .parse call in loadImportsManifest
with .safeParse and handle the result: call
ImportsManifestSchema.safeParse(raw), if the result is successful return
result.data, otherwise throw a new Error with a clear, user-friendly message
that includes the validation errors (derived from the safeParse result) so
callers get readable feedback instead of Zod's internal error format.

In `@src/helpers/registry.ts`:
- Around line 245-261: The current logic checks sourceKind === "official" before
verifying pathExists, which causes the code in the official branch to always
throw the "Pack not found" error even when the path exists but points to an
invalid target; fix this by ensuring path existence is validated first (or by
adding pathExists to the official-branch condition). Concretely, update the code
around the checks for sourceKind, packName, listAvailablePacks, pathExists, and
subpath so that you either move the if (!pathExists) block to run before the if
(sourceKind === "official") block, or change the official branch to only throw
the "Pack not found" error when sourceKind === "official" && !pathExists
(leaving the existing invalid-target diagnostic to run when pathExists is true).

---

Outside diff comments:
In `@src/commands/adr/import.ts`:
- Around line 75-83: The temp directories returned by resolveAndCloneSources are
not cleaned on error paths; wrap the work that uses tempDirs (the calls to
collectAdrsToImport, writeImportedAdrs, manifest save, and any early returns) in
a try/finally so cleanupTempDirs(tempDirs) always runs: call
resolveAndCloneSources(sources) to get { resolved, tempDirs }, then do the
processing inside try { ... } and place cleanupTempDirs(tempDirs) in finally {
... }; ensure any early returns inside the try still allow the finally to
execute and remove any duplicate cleanup calls elsewhere (remove the existing
direct calls to cleanupTempDirs(tempDirs) where they become redundant).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0176c245-db16-4517-a1aa-cf8a2b47790b

📥 Commits

Reviewing files that changed from the base of the PR and between 7f2f5a4 and e31a703.

📒 Files selected for processing (5)
  • src/commands/adr/import.ts
  • src/helpers/adr-import.ts
  • src/helpers/registry.ts
  • tests/helpers/adr-import.test.ts
  • tests/helpers/registry.test.ts

Comment thread src/helpers/adr-import.ts
Comment thread src/helpers/registry.ts Outdated
- Use safeParse() instead of .parse() in loadImportsManifest for
  user-friendly validation errors per coding guidelines
- Guard official-source "pack not found" branch with !pathExists so
  existing-but-invalid paths still get the correct diagnostic
- Move tempDirs cleanup to a finally block so temp directories are
  always removed, even on error paths

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@rhuanbarreto rhuanbarreto enabled auto-merge (squash) June 13, 2026 20:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/helpers/registry.ts (1)

245-255: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Restrict the official-registry “pack not found” branch to packs/<name>.

This still catches any missing official subpath, so an input like packs/existing-pack/adrs/missing reports Pack "existing-pack/adrs/missing" not found... even when the pack exists. Limit this branch to top-level pack paths and let deeper misses fall through to the generic “path does not exist” diagnostic.

Suggested fix
-  if (!pathExists && sourceKind === "official") {
+  if (
+    !pathExists &&
+    sourceKind === "official" &&
+    /^packs\/[^/]+$/u.test(subpath)
+  ) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/helpers/registry.ts` around lines 245 - 255, The branch that throws the
“Pack ... not found in the official registry” error should only run for
top-level pack paths; change the condition around pathExists/sourceKind/subpath
so it only triggers when sourceKind === "official" AND subpath matches a
top-level pack pattern (e.g., /^packs\/[^/]+$/) before computing packName and
calling listAvailablePacks(cloneDir); leave deeper paths (like packs/<pack>/...)
to fall through to the generic missing-path handling so that missing inner
files/dirs are reported by the existing fallback. Use the existing identifiers
subpath, pathExists, sourceKind, packName, listAvailablePacks, and cloneDir to
locate and update the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/adr/import.ts`:
- Around line 76-78: The finally block leaks temp dirs because tempDirs is only
assigned after await resolveAndCloneSources(sources); fix by ensuring partial
temp dirs are tracked before the await resolves: either (A) modify
resolveAndCloneSources to push each created temp dir into a shared array
(tempDirs) as soon as shallowClone creates it (ensure shallowClone calls a
registration helper when it creates a temp dir), and have resolveAndCloneSources
reject while still returning/maintaining that array so the outer finally can
clean it up; or (B) move responsibility for cleanup into
resolveAndCloneSources/shallowClone so they always cleanup their own temp dirs
on error and the outer finally becomes a best-effort fallback; reference the
functions resolveAndCloneSources and shallowClone and the tempDirs variable when
applying the change.

---

Duplicate comments:
In `@src/helpers/registry.ts`:
- Around line 245-255: The branch that throws the “Pack ... not found in the
official registry” error should only run for top-level pack paths; change the
condition around pathExists/sourceKind/subpath so it only triggers when
sourceKind === "official" AND subpath matches a top-level pack pattern (e.g.,
/^packs\/[^/]+$/) before computing packName and calling
listAvailablePacks(cloneDir); leave deeper paths (like packs/<pack>/...) to fall
through to the generic missing-path handling so that missing inner files/dirs
are reported by the existing fallback. Use the existing identifiers subpath,
pathExists, sourceKind, packName, listAvailablePacks, and cloneDir to locate and
update the code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bd935e85-6ef9-4a71-b0ac-6f6f96ae94dd

📥 Commits

Reviewing files that changed from the base of the PR and between e31a703 and 5c09048.

📒 Files selected for processing (3)
  • src/commands/adr/import.ts
  • src/helpers/adr-import.ts
  • src/helpers/registry.ts

Comment on lines +76 to +78
const cloned = await resolveAndCloneSources(sources);
const { resolved } = cloned;
tempDirs = cloned.tempDirs;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

finally still misses temp dirs created before resolveAndCloneSources() rejects.

tempDirs is only populated after resolveAndCloneSources() returns. If that helper clones one repo and then fails on a later source (or if shallowClone() fails after creating its temp directory), the promise rejects before this assignment runs, so the finally block cleans nothing and those temp dirs leak on disk. The helper layer needs to own cleanup on failure, or otherwise expose partial tempDirs before the await completes.

Also applies to: 201-202

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/adr/import.ts` around lines 76 - 78, The finally block leaks
temp dirs because tempDirs is only assigned after await
resolveAndCloneSources(sources); fix by ensuring partial temp dirs are tracked
before the await resolves: either (A) modify resolveAndCloneSources to push each
created temp dir into a shared array (tempDirs) as soon as shallowClone creates
it (ensure shallowClone calls a registration helper when it creates a temp dir),
and have resolveAndCloneSources reject while still returning/maintaining that
array so the outer finally can clean it up; or (B) move responsibility for
cleanup into resolveAndCloneSources/shallowClone so they always cleanup their
own temp dirs on error and the outer finally becomes a best-effort fallback;
reference the functions resolveAndCloneSources and shallowClone and the tempDirs
variable when applying the change.

…ails

If shallowClone succeeds for source #1 but a later source fails
(clone error, detectTarget rejection), the promise rejects before
the caller can capture tempDirs, leaking clone #1 on disk.

Wrap the clone loop in try/catch so resolveAndCloneSources cleans
up any temp dirs it already created before re-throwing.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/helpers/adr-import.ts (1)

133-145: ⚠️ Potential issue | 🟠 Major

Sort pack ADRs before building the import list to make ID assignment deterministic.

src/helpers/registry.ts builds target.adrFiles/target.rulesFiles from readdirSync(adrsDir) without sorting. src/helpers/adr-import.ts then consumes target.adrFiles in-order to construct adrsToImport, and buildIdMap() assigns newId values sequentially over that order—so pack ID mappings (and imports.json entries) can vary across filesystems/runs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/helpers/adr-import.ts` around lines 133 - 145, The import order for pack
ADRs is non-deterministic because target.adrFiles is consumed as-is when
building adrsToImport (the block starting with "if (target.kind === \"pack\")"
that calls Bun.file(...).text(), parseAdr(...), basename(...), and
items.push(...)), which causes buildIdMap() to assign newId values in filesystem
order; fix this by deterministically sorting the ADR list before processing
(e.g., const sortedAdrFiles = [...target.adrFiles].sort() and use sortedAdrFiles
for the Promise.all and subsequent loop) and likewise sort or otherwise
deterministically handle target.rulesFiles if you rely on their order—this
ensures the constructed items/adrsToImport and resulting ID map are stable
across runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/helpers/adr-import.ts`:
- Around line 105-116: The clone temp directory can be created inside
shallowClone but not returned on failure, so failed clones leak temp dirs;
inside the shallowClone function wrap the git clone step in a try/catch and on
any error synchronously/awaitably remove the temp directory you created (e.g.,
fs.rm/recursive) before rethrowing the error so callers (like the try/catch in
the import flow that only tracks returned cloneDir values) don't need to change;
keep the existing cleanupTempDirs usage as is.

---

Outside diff comments:
In `@src/helpers/adr-import.ts`:
- Around line 133-145: The import order for pack ADRs is non-deterministic
because target.adrFiles is consumed as-is when building adrsToImport (the block
starting with "if (target.kind === \"pack\")" that calls Bun.file(...).text(),
parseAdr(...), basename(...), and items.push(...)), which causes buildIdMap() to
assign newId values in filesystem order; fix this by deterministically sorting
the ADR list before processing (e.g., const sortedAdrFiles =
[...target.adrFiles].sort() and use sortedAdrFiles for the Promise.all and
subsequent loop) and likewise sort or otherwise deterministically handle
target.rulesFiles if you rely on their order—this ensures the constructed
items/adrsToImport and resulting ID map are stable across runs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6fd69185-6cdd-4954-b788-6a004b8c2e2d

📥 Commits

Reviewing files that changed from the base of the PR and between 5c09048 and 556d951.

📒 Files selected for processing (1)
  • src/helpers/adr-import.ts

Comment thread src/helpers/adr-import.ts
shallowClone creates a temp directory before running git clone.
On clone failure it threw without removing the directory, leaking
archgate-import-* dirs in the system temp area. Now rmSync runs
before re-throwing.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/helpers/registry.ts (2)

149-154: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clean up temp dirs when run(args) throws, not only on non-zero exit.

At Line 150, if run(args) throws (spawn/stream failure), cleanup at Line 153 is skipped and the temp directory leaks. Wrap run(args) in try/catch and remove tempDir before rethrowing.

Suggested patch
   logDebug("Cloning:", args.join(" "));
-  const result = await run(args);
+  let result: { exitCode: number; stdout: string; stderr: string };
+  try {
+    result = await run(args);
+  } catch (err) {
+    rmSync(tempDir, { recursive: true, force: true });
+    throw err;
+  }

   if (result.exitCode !== 0) {
     rmSync(tempDir, { recursive: true, force: true });
     throw new Error(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/helpers/registry.ts` around lines 149 - 154, The code currently calls
run(args) without catching exceptions so rmSync(tempDir, { recursive: true,
force: true }) only runs when result.exitCode !== 0; wrap the run(args) call in
a try/catch (or try/finally) around the block that includes logDebug and run so
that on any thrown error you call rmSync(tempDir, { recursive: true, force: true
}) before rethrowing the original error; keep the existing non-zero-exit
handling (checking result.exitCode) but remove duplicate cleanup by centralizing
tempDir removal in the catch or finally so temp dirs are always removed whether
run throws or returns a non-zero exit code.

212-213: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Constrain subpath to the clone root to block path traversal.

subpath is joined directly at Line 212, so inputs containing ../ can escape cloneDir and make Line 217/Line 239 read arbitrary local files. Please normalize and enforce that the resolved target stays within cloneDir before any filesystem access.

Suggested patch
-import { join } from "node:path";
+import { isAbsolute, join, relative, resolve } from "node:path";
...
 export async function detectTarget(
   cloneDir: string,
   subpath: string,
   sourceKind?: ResolvedSource["kind"]
 ): Promise<ImportTarget> {
-  const fullPath = join(cloneDir, subpath);
+  const fullPath = resolve(cloneDir, subpath);
+  const rel = relative(cloneDir, fullPath);
+  if (rel.startsWith("..") || isAbsolute(rel)) {
+    throw new Error(`Path "${subpath}" escapes the repository root.`);
+  }

Also applies to: 217-218, 238-240

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/helpers/registry.ts` around lines 212 - 213, The code currently joins
user-controlled subpath with cloneDir into fullPath (variable fullPath) and then
reads files, allowing path traversal; fix by resolving and validating the target
stays inside cloneDir before any FS access: compute const resolved =
path.resolve(cloneDir, subpath) (or use fs.realpathSync on cloneDir and resolved
for symlink safety), then verify that resolved === path.resolve(cloneDir) or
resolved.startsWith(path.resolve(cloneDir) + path.sep); if the check fails,
throw or return an error and do not perform the read; apply this same validation
wherever fullPath/target is constructed from subpath (the places using cloneDir,
subpath, and fullPath) so all reads are gated by this containment check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/helpers/registry.ts`:
- Around line 149-154: The code currently calls run(args) without catching
exceptions so rmSync(tempDir, { recursive: true, force: true }) only runs when
result.exitCode !== 0; wrap the run(args) call in a try/catch (or try/finally)
around the block that includes logDebug and run so that on any thrown error you
call rmSync(tempDir, { recursive: true, force: true }) before rethrowing the
original error; keep the existing non-zero-exit handling (checking
result.exitCode) but remove duplicate cleanup by centralizing tempDir removal in
the catch or finally so temp dirs are always removed whether run throws or
returns a non-zero exit code.
- Around line 212-213: The code currently joins user-controlled subpath with
cloneDir into fullPath (variable fullPath) and then reads files, allowing path
traversal; fix by resolving and validating the target stays inside cloneDir
before any FS access: compute const resolved = path.resolve(cloneDir, subpath)
(or use fs.realpathSync on cloneDir and resolved for symlink safety), then
verify that resolved === path.resolve(cloneDir) or
resolved.startsWith(path.resolve(cloneDir) + path.sep); if the check fails,
throw or return an error and do not perform the read; apply this same validation
wherever fullPath/target is constructed from subpath (the places using cloneDir,
subpath, and fullPath) so all reads are gated by this containment check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fc342038-31de-42ba-9e1e-d6efd2a5d300

📥 Commits

Reviewing files that changed from the base of the PR and between 556d951 and b716a00.

📒 Files selected for processing (1)
  • src/helpers/registry.ts

- Wrap run(args) in shallowClone with try/catch so the temp dir is
  removed when the spawn itself throws, not only on non-zero exit
- Validate that subpath in detectTarget resolves within cloneDir,
  rejecting inputs containing ../ that would escape the clone root

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
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.

1 participant