Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- [Global store atomicity patterns](project_store_atomicity.md) — writeEntryAtomic/acquireEntryLock have known race windows; check before touching
- [Shell pipe stderr loss pattern](feedback_shell_pipe_errors.md) — curl|tar execSync strings erase real errors; require -fsSL or execFileSync
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: Shell pipe stderr loss pattern
description: execSync with curl | tar pipelines erases HTTP/network errors — require -fsSL or split into execFileSync calls
type: feedback
---

`execSync('curl -sL "<url>" | tar xz -C "<dir>"')` is a recurring anti-pattern in `packages/cli/src/sources/`. Three compounding failures:

1. `curl -s` silences stderr AND `curl -L` does not imply `-f` — curl exits 0 on HTTP 404/403/500, sending the error body into tar.
2. The pipe exit code is tar's, not curl's. Tar exits 0 on an empty input (or fails with a cryptic "unexpected EOF" that doesn't mention the HTTP status).
3. The caller's error is a generic "Failed to extract archive" with zero diagnostic detail.

**Why:** A reviewer (me, in PR #60) flagged this in `sources/github.ts:fetchFromTarGz`. Users hitting rate limits / private repos / wrong refs would get indistinguishable "Failed to extract archive" errors.

**How to apply:** Whenever new code uses `execSync` with a `curl | tar` pipeline, require either:
- `curl -fsSL` (the `-f` is load-bearing — fail on HTTP errors; `-S` shows errors even with `-s`), OR
- Split into `execFileSync('curl', [...])` followed by `execFileSync('tar', [...])` so each command's stderr surfaces independently. Prefer this when the URL or ref comes from user input, since it also avoids shell injection.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
name: Global store atomicity patterns
description: Known race/partial-state windows in packages/cli/src/store and how readers interact with them
type: project
---

Global docs store (`packages/cli/src/store/index.ts`) has two subtle atomicity issues surfaced in PR #60:

1. `writeEntryAtomic` does `rmSync(target)` then `renameSync(tmp, target)`. Between those calls the target does not exist, and **callers check `fs.existsSync(storeDir)` outside the lock** (e.g. `sources/npm.ts`, `sources/web.ts`, `sources/llms-txt.ts`). That existence check is the store-hit fast path, and it can return false mid-swap. The lock is the real source of truth — existence checks are best-effort.

2. `acquireEntryLock` on timeout calls `fs.unlinkSync(lockPath)` with no staleness check (no mtime, no PID file), then throws. A legitimately slow writer can have its live lock deleted by a timed-out peer, and a third process can then acquire the lock while the original writer is still mid-write.

**Why:** PR #60 introduces the global store and this is the first time concurrent writes across projects are possible. The docstring claims `writeEntryAtomic` is atomic; it is not across concurrent readers.

**How to apply:** When reviewing changes to `store/index.ts` or any caller of `writeEntryAtomic`/`acquireEntryLock`, flag any new code that (a) pre-checks `existsSync(entryDir)` outside the lock as authoritative or (b) touches the lock file without a staleness check. If fixing: rename target to `.old-<uuid>` first, then rename tmp, then rm old; add mtime-based staleness to unlink.
20 changes: 20 additions & 0 deletions .please/docs/product-specs/global-ask-docs-store-at-ask/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
id: SPEC-002
level: V_M
domain: global-ask-docs-store-at-ask
feature: spec
depends: []
conflicts: []
traces: []
created_at: 2026-04-11T05:52:35.393Z
updated_at: 2026-04-11T05:52:35.393Z
source_tracks: [global-docs-store-20260410]
---

# Global ASK docs store at `~/.ask/` Specification

## Purpose

Global ASK docs store at `~/.ask/` 관련 요구사항.

## Requirements
1 change: 1 addition & 0 deletions .please/docs/product-specs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
| Spec | Domain | Feature | Created | Related Tracks |
|------|--------|---------|---------|----------------|
| SPEC-001 | default-off-claude-code-skill- | spec | 2026-04-10 | [skill-emission-opt-in-20260410] |
| SPEC-002 | global-ask-docs-store-at-ask | spec | 2026-04-11 | [global-docs-store-20260410] |
2 changes: 1 addition & 1 deletion .please/docs/tracks.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
{"id":"rich-list-command-20260409","type":"feature","status":"in_progress","phase":"implement","issue":"#47","created":"2026-04-09","section":"active"}
{"id":"pm-driven-install-20260410","type":"feature","status":"review","phase":"finalize","issue":"#51","pr":"#52","created":"2026-04-10","section":"completed"}
{"id":"skill-emission-opt-in-20260410","type":"refactor","status":"planned","phase":"spec","issue":"#53","created":"2026-04-10","section":"active"}
{"id":"global-docs-store-20260410","type":"feature","status":"planned","phase":"spec","issue":"#55","created":"2026-04-10","section":"active"}
{"id":"global-docs-store-20260410","type":"feature","status":"review","phase":"finalize","issue":"#55","pr":"#60","created":"2026-04-10","section":"completed"}
{"id":"in-place-npm-docs-20260410","type":"refactor","status":"planned","phase":"spec","issue":"#56","created":"2026-04-10","section":"active"}
{"id":"skill-emission-opt-in-20260410","type":"refactor","status":"review","phase":"finalize","issue":"#53","pr":"#54","created":"2026-04-10","section":"completed"}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"track_id": "global-docs-store-20260410",
"type": "feature",
"status": "planned",
"status": "review",
"created_at": "2026-04-10T02:10:00Z",
"updated_at": "2026-04-10T02:10:00Z",
"updated_at": "2026-04-11T09:55:00Z",
"issue": "#55",
"pr": "https://github.com/pleaseai/ask/pull/57",
"pr": "#60",
"project": "",
"project_item_id": ""
}
Loading