Skip to content

fix(cli): skip doc extraction in ensureCheckout; fix symlinks in cpDirAtomic#96

Merged
amondnet merged 1 commit intomainfrom
amondnet/src-docs-fallback
Apr 16, 2026
Merged

fix(cli): skip doc extraction in ensureCheckout; fix symlinks in cpDirAtomic#96
amondnet merged 1 commit intomainfrom
amondnet/src-docs-fallback

Conversation

@amondnet
Copy link
Copy Markdown
Contributor

@amondnet amondnet commented Apr 16, 2026

Summary

  • Bug 1 — spurious "No docs directory found" error in ask src / ask docs: GithubSource.fetch always ran extractDocsFromDir, correct for runInstall but wrong for path-only callers. Repos without a conventional docs/ dir (e.g. gitbutler) threw through both the shallow-clone and tar.gz paths. Added skipDocExtraction?: boolean to GithubSourceOptions; ensureCheckout now sets this flag, so only the checkout path is returned (files: []). runInstall is untouched — skipDocExtraction is strictly opt-in.

  • Bug 2 — ENOENT on cache hits caused by broken symlinks in the store: cpDirAtomic called fs.cpSync without verbatimSymlinks: true, so Node resolved relative symlinks against the temporary source dir, baking absolute paths pointing at the soon-deleted ask-gh-clone-* tmpdir into the store. Every subsequent verifyEntry → hashDir → readFileSync hit ENOENT. Gitbutler's claude.md/CLAUDE.md → AGENTS.md relative symlinks triggered this. Fixed by adding verbatimSymlinks: true to the fs.cpSync call in packages/cli/src/store/index.ts.

Both bugs manifest as a single user-visible failure: ask src github:gitbutlerapp/gitbutler returns an error instead of the cached master checkout path.

Test plan

  • Unit tests addedpackages/cli/test/sources/github.test.ts: 3 new tests under describe('skipDocExtraction — callers that only need the checkout path'):
    • Success path: repo without docs dir succeeds when flag is set
    • Master fallback + no docs: combined fallback + skip still resolves
    • Guardrail: default behavior (flag unset) still throws — runInstall path is unaffected
  • Unit test addedpackages/cli/test/commands/ensure-checkout.test.ts: 1 new test verifying skipDocExtraction: true is forwarded to the fetcher
  • Manual: ask src — run ask src github:gitbutlerapp/gitbutler; should print the master checkout path without error
  • Manual: ask docs cache hit — run ask docs github:gitbutlerapp/gitbutler after a cached checkout exists; should emit doc-like subdirectory paths without ENOENT
  • Regression: runInstall unaffected — run ask install for a spec with a docs dir; normal doc extraction must still work (skipDocExtraction is not set by runInstall)

Files changed

  • packages/cli/src/sources/index.tsskipDocExtraction?: boolean added to GithubSourceOptions
  • packages/cli/src/sources/github.ts — three call sites skip extractDocsFromDir when flag is set
  • packages/cli/src/commands/ensure-checkout.ts — sets skipDocExtraction: true on fetch opts
  • packages/cli/src/store/index.tsverbatimSymlinks: true added to fs.cpSync
  • packages/cli/test/sources/github.test.ts — 3 new regression tests
  • packages/cli/test/commands/ensure-checkout.test.ts — 1 new regression test
  • CLAUDE.md — two gotchas added to prevent regression of both fixes

Summary by cubic

Skip doc extraction during GitHub checkout so ask src/ask docs don’t fail on repos without docs/. Preserve symlinks when copying store entries to prevent ENOENT on cache hits.

  • Bug Fixes
    • ensureCheckout now sets skipDocExtraction: true on GithubSource.fetch, returning only the checkout path (files: []) for path-only callers; runInstall behavior is unchanged.
    • cpDirAtomic uses fs.cpSync(..., { verbatimSymlinks: true }) to keep relative symlinks intact and avoid broken links in the store.
    • Added regression tests for skip-doc-extraction behavior and for forwarding the flag from ensureCheckout.

Written for commit a2e2d51. Summary will update on new commits.

… in cpDirAtomic

Fixes `ask src github:gitbutlerapp/gitbutler` failing with "No docs directory
found" and a latent ENOENT on subsequent cache hits.

Bug 1 — GithubSource.fetch always called extractDocsFromDir, which is correct
for runInstall but wrong for ask src / ask docs (only the checkout path is
needed). Repos without a conventional docs/ directory threw through both the
shallow-clone and tar.gz paths. Added `skipDocExtraction?: boolean` to
GithubSourceOptions; three call sites in github.ts now skip extraction when
the flag is set, returning files: [] with the populated storePath.
ensureCheckout sets skipDocExtraction: true; runInstall is untouched.

Bug 2 — cpDirAtomic called fs.cpSync without verbatimSymlinks, so Node
resolved relative symlinks against the tmp source dir, baking absolute paths
pointing at the soon-deleted ask-gh-clone-* tmpdir into the store. Every
subsequent verifyEntry -> hashDir -> readFileSync then hit ENOENT on the
broken symlink. Gitbutler's claude.md/CLAUDE.md -> AGENTS.md relative
symlinks triggered this. Added verbatimSymlinks: true to the fs.cpSync call.

Regression tests added:
- 3 new tests in github.test.ts under "skipDocExtraction — callers that only
  need the checkout path": success without docs dir, master fallback + no docs,
  guardrail that default behavior still throws.
- 1 new test in ensure-checkout.test.ts verifying skipDocExtraction: true is
  forwarded to the fetcher.

Two CLAUDE.md gotchas added to prevent regression of both fixes.
@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. type:bug Something isn't working type:test Testing improvements or additions labels Apr 16, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 16, 2026

Codecov Report

❌ Patch coverage is 72.72727% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/cli/src/sources/github.ts 66.66% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying ask-registry with  Cloudflare Pages  Cloudflare Pages

Latest commit: a2e2d51
Status: ✅  Deploy successful!
Preview URL: https://20786671.ask-registry.pages.dev
Branch Preview URL: https://amondnet-src-docs-fallback.ask-registry.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 7 files

Requires human review: Modifies core fetcher and filesystem cache logic (symlink handling), which impacts the CLI's central data storage and retrieval paths. Requires human review for potential side effects.

Architecture diagram
sequenceDiagram
    participant CLI as CLI (ask src/docs)
    participant EC as ensureCheckout()
    participant GS as GithubSource
    participant Store as cpDirAtomic()
    participant FS as Node.js fs
    participant Remote as GitHub/Git

    Note over CLI,Remote: Request Flow for Checkout (ask src / ask docs)

    CLI->>EC: Request checkout for repo
    EC->>GS: fetch(repo, options)
    Note right of EC: NEW: Sets skipDocExtraction = true

    GS->>Remote: git clone / download tarball
    Remote-->>GS: Source files (in tmp dir)

    GS->>Store: Atomic copy to cache
    Store->>FS: fs.cpSync(src, dest)
    Note right of Store: CHANGED: verbatimSymlinks = true<br/>prevents absolute path resolution

    alt skipDocExtraction is TRUE
        GS->>GS: Skip extractDocsFromDir()
        Note over GS: Fixes crash on repos<br/>without /docs folder
    else skipDocExtraction is FALSE (e.g. ask install)
        GS->>GS: internal: extractDocsFromDir()
        alt No docs directory found
            GS-->>EC: Throw "No docs directory" Error
        else docs found
            GS->>GS: Generate file list
        end
    end

    GS-->>EC: Return { storePath, files: [] }
    EC-->>CLI: Return cached checkout path

    Note over CLI,FS: Cache Hit Flow (Next Request)

    CLI->>EC: Request same repo
    EC->>GS: fetch(repo)
    GS->>GS: verifyEntry(storeDir)
    GS->>FS: readFileSync() / hashDir()
    Note right of FS: Validates symlinks.<br/>Now succeeds because links<br/>remained relative.
    GS-->>EC: Return cached path
    EC-->>CLI: Return path
Loading

@amondnet amondnet merged commit 3837ee1 into main Apr 16, 2026
3 of 5 checks passed
@amondnet amondnet deleted the amondnet/src-docs-fallback branch April 16, 2026 15:40
@github-actions github-actions bot mentioned this pull request Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files. type:bug Something isn't working type:test Testing improvements or additions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant