NEXO 6.0.5: Strict pre-tool guardrail stops emitting “unknown target”

Published April 17, 2026 · by Francisco Cerdà Puigserver

The bug that shipped in 6.0.2 and survived until 6.0.4

Several Claude Code builds deliver PreToolUse without a session_id field. Until 6.0.5, src/hook_guardrails.py::process_pre_tool_event consulted only payload["session_id"]. With no id, _resolve_nexo_sid returned an empty string, the strict branch recorded a strict_protocol_write_without_startup debt, and the formatter emitted:

NEXO STRICT MODE BLOCKED THIS EDIT:
- Start the shared-brain session first: call `nexo_startup`, then `nexo_task_open`, before editing (unknown target).

Operators who had already called nexo_startup, opened a protocol task, acknowledged guard warnings and tracked the target file still saw the block on every Edit/Write. Tracked as learning #411. 6.0.3 shipped a partial fix for the handle_guard_check side of the same family but did not cover the missing-payload case for edits.

The fix

The SessionStart hook already writes the active Claude Code session UUID to $NEXO_HOME/coordination/.claude-session-id. 6.0.5 adds a fallback helper that reads it:

claude_sid = str(payload.get("session_id", "") or "").strip()
if not claude_sid:
    claude_sid = _read_claude_session_id_from_coordination()
sid = _resolve_nexo_sid(conn, claude_sid)

The lookup walks $NEXO_HOME/coordination/.claude-session-id, then ~/.nexo/coordination/.claude-session-id, and stops at the first non-empty value. Fail-closed is preserved: if neither the payload nor the file yields an id, the guardrail still blocks with missing_startup. No behavioural change for callers that already supply session_id.

Tests that silently regressed since 6.0.2

Three pre-tool tests had been failing quietly:

pytest joins CI

The real reason those regressions shipped is that CI only ran ruff, bandit, verify_release_readiness and verify_client_parity. None of those execute the test suite. 6.0.5 adds .github/workflows/tests.yml which runs pytest tests/ -q --maxfail=5 on every PR and push to main. Merges are blocked on failure.

Merged alongside

Two open tails

Two tests/test_protocol.py cases assert a handle_task_close / cognitive-trigger shape that no longer exists. Marked pytest.mark.xfail(strict=False) for 6.0.5 so the new gate stays green and tracked in followup NF-TEST-PROTOCOL-API-REFACTOR.

Suite

1093 passed, 2 xfailed, 1 skipped.