feat(setup): report an unwritable target as a structured error (task 0044) #53
Reference in New Issue
Block a user
Delete Branch "task-0044-setup-fails-clean-on-unwritable-targets"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements
.claude/tasks/0044-setup-fails-clean-on-unwritable-targets.md.Summary
Both halves of
setupassumed the files they manage are writable.A declaratively managed target made the skill install raise a raw filesystem exception, and the hook install surface the underlying message with no guidance about what to do.
Both now fail with a new
TARGET_NOT_WRITABLEcode, naming the file and pointing at the general remedy — it appears to be managed by another tool, so declare the skill or hook through that configuration instead.The error names no particular manager, because read-only is not diagnostic of one.
A target already byte-identical to the bundled copy still succeeds, since nothing needs to be written.
Verified against the built binary as well as the fast tier: read-only file, read-only directory, unreadable file, and read-only
~/.claude/settings.json.Deviations
Two things go slightly beyond the literal criteria, both from review:
000file reports the same error rather than a raw exception.{path, detail}records rather than the SDK's flattened<path>: <message>text. This is correctness, not shape: testing the whole formatted string for an errno would misclassify an unrelated failure whose path happened to containEACCES.Two known limits are documented in the task's Implementation Notes rather than fixed: the hook error names the SDK's intended target rather than necessarily the blocking path (the SDK discards the error object), and an unwritable
settings.jsonstill leaves the Codex and OpenCode integrations installed because the SDK writes them first.Review
Risk
Overall: Low
AxiErrorCodemember and helpers with no other callers.git revert.find.setupis a one-shot install command, not a hot path.Unaddressed findings
Standards — Divergent Change (judgement call).
errors.tswas an HTTP-response classifier and now also holds filesystem errno predicates. Kept there because that module owns error classification and construction generally, not HTTP specifically — splitting one concept across two files to preserve the narrower reading seemed worse.Spec — partial state on the hook path. An unwritable
~/.claude/settings.jsonexits 1 with the other three integrations installed. Making the install transactional across integrations the SDK owns is larger than this task, and re-running after fixing the permission converges.All other findings were fixed and are in the diff: the flattened-string parse, the
-1slice, the errno false-positive on paths, thedeclareparameter name, the misattributed comment, and the stale error-code list in the spec.