fix: resolve sessions by name in attach, stop, and theia - #58
Conversation
Containers are named enclave-<tool>-<hash>-<session>, but attach/stop/theia passed their positional argument straight to Docker as an exact container name, so the documented `--name my-task` + `attach my-task` flow could never work. All three now share one resolver that also accepts a session name, preferring the current project and reporting ambiguity instead of guessing; session names stay non-unique across projects, which parallel agents rely on. The session label now stores the sanitized name so it matches the container name suffix, which also fixes `ps --name "My Task"` finding nothing, and `ps` gained a SESSION column so the addressable name is discoverable.
|
Filtering by --name is done in the app layer with both sides sanitized instead of as an exact label filter, so `stop --name "???"` (which sanitizes to nothing) stops nothing rather than every background container, and the session label keeps the user-typed name: no ps/status JSON value change and pre-upgrade containers stay selectable. Also restores resolution by container ID, applies --tool to the candidates rather than to the listing so an exact container name always resolves, registers --tool on attach/theia as the documented disambiguator, and keeps the no-argument form inside the current project and on detached sessions so a bare `attach` cannot grab a foreground session's TTY.
|
Follow-up commit a37ee43 addresses a subagent review of the first commit. The substantive findings: Dangerous regression (introduced by the first commit): Dropped the session-label change. The label keeps the user-typed name, since sanitized-both-sides matching makes the stored form irrelevant. This removes the Container IDs resolve again.
No-argument form stays home. Bare Also dropped an unused parameter with a misleading comment, and fixed the doc claims about Tests added for all of the above (including two that failed first and caught real bugs in the fix: the unsanitizable-name guard and the project-scope fallback). Two review points I did not act on, for the record: |
EclipseSourceAI
left a comment
There was a problem hiding this comment.
Note
Autonomous AI review.
This review was done by an AI agent and therefore may contain mistakes. Feel free to ignore any comment you disagree with. A thumbs-down reaction on a comment marks it as rejected for follow-up reviews. Noting why in a reply helps, since replies are read too.
Resolving all AI comments does not lead to an automatic approval. A maintainer still needs to review and sign off on the overall architecture and design.
To get an updated review after pushing changes, a maintainer may re-request a review from this account.
Running in Eclipse Enclave, submitted via review-guard-mcp
Makes attach, stop <name>, and theia/theia-next resolve their positional argument as a session name (and container ID) instead of passing it verbatim to Docker, so the documented --background --name my-task / attach my-task flow works. All three share a new resolver in internal/app/session_target.go: exact container name first, then sanitized session-name matching scoped current-worktree → project → host, then container ID, with ambiguity reported rather than guessed. sanitizeSessionName moved from runtime to model.SanitizeSessionName, --name filtering for ps/status/stop moved into the app layer with both sides sanitized, and ps gained a SESSION column.
The approach is sound and the follow-up commit already fixed the worst of it (the stop --name "???" wipe, container IDs, --tool actually being registered). Build and tests pass locally.
Points worth a maintainer's attention:
SessionFilter.SessionNameis now dead ininternal/backend, and it is exactly the field whose empty-means-no-filter semantics caused thestop --name "???"regression. Removing it closes that off.stop <name>andstop --name <name>now diverge on tool scope, background-only, and stopped inclusion. That is easy to trip over.- The host-wide fallback for explicit names is more consequential for
stop(which force-removes) than forattach, especially with auto-assigned1,2, … names that collide across projects by construction. resolveExecTargetininternal/runtime/exec.goalready does worktree-preferred selection. Two resolvers with different error wording now coexist.
The resolver has not been exercised against live containers, per the PR description. The cross-project and stopped-container paths in particular deserve a manual pass.
| // in sanitized form, which the backend cannot express as a label filter. | ||
| func psSessionFilterFor(opts model.Options) backend.SessionFilter { | ||
| filter := backend.SessionFilter{Tool: psToolFilter(opts), SessionName: psSessionFilter(opts)} | ||
| filter := backend.SessionFilter{Tool: psToolFilter(opts)} |
There was a problem hiding this comment.
With ps, status, and stop all moved to app-layer matching, SessionFilter.SessionName has no producers left. It is still declared (types.go) and still turned into a label filter (docker.go). Dropping it removes the empty-value-means-no-filter footgun that caused the stop --name "???" regression instead of leaving it around for the next caller.
There was a problem hiding this comment.
Removed in f24bf91: the field and the docker label filter it produced are gone. ps, status, and stop all match names in the app layer now, so nothing was left to feed it.
| sessions, err := be.List(context.Background(), backend.SessionFilter{All: true, Background: &background, Tool: run.Tool}) | ||
| if err != nil { | ||
| logx.Errorf("Failed to list background sessions: %v", err) | ||
| return 1 | ||
| } | ||
| // Sanitized matching happens here rather than as a label filter, so that a | ||
| // name which sanitizes to nothing stops nothing instead of everything. | ||
| if name := strings.TrimSpace(run.SessionName); name != "" { | ||
| sessions = sessionsMatchingName(sessions, name) | ||
| } |
There was a problem hiding this comment.
stop <name> and stop --name <name> now behave quite differently: the positional path resolves across every tool, every project, and includes stopped and foreground containers, while this path stays pinned to run.Tool (defaults to claude) and Background: true. Users will read them as the same thing, so either align them or spell the difference out in docs/cli-reference.md.
There was a problem hiding this comment.
Kept as two selectors, but the project-scope axis is gone: 37bcd0b scopes stop --name to the current project as well (it previously listed background sessions of every project, which is the same collision hazard as the positional form). The remaining differences — one session vs. all matching, tool from an explicit --tool vs. the resolved tool, background-only — are now spelled out in docs/cli-reference.md and in a new stop --help long text.
| scoped, _ := projectSessions(matches, q.Project) | ||
| return selectSingleSession(scoped, requested) |
There was a problem hiding this comment.
When the current project has no match, this falls through to the host-wide candidates, and a single match elsewhere is used silently. That is fine for attach, but stop force-removes the container, and auto-assigned session names are 1, 2, … per project, so enclave stop 1 from a project without a session 1 can reach into an unrelated project. Restricting the fallback when IncludeStopped is set would be safer.
There was a problem hiding this comment.
Fixed in f24bf91, and extended in 37bcd0b. Name resolution for stop no longer falls back host-wide (sessionTargetQuery.ProjectScopedNames); a name that matches nothing in the current project is rejected with a pointer to the container name or ID, which still resolve from anywhere. The same scoping now applies to stop --name, which listed background sessions of every project.
| // from the very same worktree first, then any session of the project. ok | ||
| // reports whether the project matched anything, so that callers can decide | ||
| // between falling back to all candidates and reporting nothing. | ||
| func projectSessions(sessions []backend.Session, project model.Project) (scoped []backend.Session, ok bool) { |
There was a problem hiding this comment.
resolveExecTarget already implements same-worktree-preferred selection with ambiguity reporting for exec/shell (exec.go). Two implementations with different error wording now coexist for the same job. Not asking for the refactor in this PR, but a maintainer should decide whether exec should move onto this resolver.
There was a problem hiding this comment.
Noted, not addressed here. Merging them is not mechanical: exec/shell synthesize the container name from --name instead of searching, list by name prefix, and do not accept container IDs — only the worktree-preferred selection overlaps. Worth its own issue.
| With no argument, `attach` picks the single detached session of the current | ||
| project (a foreground session is entered with `exec` instead), and `theia` picks | ||
| its single running container. |
There was a problem hiding this comment.
theia is project-scoped now too (autoSelectSession scopes by project whether or not BackgroundOnly is set), so "its single running container" reads as host-wide and is misleading. The theia --help long text already says "of the current project".
There was a problem hiding this comment.
Fixed in f24bf91: the sentence now reads "the single running container of the current project".
xai
left a comment
There was a problem hiding this comment.
I think we should also address three safety issues related to session selection before merging. See inline comments for details.
`stop` no longer reaches into another project by session name: the auto-assigned names `1`, `2`, … collide across projects and removal is destructive, so another project's session needs its container name or ID. A positional argument that is present but blank is rejected instead of falling back to auto-selection, and an explicitly blank `--name` matches nothing rather than every background session. Ambiguous container-ID prefixes are reported instead of resolved by listing order. Also drops the now-unused `SessionFilter.SessionName` label filter, whose empty-means-everything semantics caused the original `--name` regression.
`stop --name` filtered background sessions of every project, so the name collision the positional form now guards against was still reachable through the flag. Both forms are project-scoped; an unresolvable project matches nothing instead of the whole host. Only a complete container ID may extend a recorded (truncated) one, since a value of some length in between cannot be verified against it.
|
Follow-up commits
Note for consumers: Blank Ambiguous container-ID prefix.
|
EclipseSourceAI
left a comment
There was a problem hiding this comment.
Note
Autonomous AI review.
This review was done by an AI agent and therefore may contain mistakes. Feel free to ignore any comment you disagree with. A thumbs-down reaction on a comment marks it as rejected for follow-up reviews. Noting why in a reply helps, since replies are read too.
Resolving all AI comments does not lead to an automatic approval. A maintainer still needs to review and sign off on the overall architecture and design.
To get an updated review after pushing changes, a maintainer may re-request a review from this account.
Running in Eclipse Enclave, submitted via review-guard-mcp
Follow-up on f24bf91 and 37bcd0b. Checked these against the current code and tests, all hold up:
stop's positional and--nameforms are now both project-scoped (ProjectScopedNames,projectHashSessions), closing the cross-project removal hazard for auto-assigned names like1.- Blank
--nameand blank positional arguments are rejected/matched-to-nothing instead of falling back to "no filter" or "auto-select" (sessionNameFilter,requestedSession), which also fixes the underlying cause of the earlierstop --name "???"force-remove regression. - Ambiguous container-ID prefixes are now collected and reported instead of picked by list order (
sessionsByContainerID+selectSingleSession), and a value can only extend a stored truncated ID when it's exactly full length. SessionFilter.SessionNameand its docker label filter are removed.docs/cli-reference.md,docs/persistence.md, and the newstop --helptext now spell out howstop <name>andstop --namediffer, and thetheiaproject-scoping wording is fixed.
Build and full test suite pass locally. No new issues in the commits since my last pass; the exec/resolveExecTarget duplication is acknowledged as out of scope with a reasonable explanation (different selection semantics, worth its own issue).
These previous comments can be resolved as they are now handled:
- dead
SessionFilter.SessionNamefield and its label filter stop <name>vsstop --name <name>scope divergence, now documented and aligned on project scope- host-wide fallback for
stopbeing destructive with colliding auto-assigned names theiadoc wording implying host-wide single-container selection
I can't resolve them myself as I would need write permission on this repository.
xai
left a comment
There was a problem hiding this comment.
Thanks for the fixes!
The changes look good, and all parts of the test plan (+ some additional testing) execute as expected.
Why
Containers are named
enclave-<tool>-<project-hash>-<session>, butattach,stop <name>, andtheiapassed their positional argument straight to Docker as an exact container name. The flow documented on the website —enclave --background --name my-taskthenenclave attach my-task— could therefore never work.Global uniqueness of session names is deliberately not introduced: container names are already unique via the project hash, and reusing
--name reviewacross several repos is a normal parallel-agent workflow.What
attach,stop <name>, andtheia/theia-nextshare one resolver (internal/app/session_target.go). A full container name still matches verbatim; anything else is matched as a session name, preferring sessions from the same worktree, then the same project, then the whole host. When a name matches several containers, the candidates are listed instead of one being guessed.--toolnarrows candidates when given on the CLI.enclave attachtakes an optional argument: with a single running session in the project, bareattachpicks it (matchingtheia).stop <name>also considers stopped containers, so a leftover can be cleared by name.model.SanitizeSessionName. Theenclave.sessionlabel now stores the sanitized form, so it equals the trailing container-name segment;ps --name/status --namesanitize their filter input, which fixes--name "My Task"not matching--name my-task.enclave psgained aSESSIONcolumn (-for a project's default container) so the addressable name is discoverable.enclave stop my-taskrather than the full container name.docs/cli-reference.md,docs/persistence.md,website/docs/docs/cli.md, and theattach/stop/theiahelp text.Note for consumers
ps --jsonsessionNamenow reports the sanitized value (my-task, notMy Task) for names that needed sanitizing. Field and shape are unchanged. Sessions started by an older build keep their raw label and still resolve, since matching sanitizes both sides.How to test
Unit coverage:
internal/app/session_target_test.go,internal/model/session_name_test.go,internal/runtime/session_name_test.go,internal/app/command_ps_test.go.make build,make test,make lint, andmake check-license-headerspass; the resolver has not been exercised against live containers.Manual:
Ambiguity and scoping:
Sanitization:
enclave --background --name "My Task"is addressable asenclave attach my-task, andenclave ps --name "My Task"and--name my-tasklist the same row.