Repository navigation
Kherep 0.3.0
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 withwaiting/cwd-refusedinstead of
waiting/wake-not-authorized, andmsg statuson the sender says so. Nodes
advertisemessaging.progress.v2; the Worker sendswake-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) withofferedAt; the wake listener leaves
listeners/<session_id>.woke.json, which the woken turn's
UserPromptSubmitclaims within 30 s;msg statusandmsg inboxshow
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,statusandrollbackmanage 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 byfromSessiononly 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 beforedelivered, nor when the message isrepliedor its reply
already arrived.
A receipt never wakes a session or continues a turn:Stopshows 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
SubagentStophook,obs-result-check.mts,
wired with matcherclaude-obs, checks the agent'sOBS-RESULTstatus line
throughclaude/hooks/lib/obs-result-policy.mts. The first non-empty line is
the onlyOBS-RESULTline;wrote <n> <ids>needs n positive and exactly n
numeric ids;emptyandfailedneed a reason whose first word is no status
word, which catchesfailed 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 validfailed, the operator gets asystemMessage
instead. The message comes fromlast_assistant_message, else the agent
transcript. No message text reaches stdout, and any error is one
systemMessagewith exit 0. - Codex observation candidate check: a new Codex
SubagentStophook,
codex-obs-result-check.mts, wired with matchercodex-obsin 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 anobservationsarray, each candidate with exactlytitle,
bodyStorage,evidence,labelsandplacement. A fenced or otherwise
malformed candidate is sent back to the worker once (decision: "block"); on
the continuation the operator gets asystemMessageand the Maestro publishes
nothing. The message comes fromlast_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-pushhook besidecommit-msgrecords one
line per pushed ref in the node's local
<config root>/control-plane/attribution.jsonl(mode0600) for
repositories under the workspace, and always exits 0. The session is
CLAUDE_CODE_SESSION_ID, elseKHEREP_SESSION_ID, else a marker the Codex
PreToolUsephase leaves for that repository within 120 seconds, else
unknown.gh pr createnever reaches git, so
modules/control-plane/node/attribution-hook.mtsrecords 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 staysnull. 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.mtsand serves both hooks. - Main-checkout guard: a new
PreToolUsehook,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, andgit switchto any branch other than the
default, including-c,-Cand--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 localmainormaster; without one it warns and
exits 0. The inlineKHEREP_MAIN_CHECKOUT=switchprefix, 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 newBash|PowerShellgroup; 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 gitpost-checkouthook besidecommit-msgwarns, and
always exits 0, when a branch checkout leaves such a main checkout off its
default branch; it is silent in linked worktrees, afterworktree add,
during a rebase and with the marker.critical-file-integrity.mtsguards it
likecommit-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 onepagerow per match,
thentotal:,shown:andtruncated:. The total is counted by reading
every result page, never taken fromtotalSize; only--limittruncates.
Exit 0 for any completed read, includingtotal: 0, and exit 1 for a
Confluence or argument error, which prints nototal:. 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 printsruntime: kept,runtime: removedand
labels:; it exits 1 when the removed label is still there. It cannot be
combined with--labelsor--remove, and it refuses without writing when
the value is not aruntime-label, when the page carries one, none or more
than two runtime labels, or when the named label is not among them. The
labelsverb now lives in the sharedconfluence-label-cli.mts, which the
installers, drift checks and smoke test project beside the brokers
(issue #318). - Atlassian destructive guard: a new
PreToolUsehook,
atlassian-destructive-guard.mts, stops everymcp__<server>__executeDestructive
call, under any server name, the managedatlassianserver, 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
matchermcp__.*__executeDestructive. Codex hooks cannot ask, so Codex denies
it with--runtime codex; the guard is the last entry of the existing
Agent, web and MCPPreToolUsegroup, because a new group would move the
positional trust key of every operatorPreToolUsegroup 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 themoffered; only the turn's
Stopconfirms themdelivered(issue #308 rule), and thatStopand a later
UserPromptSubmitinject them no second time. Calls from a subagent or
in-process teammate (agent_id) and calls in permission mode
bypassPermissionsinject 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 doctorcounts 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
nodepolicy.jsonwithout awakesection 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 amessagingsection still accepts nothing. The rule for every
sender,{ "accept": [{ "session": "*", "from": ["*"] }] }, is written
explicitly byonboardinto a newpolicy.jsontogether with the default
wakesection (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 awake.budgetwithperDayalone is no longer compared
with the default hour.kherep-node doctorreportswakeSource(fileor
default) andmessagingSource(file,absentfor a file without the
section,defaultfor a missing file), and no longer reports
"enabled": falseas 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 wrappersupergateway-secret-wrapper.mts, which sends it as
Authorization: Bearer. The file isKHEREP_ATL_MCP_TOKEN_FILE_CLAUDEor
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 managedatlassiantable runs the wrapper and sets
approval_mode = "prompt"forexecuteDestructive; a block with the
previous native url table, or with a table for another key file, is
recognised and replaced, and the receipt reportsatlassianMcpwith its
status andtokenSource, never the path or the key. Claude: the installer
registers the user-scope stdio serveratlassianthroughclaude 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. Anatlassianentry 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-defaultCLAUDE_HOMEwithout
KHEREP_INSTALL_ALLOW_ATL_MCP=1(issue #376). -
Research evidence: a v2
executeReadwhose operation issearchConfluence
orsearchConfluenceUsingCqlcounts as a Central Brain lookup on both
runtimes (issue #376). -
The secret-file wrapper reports a failure as
MCP secret wrapper failed safelyinstead 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,refusedor
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 withoutmessaging.ack.v1are deleted 24 hours after their
final state, and a revoked node's final messages at once;queuedand
acceptedmessages 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, MCPstatusand task-control
provenance. The Worker now stores every message's reply depth and refuses a
reply beyondMAX_REPLY_DEPTHwithreply depth exceeded. An MCPreply
to a deleted message reads that one item from the replying node's inbox
(mcp.inbox.requestwithmessageIdandreply; 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/messagesno 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 arovoserver. 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
legacyatlassian-rovo@openai-curatedplugin, retires the v1rovotable,
and recognises a managed block an earlier installer wrote with or without
thatrovotable; tables outside the block stay untouched. The install
receipt dropsnativePlugins. The research-evidence matchers count the v2
searchConfluencetool under any server name, including a claude.ai
connector named by a UUID (issue #304). -
Codex delivery hook: the reason of a blocked
Stopnow 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 ClaudeStop
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.lockin an enrolled node'scontrol-planedirectory. While that
file is younger than 30 minutes the daemon starts no new Claude readiness
probe and logsprobe deferred: install in progressonce; 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 --jsonpre-check warns when Claude already reports no
sign-in and never blocks the installation. At the end,
bootstrap/claude-readiness-check.mtsruns the daemon's own probe once and
printsinstall: 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.
- run 'claude auth login'
- Codex headless broker research (issue #405): the research reminder explains
command-scoped approval when sandbox networking blocks a read-only search.
The documentedcodex exec --approve-for-meinvocation 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 acustom_tool_callnamedexecwithout a
namespacefield, which the parser accepted only asfunctions.execor 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 acustom_tool_call; a bareexec
function_calland other namespaces still do not count. The nested
tools.exec_command(...)call counts as before, also inside
Promise.allSettled, with its output collected throughwrite_stdin. Text in
comments, strings, user messages, tool output anditem_completedrecords
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-planedeliver-hook.mtsandattribution-hook.mtsis upgraded
instead of refused, also with an earlier Node executable; any other
difference in the block is still refused. An upgrade keeps a managed
atlassiantable: withoutKHEREP_INSTALL_ATLASSIAN_TOOLSthe tool set is
installed again with the key file the installed table names (receipt
tokenSourceinstalled), an unusable key file stops the install before
it writes, and an explicit opt-out recordsatlassianMcp.statusremoved.
kherep-node doctorcounts a hook in a source pin of the checked-out commit
as this checkout and lists it underpinned; 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 (noclaudeon
PATH, a listing at its 512-session cap), counts no miss. The count is kept
inknown-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 withwaiting/awaiting-user-turn, and is refused
withtarget session not runningwhen 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 statuson 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
atlassianentry in
~/.claude.jsonthat 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_KEYin the
installing shell could not prove a working broker credential. Both Jira
brokers validated the full project seed beforeselftest, although the check
proves only the OAuth client and the site;selftestnow needs
KHEREP_ATL_SITEand nothing else. When a broker still stops on a
KHEREP_ATL_*configuration error,bootstrap/atl-credential.mtsnames 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 readsNO-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 brokeratl-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 brokersatl-jira.mtsandatl-confluence.mts(issue #376). - claude-obs reads its host configuration
<Claude home>/kherep/confluence.json
with the Read tool instead ofcat, 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
sharedisWithinWorkspace()inreal-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()inreal-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, soGet-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 inprivate-path-policy.mts, which Bash
and Codex use too, now followssl, reads a colon-attached parameter value
(-LiteralPath:<path>), and walks the script ofbash -c,pwsh -Command
and the other shells incommand-walk.mtsin 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
sameforge deployorforge install. Before, they matchedforge deploy
and-e production, orforge installand-s, anywhere in the command,
soforge deploy -e development && grep -e production log.txt, a dev
deploy followed byforge install -e production, and an install followed
byrsync -s a bwere blocked. They now also block what they let through:
-e "production",--environment 'production',-eproduction,
-e prod,--site "<site>",forge.cmd,forge.exeand
npx @forge/cli. Aproduction*value still blocks,forge install list
is read-only, and an install without a site stays allowed. The new
hooks/lib/forge-match.mtsreuses the segment scan of
hooks/lib/git-push-match.mts, now exported assegmentVerdict, 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 tunnelonly whentunnelis the
verb of a forge invocation, read with the segment scan of rules 1 and 2.
It now also blocksforge.cmd,forge.exe, a Windows path to
forge.cmd,npx @forge/cliwith 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 asecho "forge tunnel", stays blocked (#327 decision 2).
forge TUNNELpasses, 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
sharpfrom 0.35.4 to
0.35.5 (CVE-2026-96889, Dependabot alert #12).miniflarepins
sharpto exactly 0.35.4, also in its latest release, so Dependabot could
not raise it on its own. The override can go onceminiflarerequires a
patchedsharp.sharpis a development dependency only and is not part
of the deployed Worker.@cloudflare/workers-typesmoves to
5.20261008.1, the peer rangewrangler4.148.0 asks for; without it the
lockfile could not be regenerated (issue #343). - Plugin reconciliation:
claude plugin list --jsonnow also reports rows
with"scope": "synced", plugins synced from claude.ai, and one such row
madereconcile-pluginsfail withClaude plugin list contains a malformed entry, so the install ended with errors and no plugin was reconciled.
syncedis now a known scope: the row is read for duplicate detection and
never installed, enabled or removed, likeprojectandlocal. 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 samegit push. Before, it matchedgit pushand a force
flag anywhere in the command, sogh 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 pushnext to its own-fwere 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:mainand--force-with-lease=with an empty value.
--force-if-includesalone and-fafter--stay allowed. The new
hooks/lib/git-push-match.mtssplits 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--labelsor--remove
is read-only. It sends one GET and printslabels: a, borlabels: none.
Before, it posted the computed runtime label, so reading a page from a
second host gave it a second runtime label.labels --labelsreads 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.creategives every new page the computed
runtime label, also without--labels; before, a page created without
--labelscarried none. When that label write fails,createexits 1 and
names the page that now exists without a runtime label (issue #318). - Confluence brokers:
searchpasses--limitto the semantic search as
min(max(limit, 25), 100), where 100 is thetwg rovo searchmaximum.
Before, the limit never reached it and every search asked for the default
25 proposals. After the hits it printstruncated: true|false; true means
--limitcut off a further matching page, the proposals filled the request,
or--limitwas above 100. Exit codes are unchanged (issue #315). - Control plane node:
deliveredis a read receipt on every path. The
messages a new Claude intercom session carries in its task text are offered
to it and becomedeliveredonly once its turn completed (the session
donein the watch round, or its ownStop); before, a session still
working30 seconds after its start already counted. A failed or stopped
turn offers them again. The records an MCPinboxread returns are offered,
so a reply or the session'sStopconfirms them; a read that does not fit
the transport offers nothing, andStopFailurestays no receipt. The node
answers every finalmessage.statusit recorded (delivered,replied,
refused,expired), also for a message without a localsent/record,
with the eventmessage.status.ack, and advertisesmessaging.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.requestbursts from themsgCLI 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 wholemessagestable, 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.sqlline withpath,rowsReadandrowsWrittenper Registry
request, without message text, and the committedwrangler.jsoncand 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.shcompares the optional Jira helpers in
<workspace>/tools/whenKHEREP_INSTALL_ATLASSIAN_TOOLS=1is set or when
any of the twelve exact file names is present. Before, it compared them only
with the switch, so a staleatl-jira.mtsfrom an earlier installation with
the switch passed as clean. A stale file is nowDRIFTand the absent members
of an incomplete set areMISSING-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, andrelayed:, with its source, otherwise.claude-obsand
codex-obslabel ameasured:finding with that evidenceconfirmedand
put the command and excerpt in the page body; arelayed:finding, a
measured:one without evidence, or an unmarked one staysassumed. 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.mdfiles 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. Aclaude-obsprompt or acodex-obs
message is denied when it marks no finding, when ameasured:finding has
no command in an inline code span or no->with an excerpt after it, or
when arelayed:finding is empty; the Claude rule id isOBS_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 installedlibsibling; Codex hook
integrity checks and restores it. The three observation hook reasons and
bothROUTING.mdfiles 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).