Skip to content

Commit 0c4be25

Browse files
Shahinyanmclaude
andauthored
fix: task_create ENOENT on missing project dir (0.28.2) (#58)
project_hash::from_path canonicalized the project dir as its first op, and canonicalize returns ENOENT when the path does not exist yet. A Loom task session whose worktree wasn't checked out failed task_create before writing anything. On NotFound, fall back to std::path::absolute (no filesystem access); other canonicalize errors still propagate. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7dcf759 commit 0c4be25

5 files changed

Lines changed: 44 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.28.2] - 2026-06-18
11+
12+
### Fixed
13+
- **`task_create` ENOENT on first use in a Loom session.** Project-path
14+
resolution (`project_hash::from_path`) canonicalized the project directory,
15+
and `canonicalize` returns "No such file or directory" when the path does
16+
not exist yet — so a task session whose worktree wasn't checked out failed
17+
before writing anything, even though the data directory existed. When the
18+
path is merely absent, resolution now falls back to a lexical absolutisation
19+
that touches no filesystem; other canonicalize errors still propagate.
20+
1021
## [0.28.1] - 2026-06-18
1122

1223
### Fixed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ members = [
77
]
88

99
[workspace.package]
10-
version = "0.28.1"
10+
version = "0.28.2"
1111
edition = "2021"
1212
rust-version = "1.88"
1313
license = "MIT"

crates/tj-core/src/project_hash.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,21 @@ pub fn project_root(start: &Path) -> PathBuf {
3232
}
3333

3434
pub fn from_path(p: impl AsRef<Path>) -> anyhow::Result<String> {
35-
let canonical = dunce::canonicalize(p.as_ref())
36-
.with_context(|| format!("canonicalize {:?}", p.as_ref()))?;
35+
let p = p.as_ref();
36+
// `canonicalize` requires the path to EXIST — it returns ENOENT ("No such
37+
// file or directory") otherwise. A Loom task session can resolve its
38+
// project dir before the worktree is checked out, which made `task_create`
39+
// hard-fail on its very first path resolution. When the path is merely
40+
// absent (not a permission/other error), fall back to a lexical
41+
// absolutisation that touches no filesystem, so journal resolution still
42+
// works instead of erroring.
43+
let canonical = match dunce::canonicalize(p) {
44+
Ok(c) => c,
45+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
46+
std::path::absolute(p).unwrap_or_else(|_| p.to_path_buf())
47+
}
48+
Err(e) => return Err(e).with_context(|| format!("canonicalize {p:?}")),
49+
};
3750
let root = project_root(&canonical);
3851
let bytes = root.as_os_str().as_encoded_bytes();
3952
let mut h = Sha256::new();
@@ -58,6 +71,19 @@ mod tests {
5871
assert_eq!(a.len(), 16, "16 hex chars expected, got: {a}");
5972
}
6073

74+
#[test]
75+
fn nonexistent_path_falls_back_instead_of_erroring() {
76+
// Regression: `from_path` used to ENOENT on a path that doesn't exist
77+
// yet (canonicalize requires existence), which made task_create
78+
// hard-fail in a Loom session whose worktree wasn't checked out.
79+
let base = TempDir::new().unwrap();
80+
let missing = base.path().join("not/created/yet");
81+
let h = from_path(&missing).expect("must not fail on a missing path");
82+
assert_eq!(h.len(), 16);
83+
// Deterministic for the same absent path.
84+
assert_eq!(h, from_path(&missing).unwrap());
85+
}
86+
6187
#[test]
6288
fn different_paths_yield_different_hashes() {
6389
let d1 = TempDir::new().unwrap();

plugin/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "task-journal",
3-
"version": "0.28.1",
3+
"version": "0.28.2",
44
"description": "Append-only journal of AI-coding task reasoning chains: hypotheses, decisions, rejections, evidence. Renders compact resume packs so an agent can pick up a 2-week-old task with full context.",
55
"author": {
66
"name": "Mher Shahinyan"

0 commit comments

Comments
 (0)