Skip to content

Kherep 0.3.0

Choose a tag to compare

@cfaysal cfaysal released this 15 Oct 18:19
· 437 commits to main since this release

Added

  • Closed-session cwd refusal code (issue #396): a closed-session delivery that
    the recorded working directory refuses (not absolute, missing, or outside
    the workspace roots) now waits with waiting/cwd-refused instead of
    waiting/wake-not-authorized, and msg status on the sender says so. Nodes
    advertise messaging.progress.v2; the Worker sends wake-not-authorized
    to a sender without it. Upgrade the Worker before the nodes: an older
    Worker rejects the new code, and the target node retries the report.
  • Offer provenance (issue #417): an inbox record carries offeredBy
    (wake, prompt, stop, tool, codex-prompt, receive, codex-wake,
    intercom, mcp) with offeredAt; the wake listener leaves
    listeners/<session_id>.woke.json, which the woken turn's
    UserPromptSubmit claims within 30 s; msg status and msg inbox show
    offered by; msg status <id> now answers for a message in this node's
    inbox (exit 0 instead of 1).
  • Managed node daemon service (issue #404): kherep-node service install,
    upgrade, status and rollback manage a per-user macOS LaunchAgent or
    Windows Scheduled Task from a verified immutable source pin. Upgrade and
    rollback retain exact native definition and security backups, verify daemon
    ownership, authenticated reconnection and loaded source identity, and refuse
    service drift, unsafe adoption, uncertain process identity and active
    descendants before stopping a service. The Windows task uses a fixed Node
    supervisor that preserves the selected log, verifies the source before every
    child start and restarts an exited daemon after 30 seconds. A generation-bound
    one-use permit admits only the transaction's candidate while task admissions
    are drained, and stop retains and terminates the selected supervisor before
    its exact daemon child. Doctor now compares the daemon's
    recorded startup source and verified hook pins with the invoking checkout.
  • Sender receipts (issue #402): when a message reaches delivered (the target
    session's turn that carried it completed), the sending session's next
    delivery hook event shows one line, Your message <id> to <node>/<session> was received (confirmed <updatedAt>)., once per message (noticedAt), for
    Claude Code and Codex senders. The time is when the sender's node recorded
    delivered. A receipt matches the sender by session id (fromSessionId),
    and by fromSession only for a record without it (an older sender), so a
    renamed session still gets it and a later session reusing the name does not.
    No receipt before delivered, nor when the message is replied or its reply
    already arrived.
    A receipt never wakes a session or continues a turn: Stop shows it only
    beside messages or failure notices, and no wake entry or turn budget is used.
    Receipts never cost a peer message slot or block: they take only the room
    left after the messages and wait otherwise; more than 5 are
    counted in one line. The intro names receipts, failures or both. Failure
    notices are unchanged. Selection lives in
    modules/control-plane/node/sent-notices.mts.
  • Observation result check: a new SubagentStop hook, obs-result-check.mts,
    wired with matcher claude-obs, checks the agent's OBS-RESULT status line
    through claude/hooks/lib/obs-result-policy.mts. The first non-empty line is
    the only OBS-RESULT line; wrote <n> <ids> needs n positive and exactly n
    numeric ids; empty and failed need a reason whose first word is no status
    word, which catches failed wrote 4 pages: .... A malformed line is sent
    back to the agent once with a fixed instruction (decision: "block"); on the
    continuation, and for a valid failed, the operator gets a systemMessage
    instead. The message comes from last_assistant_message, else the agent
    transcript. No message text reaches stdout, and any error is one
    systemMessage with exit 0.
  • Codex observation candidate check: a new Codex SubagentStop hook,
    codex-obs-result-check.mts, wired with matcher codex-obs in the last hook
    group of the managed block, checks the worker's candidate through
    codex/hooks/obs-candidate-policy.mts: exactly one strict JSON document whose
    only key is an observations array, each candidate with exactly title,
    bodyStorage, evidence, labels and placement. A fenced or otherwise
    malformed candidate is sent back to the worker once (decision: "block"); on
    the continuation the operator gets a systemMessage and the Maestro publishes
    nothing. The message comes from last_assistant_message, else the subagent
    rollout; when neither is visible the hook says so. It prints one JSON document
    or nothing, never message text. An upgrade appends only this group, so every
    existing positional trust key stays (issue #326).
  • Attribution log: a new git pre-push hook beside commit-msg records one
    line per pushed ref in the node's local
    <config root>/control-plane/attribution.jsonl (mode 0600) for
    repositories under the workspace, and always exits 0. The session is
    CLAUDE_CODE_SESSION_ID, else KHEREP_SESSION_ID, else a marker the Codex
    PreToolUse phase leaves for that repository within 120 seconds, else
    unknown. gh pr create never reaches git, so
    modules/control-plane/node/attribution-hook.mts records it at
    PostToolUse, from the checkout, in Claude for Bash and PowerShell and in
    Codex for the shell tools; the PR number comes from the tool output, else
    gh pr view, else it stays null. A record holds ids, owner/name, the
    toplevel, branch, remote ref, SHA and PR number, never commit subjects, PR
    text, tokens or the environment; every append drops records older than 90
    days. kherep-node attribution [--branch] [--pr] [--repo] [--since <hours>] [--json] reads it. The token walk of the main-checkout guard moved to
    claude/hooks/lib/command-walk.mts and serves both hooks.
  • Main-checkout guard: a new PreToolUse hook, main-checkout-guard.mts,
    keeps the main checkout of a workspace repository (git-dir equals
    common-dir) on its default branch, because sessions share it. It blocks
    git checkout <ref> without -- (a SHA included), checkout -b, -B,
    --orphan, --detach, and git switch to any branch other than the
    default, including -c, -C and --detach, with exit 2 and a pointer to
    git worktree add <path> -b <branch>. File checkouts, restore,
    worktree add, pull, fetch, merge, rebase, linked worktrees and
    repositories outside the workspace pass. The default branch is
    origin/HEAD, else a local main or master; without one it warns and
    exits 0. The inline KHEREP_MAIN_CHECKOUT=switch prefix, or
    $env:KHEREP_MAIN_CHECKOUT='switch'; in PowerShell, lets one approved
    command through; a persistent variable is ignored. Claude runs it for Bash
    and PowerShell in a new Bash|PowerShell group; Codex runs it through the
    hook adapter as the last entry of the shell group, so a block arrives as a
    JSON deny. A new git post-checkout hook beside commit-msg warns, and
    always exits 0, when a branch checkout leaves such a main checkout off its
    default branch; it is silent in linked worktrees, after worktree add,
    during a rebase and with the marker. critical-file-integrity.mts guards it
    like commit-msg (issue #325).
  • Confluence brokers: new read-only verb list --space <key> [--title-contains <text>] [--label <name>] [--limit <n>]. It reads every
    page of the space through the paginated v2 pages endpoint, filters titles by
    case-insensitive substring in the broker, and filters by label through one
    v1 CQL query that follows _links.next. It prints one page row per match,
    then total:, shown: and truncated:. The total is counted by reading
    every result page, never taken from totalSize; only --limit truncates.
    Exit 0 for any completed read, including total: 0, and exit 1 for a
    Confluence or argument error, which prints no total:. The space index now
    stops on a cursor that adds no new page, not only on an empty page
    (issue #315).
  • Confluence brokers: labels --id <page> --keep-runtime <runtime-label>
    repairs a page that carries two runtime labels. It deletes the other one,
    reads the labels back and prints runtime: kept, runtime: removed and
    labels:; it exits 1 when the removed label is still there. It cannot be
    combined with --labels or --remove, and it refuses without writing when
    the value is not a runtime- label, when the page carries one, none or more
    than two runtime labels, or when the named label is not among them. The
    labels verb now lives in the shared confluence-label-cli.mts, which the
    installers, drift checks and smoke test project beside the brokers
    (issue #318).
  • Atlassian destructive guard: a new PreToolUse hook,
    atlassian-destructive-guard.mts, stops every mcp__<server>__executeDestructive
    call, under any server name, the managed atlassian server, a plugin
    namespace and a claude.ai connector named by a UUID included. Claude asks the
    operator to confirm the call (permissionDecision: "ask"), wired with
    matcher mcp__.*__executeDestructive. Codex hooks cannot ask, so Codex denies
    it with --runtime codex; the guard is the last entry of the existing
    Agent, web and MCP PreToolUse group, because a new group would move the
    positional trust key of every operator PreToolUse group after the managed
    block. The installer recognises the exact block from immediately before the
    guard and replaces it; Codex asks to review that one entry. Read and write
    calls pass (issue #376).
  • Claude mid-turn delivery: the control-plane delivery hook also runs at
    PostToolUse, as the last entry of the all-tools group, so a Claude Code
    session in a running turn receives accepted peer messages next to the result
    of its next successful tool call instead of only at the turn's end
    (node/deliver-midturn.mts). The call marks them offered; only the turn's
    Stop confirms them delivered (issue #308 rule), and that Stop and a later
    UserPromptSubmit inject them no second time. Calls from a subagent or
    in-process teammate (agent_id) and calls in permission mode
    bypassPermissions inject nothing. A per-session lock file keeps the
    concurrent hooks of parallel tool calls from offering a message twice. The
    call starts no turn and neither checks nor spends the autonomous-turn budget.
    kherep-node doctor counts the new entry with the other delivery hooks
    (issue #388).

Changed

  • Control Plane wake is on by default (operator decision of 2026-10-12). A
    node policy.json without a wake section now acts as
    { "enabled": true, "sessions": ["*"], "codexApp": true, "replies": true },
    so an existing node that accepts messages wakes its idle sessions for them
    after the upgrade; "wake": { "enabled": false } opts out, and a malformed
    section still fails closed. Messaging admission keeps failing closed: a file
    without a messaging section still accepts nothing. The rule for every
    sender, { "accept": [{ "session": "*", "from": ["*"] }] }, is written
    explicitly by onboard into a new policy.json together with the default
    wake section (an existing file is never rewritten), and a missing file
    reads as that default policy. The default autonomous-turn budget rises from
    6 per hour and 20 per day to 20 per hour and 100 per day (30 s spacing
    unchanged), and a wake.budget with perDay alone is no longer compared
    with the default hour. kherep-node doctor reports wakeSource (file or
    default) and messagingSource (file, absent for a file without the
    section, default for a missing file), and no longer reports
    "enabled": false as a rejected wake section (issue #390).

  • Codex hooks: one shell dispatcher retains commit, deploy and checkout checks
    and pre-tool attribution while removing redundant adapter starts. Post-edit
    dispatch skips four watcher children for normalized Bash inputs, which all
    four watchers already reject. Per-turn reminders retain their rules with
    less text. The synthetic fixture runs 41 rather than 65 hook commands and
    85 rather than 121 processes; native Desktop counts remain unmeasured.
    Exact upgrades preserve operator state and require review of the new hook
    definition (issue #386).

  • Atlassian MCP: with the optional Atlassian tool set, both runtimes reach the
    v2 Atlassian remote MCP server (https://mcp.atlassian.com/v2/mcp) as their
    own Atlassian service account instead of the operator's personal OAuth login.
    The service account's API key lives in a private file, read only by the
    secret-file wrapper supergateway-secret-wrapper.mts, which sends it as
    Authorization: Bearer. The file is KHEREP_ATL_MCP_TOKEN_FILE_CLAUDE or
    KHEREP_ATL_MCP_TOKEN_FILE_CODEX, by default
    <runtime home>/kherep/atl-mcp-credential-<runtime>.txt. A file that is also
    the other runtime's key file or a broker credential file is refused, and so is
    one that is not absolute, regular, private and owned by the installing user.
    Codex: the managed atlassian table runs the wrapper and sets
    approval_mode = "prompt" for executeDestructive; a block with the
    previous native url table, or with a table for another key file, is
    recognised and replaced, and the receipt reports atlassianMcp with its
    status and tokenSource, never the path or the key. Claude: the installer
    registers the user-scope stdio server atlassian through claude mcp add-json, reads it back from ~/.claude.json, and only then disables
    atlassian@claude-plugins-official, whose server ran as the personal login;
    the plugin's five skills go with it. The plugin manifest no longer enables
    it. An atlassian entry the installer did not write stays, and so does the
    plugin. Without a usable key file no Atlassian server is written on either
    runtime, nothing falls back to OAuth, the rest of the installation stands and
    the installer exits 1 with a warning. The Claude step has the skip rules of
    the credential step: KHEREP_INSTALL_SKIP_ATL_MCP=1,
    KHEREP_INSTALL_PREVIEW=1, and a non-default CLAUDE_HOME without
    KHEREP_INSTALL_ALLOW_ATL_MCP=1 (issue #376).

  • Research evidence: a v2 executeRead whose operation is searchConfluence
    or searchConfluenceUsingCql counts as a Central Brain lookup on both
    runtimes (issue #376).

  • The secret-file wrapper reports a failure as MCP secret wrapper failed safely instead of naming n8n, since it now also carries the Atlassian
    server (issue #376).

  • Control plane Worker: a message is deleted once its sender acknowledged the
    final status (message.status.ack, delivered, replied, refused or
    expired). The acknowledgement must come from the sending node and name the
    stored final state; a repeat is a no-op. Messages from the operator API and
    from senders without messaging.ack.v1 are deleted 24 hours after their
    final state, and a revoked node's final messages at once; queued and
    accepted messages are never deleted. A 24-hour tombstone (ids, nodes,
    final state, reason, reply depth, reply id; no text, no sessions) answers a
    late resend, a late target report, MCP status and task-control
    provenance. The Worker now stores every message's reply depth and refuses a
    reply beyond MAX_REPLY_DEPTH with reply depth exceeded. An MCP reply
    to a deleted message reads that one item from the replying node's inbox
    (mcp.inbox.request with messageId and reply; the node marks it
    answered); an older node fails it closed. Messages stored before this
    deploy are never deleted, whatever acknowledgements, replays or revocations
    reach them. GET /api/messages no longer lists acknowledged messages; the
    audit keeps their history without text. All new queries are index-backed
    and covered by the read-budget test (issue #308, PR 4 of 4).

  • Atlassian MCP: Kherep targets the v2 Atlassian remote MCP server. The Claude
    capability check no longer expects a rovo server. With the optional
    Atlassian tool set the Codex installer writes the managed MCP server
    atlassian (https://mcp.atlassian.com/v2/mcp) instead of registering the
    legacy atlassian-rovo@openai-curated plugin, retires the v1 rovo table,
    and recognises a managed block an earlier installer wrote with or without
    that rovo table; tables outside the block stay untouched. The install
    receipt drops nativePlugins. The research-evidence matchers count the v2
    searchConfluence tool under any server name, including a claude.ai
    connector named by a UUID (issue #304).

  • Codex delivery hook: the reason of a blocked Stop now starts with
    Kherep message delivery (not an error): (CODEX_STOP_MARKER), because the
    Codex desktop app shows the block as "Blocked · Stop · User · Feedback" with
    the reason beneath it. The retrieval command, the escalation note, the
    continuation budget and the 6 KiB limit are unchanged; the Claude Stop
    path is untouched (issue #359).

Fixed

  • Claude sign-in around install.sh (issue #403): from the start of the
    dependency phase to the end-of-install readiness check, the installer keeps
    install.lock in an enrolled node's control-plane directory. While that
    file is younger than 30 minutes the daemon starts no new Claude readiness
    probe and logs probe deferred: install in progress once; a probe already
    running is marked in flight and the installer waits for it, at most 60
    seconds. When the file is gone the daemon probes Claude once more, also after
    a ready verdict, so a sign-out during the installation is not advertised as
    ready. The installer removes the file after the check and in its exit trap;
    it still does not restart the daemon. After writing the file, a
    claude auth status --json pre-check warns when Claude already reports no
    sign-in and never blocks the installation. At the end,
    bootstrap/claude-readiness-check.mts runs the daemon's own probe once and
    prints install: claude ready, `install: WARNING claude SIGN-IN LOST (...)
    • run 'claude auth login'(the installation exits 1 with its ownDONE WITH ERRORS (Claude sign-in lost ...)verdict), orinstall: claude
      probe inconclusive (...)(a warning, also for a login renewal another Claude process holds).KHEREP_INSTALL_PREVIEW=1, KHEREP_INSTALL_SKIP_CLAUDE_READINESS=1and a candidateCLAUDE_HOME` skip
      the pre-check and the probe. The installation guide now advises against
      killing the daemon's process tree.
  • Codex headless broker research (issue #405): the research reminder explains
    command-scoped approval when sandbox networking blocks a read-only search.
    The documented codex exec --approve-for-me invocation retains the
    workspace-write sandbox and makes the escalation reviewable. An unavailable
    lookup remains UNKNOWN; no sandbox defaults or privacy guards are changed.
  • Codex research Stop hook: a Central Brain broker search run through the Codex
    Desktop code-mode wrapper counts as a research attempt. Codex Desktop
    records that wrapper as a custom_tool_call named exec without a
    namespace field, which the parser accepted only as functions.exec or with
    namespace: "functions", so a substantial turn got the "missing a Central
    Brain research attempt" continuation after a lookup it had run. The
    unnamespaced form is accepted only as a custom_tool_call; a bare exec
    function_call and other namespaces still do not count. The nested
    tools.exec_command(...) call counts as before, also inside
    Promise.allSettled, with its output collected through write_stdin. Text in
    comments, strings, user messages, tool output and item_completed records
    still does not count (issue #398).
  • Codex upgrade (issue #397): a managed block that the Codex app split around
    its trust tables and that names an earlier checkout or source pin before the
    control-plane deliver-hook.mts and attribution-hook.mts is upgraded
    instead of refused, also with an earlier Node executable; any other
    difference in the block is still refused. An upgrade keeps a managed
    atlassian table: without KHEREP_INSTALL_ATLASSIAN_TOOLS the tool set is
    installed again with the key file the installed table names (receipt
    tokenSource installed), an unusable key file stops the install before
    it writes, and an explicit opt-out records atlassianMcp.status removed.
    kherep-node doctor counts a hook in a source pin of the checked-out commit
    as this checkout and lists it under pinned; a pin of another commit is
    still reported as another checkout.
  • Control plane messaging: closed-session delivery (messaging.resumeClosed)
    no longer takes a session for closed because one session listing did not
    show it. It readdressed every waiting message for such a session to an
    intercom session of its sender, so a reply (--reply-to) to a message that
    session S sent could land in the replying peer's background intercom session
    on S's node while S was still open and never saw it. A target now counts as
    closed only after 3 consecutive successful listings without it, the first at
    least 90 seconds before the last (about 2 minutes at the 60-second listing
    interval). A listing that shows the session resets the count; a failed
    listing, and a partial one for the runtimes it could not see (no claude on
    PATH, a listing at its 512-session cap), counts no miss. The count is kept
    in known-sessions.json, survives a daemon restart and is pruned with the
    session's entry; a target the node never listed is counted from its first
    waiting message on. Until then every message, a reply or not, stays
    addressed to S and waits with waiting/awaiting-user-turn, and is refused
    with target session not running when S does not return within 60 minutes.
    Once S is closed, a reply reaches an intercom session of its sender like any
    other message (issues #102, #105). msg status on the original sender lists,
    under each reply that a node handed over, the session it was forwarded to and
    its delivery task and session (issue #389).
  • Atlassian MCP: the Claude step reports every local-scope atlassian entry in
    ~/.claude.json that takes precedence over the managed service-account server
    in its directory, with the removal command, leaves it as it is and exits 1.
    Before, it reported the installation as configured while sessions in that
    directory reached a personal OAuth connection. The installation guide lists
    the agent-interface scopes the v2 server requires per tool group; keys with
    only the classic product scopes authenticate but every tool call answers 403
    (issue #382).
  • Atlassian credential step: a host without KHEREP_ATL_PROJECT_KEY in the
    installing shell could not prove a working broker credential. Both Jira
    brokers validated the full project seed before selftest, although the check
    proves only the OAuth client and the site; selftest now needs
    KHEREP_ATL_SITE and nothing else. When a broker still stops on a
    KHEREP_ATL_* configuration error, bootstrap/atl-credential.mts names that
    variable, leaves the credential file untouched and asks nothing, instead of
    reading the missing verdict as inconclusive and offering to replace a working
    credential. A missing verdict now reads NO-VERDICT, no longer the word the
    Codex broker uses for a check that ran and could not decide (issue #379).
  • atlassian-broker: the Claude agent wrote Confluence pages through
    twg confluence content ..., which runs as the operator's personal TWG
    login, against the rule that every Atlassian write runs as a service account.
    It now uses the Claude Confluence broker atl-confluence-ccoder.mts. The
    Codex agent was rendered from the Claude definition and named the Claude Jira
    broker; it has its own definition, codex/agents/atlassian-broker.md, with
    the Codex brokers atl-jira.mts and atl-confluence.mts (issue #376).
  • claude-obs reads its host configuration <Claude home>/kherep/confluence.json
    with the Read tool instead of cat, which the auto-mode classifier denied.
    The user settings allow exactly that one file with a new rule,
    Read(//__KHEREP_CLAUDE_HOME__/kherep/confluence.json). The renderer writes
    it in the absolute POSIX form Claude Code matches on every platform:
    Read(//c/Users/<user>/.claude/kherep/confluence.json) on Windows,
    Read(//Users/<user>/.claude/kherep/confluence.json) on macOS (issue #363).
  • main-checkout-guard and the attribution hook: a repository counts as
    inside the workspace when its path matches the workspace as written or
    after both sides are resolved on the file system. A workspace or checkout
    given as a Windows 8.3 short name, or reached through a junction or
    symbolic link, is now guarded instead of passing unchecked. Both use the
    shared isWithinWorkspace() in real-path-policy.mts (issue #346).
  • Privacy boundary guard: a path given as a Windows 8.3 short name on one
    side and as the long name on the other now meets. Before, a credentials or
    local-inference root set long and a Read, Bash or Agent payload naming it
    short, or the other way round, was allowed. Roots and candidate paths are
    now compared in both canonical forms, the JS realpath that keeps short
    names and the native one that expands them, through the new
    canonicalPathForms() in real-path-policy.mts (issue #348). The check is
    additive: nothing denied before is allowed now. The PowerShell tool is
    covered as well since issue #358.
  • Privacy boundary guard: the PowerShell tool is classified like Bash. Before,
    every PowerShell call passed unchecked, so Get-Content, gc, Copy-Item,
    -LiteralPath, pwsh -Command, $env: expansion of a protected root and
    direct HTTP to the local inference ports were allowed, and an empty command
    did not fail closed. The shell walk in private-path-policy.mts, which Bash
    and Codex use too, now follows sl, reads a colon-attached parameter value
    (-LiteralPath:<path>), and walks the script of bash -c, pwsh -Command
    and the other shells in command-walk.mts in the current directory. A
    protected path split across a directory change and a relative path inside
    such a script is now denied for both tools. The check is additive and the
    deny texts are unchanged. Not covered: relative .NET paths, Join-Path,
    string concatenation and backtick-escaped spaces (issue #358).
  • Deploy guard: the Forge rules only count a flag in the arguments of the
    same forge deploy or forge install. Before, they matched forge deploy
    and -e production, or forge install and -s, anywhere in the command,
    so forge deploy -e development && grep -e production log.txt, a dev
    deploy followed by forge install -e production, and an install followed
    by rsync -s a b were blocked. They now also block what they let through:
    -e "production", --environment 'production', -eproduction,
    -e prod, --site "<site>", forge.cmd, forge.exe and
    npx @forge/cli. A production* value still blocks, forge install list
    is read-only, and an install without a site stays allowed. The new
    hooks/lib/forge-match.mts reuses the segment scan of
    hooks/lib/git-push-match.mts, now exported as segmentVerdict, so
    quoted words are still scanned again as shell and quoted text that spells
    a complete forge command stays blocked. When the command cannot be parsed
    the old regexes decide, so both rules still fail closed. forge tunnel
    keeps its whole-string rule (issue #328).
  • Deploy guard: rule 4 now blocks forge tunnel only when tunnel is the
    verb of a forge invocation, read with the segment scan of rules 1 and 2.
    It now also blocks forge.cmd, forge.exe, a Windows path to
    forge.cmd, npx @forge/cli with or without a version, and a flag before
    the verb. A mention with punctuation on the verb, a backticked mention in a
    heredoc body and JSON text pass. Quoted text that spells the complete
    command, such as echo "forge tunnel", stays blocked (#327 decision 2).
    forge TUNNEL passes, because the verb is case-sensitive. When the command
    cannot be parsed the old regex decides, so the rule fails closed. The
    deploy-auth token still never unblocks rule 4, and the guard footer now says
    so instead of offering it (issue #347).
  • Control Plane worker: an npm override raises sharp from 0.35.4 to
    0.35.5 (CVE-2026-96889, Dependabot alert #12). miniflare pins
    sharp to exactly 0.35.4, also in its latest release, so Dependabot could
    not raise it on its own. The override can go once miniflare requires a
    patched sharp. sharp is a development dependency only and is not part
    of the deployed Worker. @cloudflare/workers-types moves to
    5.20261008.1, the peer range wrangler 4.148.0 asks for; without it the
    lockfile could not be regenerated (issue #343).
  • Plugin reconciliation: claude plugin list --json now also reports rows
    with "scope": "synced", plugins synced from claude.ai, and one such row
    made reconcile-plugins fail with Claude plugin list contains a malformed entry, so the install ended with errors and no plugin was reconciled.
    synced is now a known scope: the row is read for duplicate detection and
    never installed, enabled or removed, like project and local. A
    well-formed row with a scope the installer does not know is skipped with
    one line per scope naming the plugin ids, so a future scope does not break
    the install either. A row that is malformed in shape, including a
    non-string scope, stays fatal. A declared plugin that is present only as a
    synced row still gets its own user-scope install; the synced row is left
    untouched (issue #338).
  • Deploy guard: the force-push rule only counts a force flag in the
    arguments of the same git push. Before, it matched git push and a force
    flag anywhere in the command, so gh api … -f body=x && git push origin feat, git push origin main && npm cache clean --force, grep -f … && echo 'see git push docs' and a broker call whose quoted page text mentioned
    git push next to its own -f were blocked. It now also blocks force
    pushes it let through: git -C <dir> push -f, git -c k=v push -f,
    git --git-dir … push -f, git push -fu, git push origin +main,
    +HEAD:main and --force-with-lease= with an empty value.
    --force-if-includes alone and -f after -- stay allowed. The new
    hooks/lib/git-push-match.mts splits the command into simple commands in
    one linear pass, scans quoted words again as shell down to depth 3, reads
    heredoc body lines word by word, and when it cannot parse the command it
    falls back to the old
    regexes, so the rule still fails closed. The installer manifest lists the
    new module (issue #327).
  • Confluence brokers: labels --id <page> without --labels or --remove
    is read-only. It sends one GET and prints labels: a, b or labels: none.
    Before, it posted the computed runtime label, so reading a page from a
    second host gave it a second runtime label. labels --labels reads the page
    first: an existing runtime label is kept (runtime: kept <label>) and only a
    page without one gets the computed label (runtime: added <label>); when
    nothing but a runtime label would be added to such a page, it refuses with
    exit 1 and writes nothing. create gives every new page the computed
    runtime label, also without --labels; before, a page created without
    --labels carried none. When that label write fails, create exits 1 and
    names the page that now exists without a runtime label (issue #318).
  • Confluence brokers: search passes --limit to the semantic search as
    min(max(limit, 25), 100), where 100 is the twg rovo search maximum.
    Before, the limit never reached it and every search asked for the default
    25 proposals. After the hits it prints truncated: true|false; true means
    --limit cut off a further matching page, the proposals filled the request,
    or --limit was above 100. Exit codes are unchanged (issue #315).
  • Control plane node: delivered is a read receipt on every path. The
    messages a new Claude intercom session carries in its task text are offered
    to it and become delivered only once its turn completed (the session
    done in the watch round, or its own Stop); before, a session still
    working 30 seconds after its start already counted. A failed or stopped
    turn offers them again. The records an MCP inbox read returns are offered,
    so a reply or the session's Stop confirms them; a read that does not fit
    the transport offers nothing, and StopFailure stays no receipt. The node
    answers every final message.status it recorded (delivered, replied,
    refused, expired), also for a message without a local sent/ record,
    with the event message.status.ack, and advertises messaging.ack.v1. An
    older Worker ignores the event (issue #308, PR 3 of 4).
  • Control plane node: outbox resends and inbox status re-reports back off on a
    live connection. The first resend still comes exactly 30 seconds after the
    first send, later ones wait with equal jitter up to 10 minutes; a status is
    reported at once for each new state or progress and then on the same
    schedule. The schedule starts over after the Worker answers and on every new
    connection. directory.request bursts from the msg CLI are coalesced to
    at most one directory request per 10 seconds per connection. Before, every
    unanswered status was re-sent on each 2-second exchange round (issue #308,
    PR 2 of 4).
  • Control plane Worker: a message send, a status report and a sender's
    reconnect replay no longer scan the whole messages table, which keeps every
    final-state row. Two additive indexes (messages_queued_expiry,
    messages_from_node) serve the hot queries, which name them with
    INDEXED BY; a third, messages_state_updated, is created for a later
    sweep of final-state rows and is not read yet. With 2,000 final-state
    messages a send now reads 4 rows instead of 4,009, a status report 5 instead
    of 2,008, and the cost no longer grows with the table. The Registry caches the directory rows until a
    write to nodes, runtimes or sessions. A new SQL meter logs one
    registry.sql line with path, rowsRead and rowsWritten per Registry
    request, without message text, and the committed wrangler.jsonc and the
    README override example turn on Workers Logs; it takes effect with the
    operator's next deploy (issue #308, PR 1 of 4).
  • Bootstrap: drift-check.sh compares the optional Jira helpers in
    <workspace>/tools/ when KHEREP_INSTALL_ATLASSIAN_TOOLS=1 is set or when
    any of the twelve exact file names is present. Before, it compared them only
    with the switch, so a stale atl-jira.mts from an earlier installation with
    the switch passed as clean. A stale file is now DRIFT and the absent members
    of an incomplete set are MISSING-LIVE. Without the switch and without any of
    these files the check still passes and prints one informational
    NOT-INSTALLED project/tools/<Jira set> line. Look-alikes such as
    atl-jira.mjs, *.bak-* copies and _deprecated/ never count as present
    (issue #302).
  • Session observations: the brief marks each finding measured:, with the
    command and the deciding output excerpt, when the dispatching session ran it
    in this turn, and relayed:, with its source, otherwise. claude-obs and
    codex-obs label a measured: finding with that evidence confirmed and
    put the command and excerpt in the page body; a relayed: finding, a
    measured: one without evidence, or an unmarked one stays assumed. The
    origin line names the actual reporter, a title says "(operator-reported)"
    only when the operator is the source, and the dispatching session is never
    called the operator. Before, findings the dispatching session had measured
    in the same turn were filed as assumed and operator-reported. Both
    ROUTING.md files and the three observation hook reasons state the brief
    contract (issue #309).
  • Session observations: both dispatch guards check the brief format before
    the observation agent starts. A claude-obs prompt or a codex-obs
    message is denied when it marks no finding, when a measured: finding has
    no command in an inline code span or no -> with an excerpt after it, or
    when a relayed: finding is empty; the Claude rule id is OBS_BRIEF_FORMAT.
    The reason names the finding by position and states the exact format, so
    the session can resend in the same turn. The Claude guard reads the Agent
    prompt only, never the description. The check lives in the new
    claude/hooks/lib/obs-brief-policy.mts, which the Claude manifest installs
    and the Codex guard loads from its installed lib sibling; Codex hook
    integrity checks and restores it. The three observation hook reasons and
    both ROUTING.md files now state the exact syntax. Before, only the Stop
    hooks described the format, so a session that dispatched the agent on its
    own never saw it (issue #331).