Skip to content

Commit a943e53

Browse files
Shahinyanmclaude
andcommitted
fix(v0.6.2): async classifier — kill the fork-bomb
ingest-hook ran the classifier (nested `claude -p`) synchronously, blocking each Claude Code hook 5-30s. PostToolUse fired faster than the classifier finished, so ingest-hook procs piled up. The nested claude also loaded every installed plugin, including task-journal itself, spawning extra task-journal-mcp servers per call. After a few minutes WSL2 died on `EAGAIN: pthread_create`. Fix: - Real-classifier ingest-hook now writes an enriched pending entry (schema v2: kind/text/project_hash/events_path/backend) and spawns a detached `task-journal classify-worker` child. Hook returns <100ms instead of 5-30s. Mock path stays sync (tests rely on it); TJ_INGEST_SYNC=1 forces sync mode for the real path too. - Hidden `classify-worker` subcommand drains v2 pending entries. Holds project-scoped file lock at `state_dir/classifier-<project_hash>.lock` — at most one worker per project. Stale locks (dead PID via `kill(pid, 0)`) are reclaimed automatically. - Bare `claude` invocations now get `--strict-mcp-config --mcp-config '{"mcpServers":{}}'` injected. Inner haiku-claude no longer spawns task-journal-mcp (or any other MCP server) per classification. Wrappers like `aimux run dt` are detected via non-empty base args and left untouched. `--bare` not used because it breaks subscription auth (claude-memory-0kk); `--no-plugins` does not exist in claude 2.1.x. Tests: 259 passed, 0 failed. Three new tests cover async hook speed, worker behavior on classifier failure, and lockfile contention. Closes claude-memory-9ty. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 1d74549 commit a943e53

11 files changed

Lines changed: 663 additions & 11 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
},
77
"metadata": {
88
"description": "Task Journal — append-only reasoning chain memory for AI-coding tasks",
9-
"version": "0.6.1"
9+
"version": "0.6.2"
1010
},
1111
"plugins": [
1212
{
1313
"name": "task-journal",
1414
"source": "./plugin",
1515
"description": "Append-only journal of AI-coding task reasoning chains. Captures hypotheses, decisions, rejections, evidence — renders compact resume packs so an agent can pick up a 2-week-old task with full context.",
16-
"version": "0.6.1",
16+
"version": "0.6.2",
1717
"author": {
1818
"name": "Digital-Threads"
1919
},

CHANGELOG.md

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

88
## [Unreleased]
99

10+
## [0.6.2] - 2026-05-09
11+
12+
Fork-bomb fix. Synchronous classifier in `ingest-hook` was blocking
13+
each Claude Code hook for 5-30s while spawning a nested
14+
`claude -p` that loaded all installed plugins (including
15+
task-journal-mcp itself), so within minutes ~19 stale
16+
`task-journal ingest-hook` and `task-journal-mcp` processes piled up
17+
and WSL2 died on `EAGAIN: pthread_create`.
18+
19+
### Fixed
20+
- `ingest-hook` no longer blocks on the classifier. Real-classifier
21+
events are queued to `pending/<id>.json` (schema "v2") and a
22+
detached `task-journal classify-worker` child drains them in the
23+
background. Hook returns in <100ms instead of 5-30s. Mock-classifier
24+
path stays synchronous (tests rely on it); set `TJ_INGEST_SYNC=1`
25+
to force sync mode for the real path too.
26+
- `tj_core::classifier::cli::ClaudeCliClassifier` injects
27+
`--strict-mcp-config --mcp-config '{"mcpServers":{}}'` automatically
28+
when the configured command is bare `claude` (no wrapper). Wrappers
29+
like `aimux run dt claude` are detected by non-empty base args and
30+
left alone — wrappers may not pass through unknown flags. Stops the
31+
inner haiku-claude from spawning task-journal-mcp (and ~24 other MCP
32+
servers) per classification. `--bare` not used because it breaks
33+
subscription auth (claude-memory-0kk); `--no-plugins` does not exist
34+
in claude 2.1.x CLI.
35+
- New project-scoped worker lockfile at
36+
`state_dir/classifier-<project_hash>.lock` caps in-flight
37+
classifier workers at 1 per project. PID is written to the lockfile
38+
on acquire; stale lockfiles (dead PID) are reclaimed automatically.
39+
40+
### Added
41+
- Hidden `task-journal classify-worker --backend <cli|api>`
42+
subcommand. Internal — spawned by `ingest-hook`. Not stable API.
43+
44+
### Changed (internal)
45+
- `pending/<id>.json` gained a `"schema"` field. v2 entries carry
46+
`kind`, `text`, `project_hash`, `events_path`, `backend` and route
47+
through `classify-worker`. v1 entries (legacy `text`+`error` shape)
48+
still parse and route through the existing `pending retry` path.
49+
1050
## [0.6.1] - 2026-05-08
1151

1252
Branch-name regex was too greedy and captured the next word after any

Cargo.lock

Lines changed: 4 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.6.1"
10+
version = "0.6.2"
1111
edition = "2021"
1212
rust-version = "1.88"
1313
license = "MIT"

crates/tj-cli/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ name = "task-journal"
1616
path = "src/main.rs"
1717

1818
[dependencies]
19-
tj-core = { package = "task-journal-core", version = "0.6.1", path = "../tj-core" }
19+
tj-core = { package = "task-journal-core", version = "0.6.2", path = "../tj-core" }
2020
anyhow = { workspace = true }
2121
clap = { workspace = true }
2222
tracing = { workspace = true }
@@ -30,6 +30,9 @@ ratatui = { workspace = true }
3030
crossterm = { workspace = true }
3131
tempfile = { workspace = true }
3232

33+
[target.'cfg(unix)'.dependencies]
34+
libc = "0.2"
35+
3336
[dev-dependencies]
3437
assert_fs = { workspace = true }
3538
predicates = { workspace = true }

0 commit comments

Comments
 (0)