# Talking Stick 0.1.4

Date: 2026-04-30

Patch release that clarifies the existing `force_new` semantics on `join_path`,
makes the resulting warnings visible to human CLI users, and stabilizes the
test suite.

## Fixed

### `force_new` no-op on exact-path joins now surfaces a warning

`join_path` has always had two shapes for `force_new=true`: it creates a nested
room when a different ancestor room exists, and it joins the existing room
when one already exists at the exact `canonical_path` (because
`path_rooms.canonical_path` is `UNIQUE`). The second shape is a deliberate
no-op — `force_new` cannot duplicate a room at the same canonical path — but
prior versions returned the existing room silently with `joined_existing_room:
true` and no further signal. Operators and harnesses who flipped `--force-new`
expecting a fresh room had no way to tell their request had no effect.

`join_path` now returns a `warning` field on this no-op path explaining what
happened and pointing the caller at the only remedy: join a distinct subpath
(for example a topic-scoped subdirectory) to get a fresh room. The nested-room
warning that has always existed is unchanged. Both warnings are now also
rendered in the default `tt join` text output, not just `--json`.

In practice this means a Claude Code or Codex harness asking for a new "topic
room" at the same path now sees an explicit signal that the API only knows
about path-scoped rooms, instead of silently working off the existing room.

The bundled skill's *While waiting* section also gains a more proactive
framing: the wait window is the right place to re-read the holder's last
handoff, follow its `artifacts[]`, investigate, and surface findings via
`add_note` — not idle sleep.

### Contention test no longer races the room-purge clock

`tests/talking-stick.test.ts > only one process can claim an idle room under
contention` reproduced as a `room_not_found` failure when the wall-clock date
crossed the `idleRoomTtlMs` window relative to the parent test's fake clock
fixed at 2026-04-22. The contention worker spawned in
`tests/fixtures/claim-worker.ts` constructed its `TalkingStickService` with
the real `Date.now()`, so the worker's `purgeExpiredIdleRooms` call evicted
the room that the parent had just created under the fake clock. The fix
threads the parent's fake-clock ISO timestamp into the worker so the service
shares the same `now()` source, and the test passes deterministically
regardless of wall-clock date.

### Identity-resolver memoization test no longer asserts internal call counts

`tests/mcp-server.test.ts > createConnectionIdentityResolver > memoizes
derived identity per session and re-derives on override` was written when
`deriveMcpHarnessIdentity` made one `inspector.inspect` call per derive.
Since 0.1.3's ancestry-walk fix (commit `ab4e843`), a single derive may walk
multiple parent processes. The resolver-level memoization is still correct;
the test was asserting the wrong invariant. Updated to assert call-count
*deltas* across resolver invocations (no growth on memoized hits, growth on
fresh sessions and overrides) instead of absolute counts, so the test
remains robust to changes in ancestry-walk depth.

## Verification

- `npm run typecheck`
- `npm test` — 225 tests across 14 files, all passing under Node 24.11.0
- `npm run build`
- `git diff --check`
- `npm pack --dry-run --ignore-scripts`
