feat(gateway): session worktree targeting, branch listing, and server-side session groups#103432
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 757c98e3b9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const name = withoutPrefix.slice(slash + 1); | ||
| // remote HEAD symrefs are pointers, not selectable branches. | ||
| if (!name || name === "HEAD") { | ||
| continue; | ||
| } | ||
| branches.set(name, { name, kind: "remote" }); |
There was a problem hiding this comment.
Return remote refs that worktree creation can consume
When the only selectable branch is remote-only (for example origin/feature-a), this strips the remote and returns feature-a; clients then pass that value back as worktreeBaseRef. git worktree add -h documents --guess-remote as matching a new branch with a remote-tracking branch, and with git 2.43 the later git worktree add <path> -b openclaw/<name> feature-a creates/checks out local branch feature-a instead of openclaw/<name>. The registry still records openclaw/<name>, so subsequent remove/restore branch operations fail and leave the checkout behind; return a creatable ref such as origin/feature-a or add a separate ref field.
Useful? React with 👍 / 👎.
| // Read-only git probe, but it accepts arbitrary host paths; keep it at the | ||
| // same bar as starting worktree sessions instead of plain read scope. | ||
| { name: "worktrees.branches", scope: "operator.write" }, |
There was a problem hiding this comment.
Require admin for arbitrary branch probes
This classifies worktrees.branches as operator.write, but the handler accepts caller-supplied repoRoot and passes it directly to service.listRepositoryBranches(params.repoRoot). That differs from sessions.create, where arbitrary host paths via cwd are admin-only and write scope is limited to configured workspaces; a write-scoped gateway client can now probe any path on the host for git checkout existence and branch/default/current names. Either require operator.admin here or constrain repoRoot to configured agent workspaces before keeping write scope.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38123d382c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| worktreebaseref: String?, | ||
| worktreename: String?, | ||
| execnode: String?, |
There was a problem hiding this comment.
Keep Swift create params source-compatible
OpenClawProtocol is exported as a public SwiftPM library in apps/shared/OpenClawKit/Package.swift, so adding these optional protocol fields as non-defaulted public init parameters source-breaks existing Swift clients that construct SessionsCreateParams with the previous initializer; they now must pass three extra nil arguments even when not using worktree targeting or exec-node binding. Give the new optional parameters default nil values or add a compatibility overload so this additive protocol change remains additive for Swift callers.
Useful? React with 👍 / 👎.
| ) { | ||
| return; | ||
| } | ||
| respond(true, { ok: true, groups: putSessionGroups(params.names) }, undefined); |
There was a problem hiding this comment.
Broadcast group catalog changes
When an operator creates or reorders the server-side group catalog from one client, this persists via putSessionGroups but sends no sessions.changed broadcast; the protocol adds no separate group-change event, and the rename/delete handlers below only broadcast when member sessions changed. Other connected dashboards that already fetched the catalog will keep stale group names/order until reconnect or manual refresh, so emit a reason: "groups" change for catalog mutations even when no session rows were updated.
Useful? React with 👍 / 👎.
…catalog
- sessions.create accepts worktreeBaseRef/worktreeName (write scope) and
execNode (admin); worktree binding persists on the session entry as
worktree { id, branch, repoRoot } and projects onto session rows with
execNode so UIs can show checkout/branch/node state
- new worktrees.branches RPC lists local/remote branches (no fetch) for
base-ref pickers; worktrees.remove now reports snapshotError
- sessions.delete reports preserved dirty checkouts as worktreePreserved
- gateway-owned session group catalog in the shared state DB with
sessions.groups.list/put/rename/delete; rename/delete update member
categories server-side without bumping recency; sessions.patch absorbs
ad-hoc categories into the catalog
- group session display names prefer the human chat title (subject or
space #channel) over stored compact tokens
Part of #103431
…or and regen protocol models - bulk member-category rename/clear uses applySessionEntryReplacements instead of legacy updateSessionStore call sites (session accessor boundary guard) - regenerate Swift GatewayModels for the worktrees.branches schemas, snapshotError, and new sessions.create params Part of #103431
…ch listing - worktrees.branches returns remote-only branches remote-qualified (origin/feature-a) so every advertised name works as a worktree base ref - write-scoped worktrees.branches callers are limited to configured agent workspaces; other host paths require operator.admin, matching the sessions.create cwd bar Part of #103431
38123d3 to
a6afa58
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a6afa58471
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| repoRoot: workspace, | ||
| ownerKind: "session", | ||
| ownerId: target.canonicalKey, | ||
| name: requestedWorktreeName, |
There was a problem hiding this comment.
Reject worktree names already owned by another session
When worktreeName matches any existing live managed worktree in the same repo, managedWorktrees.create() returns that existing registry row by path before considering ownerKind/ownerId. In this new session path, that lets a second session adopt a manual or other-session checkout, ignores the requested worktreeBaseRef, and later cleanup/delete uses findLiveByOwner("session", target.canonicalKey) so it will not release or remove the checkout for this session. Validate the returned record is owned by this session, or make the service reject/reassign cross-owner name collisions.
Useful? React with 👍 / 👎.
…transactions - managedWorktrees.create rejects a caller-chosen name whose live or restorable record belongs to a different owner, so write-scoped sessions.create cannot bind a session into another session's or a manual checkout - session group catalog writes run their SQLite transaction on the same env-scoped handle as their statements, keeping OPENCLAW_STATE_DIR overrides atomic and away from the default state DB Part of #103431
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9bbb1b4efa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }); | ||
| respond(true, { ok: true, ...result }, undefined); | ||
| if (result.updatedSessions > 0) { | ||
| emitSessionsChanged(context, { reason: "groups" }); |
There was a problem hiding this comment.
Emit parseable events for group member updates
When a group rename updates member sessions, this broadcasts only { reason: "groups" }. The existing session clients I checked (ui/src/lib/sessions/reconcile.ts) discard sessions.changed payloads that lack a key or sessionKey before reconciling, so another open dashboard ignores this event and keeps the old categories until it performs a full refresh; the delete handler uses the same keyless broadcast. Emit a dedicated group-change event or per-session changes for the updated members.
Useful? React with 👍 / 👎.
|
Codex review: needs real behavior proof before merge. Reviewed July 10, 2026, 6:54 AM ET / 10:54 UTC. Summary PR surface: Source +787, Tests +542, Docs +4, Generated +16, Other +172. Total +1521 across 35 files. Reproducibility: yes. Source inspection shows successful put/reorder sends no catalog event and zero-member rename/delete sends none, while the dependent client reloads its separate group state only on Review metrics: 3 noteworthy metrics.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review findings
Review detailsBest possible solution: Keep the gateway-owned SQLite catalog, but emit one unconditional catalog invalidation after every successful put, rename, and delete, retain canonical session-row refresh for membership changes, and land only after exact-head two-client and worktree-lifecycle proof plus maintainer confirmation of the permanent protocol surface. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows successful put/reorder sends no catalog event and zero-member rename/delete sends none, while the dependent client reloads its separate group state only on Is this the best way to solve the issue? No on the current head. Gateway ownership and SQLite storage are the clean boundary, but the advertised cross-device behavior requires an unconditional catalog-invalidation contract rather than conditional session-row signaling. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 7d87cedcda21. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +787, Tests +542, Docs +4, Generated +16, Other +172. Total +1521 across 35 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
…-side session groups (openclaw#103432) * feat(gateway): session worktree targeting, branch listing, and group catalog - sessions.create accepts worktreeBaseRef/worktreeName (write scope) and execNode (admin); worktree binding persists on the session entry as worktree { id, branch, repoRoot } and projects onto session rows with execNode so UIs can show checkout/branch/node state - new worktrees.branches RPC lists local/remote branches (no fetch) for base-ref pickers; worktrees.remove now reports snapshotError - sessions.delete reports preserved dirty checkouts as worktreePreserved - gateway-owned session group catalog in the shared state DB with sessions.groups.list/put/rename/delete; rename/delete update member categories server-side without bumping recency; sessions.patch absorbs ad-hoc categories into the catalog - group session display names prefer the human chat title (subject or space #channel) over stored compact tokens Part of openclaw#103431 * fix(gateway): route group category updates through the session accessor and regen protocol models - bulk member-category rename/clear uses applySessionEntryReplacements instead of legacy updateSessionStore call sites (session accessor boundary guard) - regenerate Swift GatewayModels for the worktrees.branches schemas, snapshotError, and new sessions.create params Part of openclaw#103431 * fix(gateway): resolvable remote branch refs and workspace-scoped branch listing - worktrees.branches returns remote-only branches remote-qualified (origin/feature-a) so every advertised name works as a worktree base ref - write-scoped worktrees.branches callers are limited to configured agent workspaces; other host paths require operator.admin, matching the sessions.create cwd bar Part of openclaw#103431 * fix(gateway): guard worktree name reuse by owner and env-scope group transactions - managedWorktrees.create rejects a caller-chosen name whose live or restorable record belongs to a different owner, so write-scoped sessions.create cannot bind a session into another session's or a manual checkout - session group catalog writes run their SQLite transaction on the same env-scoped handle as their statements, keeping OPENCLAW_STATE_DIR overrides atomic and away from the default state DB Part of openclaw#103431
Related: #103431
What Problem This Solves
Session lists cannot show which sessions run in an isolated checkout or on which branch, worktree sessions always get an auto-generated branch off
origin/HEADwith no way to pick a base ref or name, custom session groups live half in browser localStorage (catalog/order do not sync across devices; rename/delete page through every session client-side), group sessions display token names likeslack:g-generalinstead of the stored chat title, and dirty worktrees silently outlive deleted sessions. This is the protocol/server foundation for the Control UI sessions-sidebar redesign tracked in #103431.Why This Change Was Made
Persist the worktree binding on the session entry (
worktree { id, branch, repoRoot }, set and cleared together withspawnedCwd) and project it plus the existingexecNodebinding onto session rows, so clients stop depending on the one-shot create response.sessions.creategains additiveworktreeBaseRef/worktreeNameat write scope andexecNodeat admin scope (matching thesessions.patchexecNode bar). A newworktrees.branchesRPC lists local/remote branches without fetching, for base-ref pickers. The custom group catalog moves into the shared SQLite state DB behindsessions.groups.list/put/rename/delete; membership stays on each session'scategoryfield, rename/delete update members server-side without bumping recency ordering, andsessions.patchabsorbs ad-hoc categories so the catalog covers everything operator UIs observe. Group display names now prefer the human chat title (subjectorspace #channel) over the stored compact token.worktrees.removestops droppingsnapshotError, andsessions.deletereports preserved dirty checkouts asworktreePreserved.User Impact
Operator clients can show repo/branch/node state per session, offer branch pickers for new worktree sessions, and keep custom group order consistent across devices. Group chats list under their real titles ("Engineering", "Acme #general"). No behavior changes for plain New Chat,
/new, or channel routing; all new protocol surface is additive and scope-gated.Evidence
pnpm test src/gateway/session-groups.test.ts src/agents/worktrees/service.test.ts packages/gateway-protocol/src/schema/worktrees.test.ts src/config/sessions/group.test.ts src/gateway/method-scopes.test.ts— all shards green (Blacksmith Testbox).pnpm test src/gateway/server.sessions.create.test.ts src/gateway/server.sessions.store-rpc.test.ts src/gateway/session-utils.test.ts— 550 tests green, including new coverage: worktree base-ref/name creation persistsentry.worktreeand checks out the requested base commit,execNodebindsexecHost=node, groups CRUD + rename repoints member categories without touchingupdatedAt, absorbed categories appear insessions.groups.list, group rows prefer subject/#channel titles, and method scopes keepexecNode/cwdat admin while worktree targeting stays at write.pnpm check:changed(Testbox): typecheck core + core tests green; lint clean after fixing the oneno-array-sortfinding.