Files
multica/server/internal/daemon/repocache
11bd18a507 MUL-6921 fix(daemon): honor the Co-authored-by toggle in existing checkouts (#7868)
* MUL-6921 fix(daemon): honor the Co-authored-by toggle in existing checkouts

Turning off the workspace's Co-authored-by switch only affected repos
checked out after the flip. The prepare-commit-msg hook the daemon
installs appends the trailer unconditionally and lives in the git common
directory, so it outlives the checkout that created it: every later
commit in an existing checkout kept getting the trailer — including
`git commit --no-verify`, which bypasses pre-commit and commit-msg but
not prepare-commit-msg. On Gitea the trailer renders as a second commit
author with its own avatar, so the stale value is publicly visible.

The hook now re-reads the workspace's current setting at commit time
from a state file the daemon publishes, instead of freezing the decision
into the script at checkout time. The daemon writes that file whenever
it resolves the setting: on every settings refresh, on every workspace
sync (covers a daemon restart and repo-cache GC, costs no request), and
on checkout. A missing state file keeps the trailer, which is what the
hook's presence meant before.

Settings edits also had no way to reach a running daemon — the server
only woke daemons on rename, and the periodic sync deliberately skips
the repos/settings endpoint for a tracked workspace. UpdateWorkspace now
notifies members' daemons on a settings change too, and that hint
refreshes settings for the workspaces the daemon already tracks.

Co-authored-by: multica-agent <[email protected]>

* MUL-6921 fix(daemon): make the Co-authored-by verdict converge on its own

Review found three ways the first cut still left a stale trailer live.

A checkout snapshots the setting before its fetch and lock waits, then
wrote that snapshot to the workspace-wide state file — so a slow checkout
could resurrect a value the user had already turned off. The daemon is
now the only publisher: it holds the ordering between settings updates,
and the checkout reads what was published (falling back to its snapshot
only when nothing has been published yet). Publication itself takes a
per-workspace lock and reads the verdict inside it, so a publisher that
started before a settings update can no longer overwrite its result.

Hooks written by earlier releases read no state at all, so publishing a
new value never reached them: an upgraded host kept adding the trailer
until each repo was checked out again. Publication now also reconciles
the hooks in the workspace's bare caches — enabled rewrites them to the
gated script, disabled deletes them, hooks the daemon does not own are
left alone — which runs on every settings refresh and every workspace
sync. Hook files are published by rename, since these repos have live
checkouts committing against them.

A settings edit made while the daemon's websocket was down produced a
hint nobody received, and the reconnect reconcile only synced workspaces
and profiles. It now re-reads settings for tracked workspaces too.

Regression tests for all three, each verified failing without its fix;
the migration test seeds the previous release's hook verbatim and makes
real commits without any CreateWorktree call.

Co-authored-by: multica-agent <[email protected]>

* MUL-6921 fix(daemon): migrate isolated checkouts' Co-authored-by hooks too

The hook sweep only reached bare caches, so a checkout with isolated git
metadata — Codex on Linux and the Windows sandbox — kept whatever hook
its task workdir was prepared with. On a host that upgraded, that is the
previous release's unconditional script, which reads no state: the user
turns the setting off, commits in that workdir, and the trailer is still
there. That is the reported bug, still live for exactly the checkouts the
first sweep could not enumerate.

Publication now also walks the daemon's env roots for the workspace and
reconciles each checkout that owns its git metadata. Env roots are
attributed by their owner record, so one workspace never rewrites another
workspace's hooks, and a linked worktree is skipped because its hook
belongs to the bare cache. The walk is bounded to the depth repos are
checked out at and never descends into a matched checkout.

Also makes the publish-ordering regression deterministic: the verdict
read is now injected, so the test parks a publisher between taking the
lock and reading, and a second publisher reaching the cache during that
window is itself the failure. It fails immediately when the read is moved
back outside the lock, instead of depending on a 20ms sleep landing right.

Co-authored-by: multica-agent <[email protected]>

* MUL-6921 fix(daemon): attribute pre-v0.4.35 env roots to their workspace

The isolated-checkout sweep required the owner record to name the
workspace, which only became true in v0.4.35 (cd169947b). Env roots
prepared by v0.4.32–v0.4.34 recorded a bare task ID, and older ones no
marker at all — and cd169947b deliberately does not migrate workdirs
already on disk. So the sweep skipped exactly the long-lived directories
an upgrade carries forward, and their pre-MUL-6921 hook kept adding the
trailer after the setting was turned off.

Those releases put env roots under <workspaces root>/<workspace ID>/<task
key>, so when an owner record names no workspace the directory name is
the evidence and has to BE the workspace ID. The readable layout that
replaced it always appends a suffix, so a bare workspace UUID can only be
one of the older roots and this cannot alias a modern one. An unreadable
marker still attributes nothing: skipping costs a stale hook in one
workdir, guessing would rewrite another workspace's.

Regression covers both legacy shapes (no marker, task-ID-only marker),
and asserts the same two shapes under another workspace's directory —
plus a readable-layout root whose marker names no workspace — are left
alone. Verified failing without the fallback.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-01 17:28:48 +08:00
..