Repository navigation
fix: keep managed skill loading on native seam - #323
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (11)
Disabled knowledge base sources:
WalkthroughThe PR adds explicit hosted MCP launch arming during ACP startup and blocks skill CLI use from managed sessions. Skill-loading prompts now require the canonical managed-session tool and restrict CLI use to operator shells. ChangesHosted MCP activation
Managed skill surface policy
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant SessionManager
participant ACPStart
participant HostedMCPLauncher
participant HostedService
SessionManager->>ACPStart: configure ActivateMCPServers
ACPStart->>ACPStart: initialize ACP connection
ACPStart->>HostedMCPLauncher: activate MCP servers
HostedMCPLauncher->>HostedService: ArmLaunch session ID
HostedService-->>HostedMCPLauncher: confirm armed launch
ACPStart->>ACPStart: negotiate session/new
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
|
| Filename | Overview |
|---|---|
| internal/acp/start.go | Activates hosted MCP after ACP initialization and before session negotiation, with startup cleanup on activation failure. |
| internal/mcp/hosted.go | Introduces idempotent launch arming so bind expiry begins only when the provider can negotiate a session. |
| internal/mcp/hosted_records.go | Rejects hosted-tool binding until the corresponding launch record has been armed. |
| internal/session/manager_start_mcp.go | Wires the session-specific hosted-MCP activation callback into ACP startup only when hosted tools are present. |
| internal/cli/skill_managed_guard.go | Detects managed-session markers and returns supported-path guidance before skill command work begins. |
| internal/cli/skill_commands.go | Applies the managed-session guard through the parent command’s persistent pre-run hook to all skill verbs. |
| internal/skills/catalog.go | Replaces managed CLI fallback guidance with native-only skill discovery and loading instructions. |
Sequence Diagram
sequenceDiagram
participant Manager as Session Manager
participant MCP as Hosted MCP
participant ACP as ACP Provider
participant Tools as Native Tool Proxy
Manager->>MCP: Launch unarmed record
Manager->>ACP: Start provider
ACP->>ACP: initialize
ACP->>MCP: ArmLaunch(sessionID)
MCP-->>ACP: Nonce TTL starts
ACP->>ACP: session/new or session/load
Tools->>MCP: Bind with nonce
MCP-->>Tools: Native tools available
Reviews (3): Last reviewed commit: "fix: review round" | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (9)
internal/acp/agent_transport_unix.go (2)
56-57: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueConsider bounding body and idle time on the managed transport server.
The server sets
ReadHeaderTimeoutonly. A managed process that opens a connection and stalls the request body or keeps an idle connection holds a goroutine until the transport closes. The socket lives in a0700temp directory and only the same local user reaches it, so this is hardening rather than an exploitable path.Add
ReadTimeout,WriteTimeout, andIdleTimeout. SetReadTimeoutandWriteTimeouthigh enough for large skill content responses.♻️ Proposed timeout bounds
-const defaultManagedTransportReadHeaderTimeout = 5 * time.Second +const ( + defaultManagedTransportReadHeaderTimeout = 5 * time.Second + defaultManagedTransportReadTimeout = 30 * time.Second + defaultManagedTransportWriteTimeout = 60 * time.Second + defaultManagedTransportIdleTimeout = 60 * time.Second +)- server := &http.Server{Handler: handler, ReadHeaderTimeout: defaultManagedTransportReadHeaderTimeout} + server := &http.Server{ + Handler: handler, + ReadHeaderTimeout: defaultManagedTransportReadHeaderTimeout, + ReadTimeout: defaultManagedTransportReadTimeout, + WriteTimeout: defaultManagedTransportWriteTimeout, + IdleTimeout: defaultManagedTransportIdleTimeout, + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/acp/agent_transport_unix.go` around lines 56 - 57, Update the http.Server construction in the managed transport setup to include ReadTimeout, WriteTimeout, and IdleTimeout alongside ReadHeaderTimeout. Use the existing managed transport timeout configuration or add appropriately generous bounds for large skill-content request and response bodies, while ensuring idle connections are eventually closed.Source: Linters/SAST tools
39-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDocument the silent no-op return contract.
startManagedAgentTransportreturns(nil, "", nil)whendaemonSocket,sessionID, oragentNameis blank. The signature does not signal this to callers. A caller that assumes a non-nil transport onerr == nilwill dereference nil.bindManagedAgentTransportand the lifecycle test both checktransport == nilexplicitly, so current callers are correct, but the behavior is implicit.Add a doc comment on the function stating that missing identity or socket inputs disable the transport and produce a nil transport with a nil error.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/acp/agent_transport_unix.go` around lines 39 - 42, Add a doc comment to startManagedAgentTransport documenting that blank daemonSocket, sessionID, or agentName disables the transport and returns a nil transport with a nil error. Keep the existing return behavior unchanged and make the contract clear for callers.internal/acp/terminal_agent_transport_proxy.go (1)
30-35: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winSet explicit timeouts on the outbound daemon transport.
The
http.Transportsets onlyDialContext. It has noResponseHeaderTimeout,IdleConnTimeout, orExpectContinueTimeout. If the daemon accepts the connection and never responds, the proxied request stays open until the managed process cancels it. The repository guideline requires explicit timeouts on outbound network calls.Add bounded timeouts to the transport.
♻️ Proposed transport timeouts
transport := &http.Transport{ DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { var dialer net.Dialer return dialer.DialContext(ctx, "unix", socketPath) }, + ResponseHeaderTimeout: 30 * time.Second, + IdleConnTimeout: 60 * time.Second, + ExpectContinueTimeout: time.Second, }As per coding guidelines: "All outbound HTTP or network calls must use clients with explicit timeouts;
http.DefaultClientis forbidden in production paths."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/acp/terminal_agent_transport_proxy.go` around lines 30 - 35, Update the http.Transport in the terminal agent transport setup to define explicit bounded ResponseHeaderTimeout, IdleConnTimeout, and ExpectContinueTimeout values, while preserving the existing Unix-socket DialContext behavior.Source: Coding guidelines
internal/acp/terminal_identity_env.go (1)
28-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSource
COMPOZY_AGENT_NAMEfrom a shared constant.
internal/cli/whoami.goalready ownsenvAgentName = "COMPOZY_AGENT_NAME"for reading it, butinternal/acp/terminal_identity_env.gouses the same literal in the managed terminal allowlist. Keep a shared constant for this consumer reference to avoid out-of-sync renames.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/acp/terminal_identity_env.go` around lines 28 - 41, Update isManagedTerminalEnvName to replace the hardcoded "COMPOZY_AGENT_NAME" entry with the shared envAgentName constant already defined in internal/cli/whoami.go, preserving the existing allowlist behavior and avoiding a duplicate literal.internal/acp/launcher.go (1)
30-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDefine the managed transport capability interface at its consumer.
prepareManagedAgentTransportconsumesmanagedAgentTransportLauncherininternal/acp/agent_transport.go. Move the interface declaration there. Keep the compile-time assertion in this file.As per coding guidelines, define interfaces where they are consumed.
Proposed refactor
--- a/internal/acp/launcher.go -type managedAgentTransportLauncher interface { - supportsManagedAgentTransport() -} - --- a/internal/acp/agent_transport.go +type managedAgentTransportLauncher interface { + supportsManagedAgentTransport() +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/acp/launcher.go` around lines 30 - 32, Move the managedAgentTransportLauncher interface declaration from this file to the consumer location in prepareManagedAgentTransport within agent_transport.go, while preserving the existing compile-time assertion here and leaving the interface method unchanged.Source: Coding guidelines
internal/cli/client_managed_transport_unix_test.go (1)
74-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse
t.Context()for the client calls.Line 32 already uses
t.Context(). Lines 74 and 81 usecontext.Background(), so a hung request is not cancelled when the test ends.♻️ Proposed consistency fix
- skill, err := client.GetSkill(context.Background(), "release-signal", query) + skill, err := client.GetSkill(t.Context(), "release-signal", query) if err != nil { t.Fatalf("GetSkill() error = %v", err) } if skill.Name != "release-signal" { t.Fatalf("GetSkill().Name = %q, want release-signal", skill.Name) } - content, err := client.GetSkillContent(context.Background(), "release-signal", query) + content, err := client.GetSkillContent(t.Context(), "release-signal", query)Remove the now-unused
"context"import.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/cli/client_managed_transport_unix_test.go` around lines 74 - 81, Update the client calls in the test, including GetSkill and GetSkillContent, to use t.Context() instead of context.Background() so requests are cancelled with the test. Remove the context import if it is no longer used.internal/api/core/skill_scope.go (1)
51-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCollapse the duplicated
ForWorkspacebranch and map its errors.Lines 51-54 call
ForWorkspacewith the same value in both branches, becauseresolvedisnilin the second branch. The workspace path also skipsmapSkillScopeError, so its errors are not classified and reachStatusForSkillErroras unknown errors, which returns 500.♻️ Proposed simplification with consistent error mapping
- if resolved != nil { - return h.SkillsRegistry.ForWorkspace(c.Request.Context(), resolved) - } - return h.SkillsRegistry.ForWorkspace(c.Request.Context(), nil) + skillList, err := h.SkillsRegistry.ForWorkspace(c.Request.Context(), resolved) + if err != nil { + return nil, mapSkillScopeError(err) + } + return skillList, nil🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/api/core/skill_scope.go` around lines 51 - 54, In the handler branch around resolved, collapse the duplicated SkillsRegistry.ForWorkspace calls into one call using resolved, then pass its returned error through mapSkillScopeError before converting it with StatusForSkillError. Preserve the existing workspace resolution behavior while ensuring both resolved and nil workspace paths receive consistent error classification.internal/cli/client_test.go (1)
1975-2001: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCover the other three changed skill methods, or narrow the test name.
The suite name is plural, but only
GetSkillContentis asserted.ListSkills,GetSkill, andGetSkillShadowsalso moved todoAgentJSONin this PR and have no header assertion. Add a subtest per method with the same round-tripper pattern, so a droppedquery.Callerargument on any of them fails the suite.As per path instructions: "Focus on critical paths … Verify tests can fail when business logic changes."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/cli/client_test.go` around lines 1975 - 2001, The TestUnixSocketClientSkillMethodsSendIdentityHeaders test currently covers only GetSkillContent despite claiming coverage for all skill methods. Add equivalent subtests and round-tripper header assertions for ListSkills, GetSkill, and GetSkillShadows, passing credentials through each method’s query.Caller and validating the expected request so dropped identity propagation fails the test.Source: Path instructions
internal/api/core/skills_test.go (1)
1331-1341: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winLog the workspace-access request instead of asserting inside
AuthorizeFn.
ValidateWorkspaceAccessreturns without calling the policy whenTargetWorkspaceIDis empty or equals the actor workspace, so thet.FatalfinsideAuthorizeFnmakes other requests fail incorrectly. Let the fixture inspect the request for non-empty cross-workspace targets, and keep the assertions in each subtest. The deniedDecision{Source: SourceDenied}with no error already maps toErrIdentityUnauthorized.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/api/core/skills_test.go` around lines 1331 - 1341, Update the AuthorizeFn fixture used by ValidateWorkspaceAccess to record or log requests instead of calling t.Fatalf on TargetWorkspaceID. Preserve the denied Decision{Source: workspaceaccess.SourceDenied} return, and move target-workspace assertions into each relevant subtest so empty or same-workspace requests remain valid without invoking the policy.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/acp/terminal_agent_transport_proxy.go`:
- Around line 69-75: Update agentSkillTransportHandler to hold a *slog.Logger,
use it in writeProxyError to log the non-nil transport error while retaining the
generic client-facing response, and remove the inert encode-error guard in the
related response-writing path so encode failures are logged or otherwise handled
rather than discarded.
In `@internal/api/spec/spec_test.go`:
- Around line 3177-3183: Update assertManagedSkillIdentityHeaders to validate
the documented bodies for both unauthorized and forbidden responses, not only
their status entries. Use jsonResponseSchema to obtain each response schema and
assert the required contract.ErrorPayload fields with the existing specific
error assertion helpers.
In `@internal/cli/client_managed_transport_unix.go`:
- Around line 28-29: Update the managedStreamClient returned by the Unix
transport constructor to either set an appropriate non-zero Timeout for bounded
SSE streams or explicitly document that doSSE cancellation via context controls
the read deadline when indefinite streaming is intentional. Keep the existing
default timeout on the regular HTTP client and ensure streamHTTPClient uses the
chosen behavior.
In `@internal/cli/client_settings_vault.go`:
- Around line 34-44: Update NewClient so managedAgentTransportClients is used
only for skill-read requests under /api/skills/*, while all other DaemonClient
methods continue using the daemon socket. If managed transport discovery fails
or returns a stale/unavailable socket, log the discovery error and fall back to
constructing the daemon client instead of returning the error; preserve managed
transport selection for valid skill-read requests.
---
Nitpick comments:
In `@internal/acp/agent_transport_unix.go`:
- Around line 56-57: Update the http.Server construction in the managed
transport setup to include ReadTimeout, WriteTimeout, and IdleTimeout alongside
ReadHeaderTimeout. Use the existing managed transport timeout configuration or
add appropriately generous bounds for large skill-content request and response
bodies, while ensuring idle connections are eventually closed.
- Around line 39-42: Add a doc comment to startManagedAgentTransport documenting
that blank daemonSocket, sessionID, or agentName disables the transport and
returns a nil transport with a nil error. Keep the existing return behavior
unchanged and make the contract clear for callers.
In `@internal/acp/launcher.go`:
- Around line 30-32: Move the managedAgentTransportLauncher interface
declaration from this file to the consumer location in
prepareManagedAgentTransport within agent_transport.go, while preserving the
existing compile-time assertion here and leaving the interface method unchanged.
In `@internal/acp/terminal_agent_transport_proxy.go`:
- Around line 30-35: Update the http.Transport in the terminal agent transport
setup to define explicit bounded ResponseHeaderTimeout, IdleConnTimeout, and
ExpectContinueTimeout values, while preserving the existing Unix-socket
DialContext behavior.
In `@internal/acp/terminal_identity_env.go`:
- Around line 28-41: Update isManagedTerminalEnvName to replace the hardcoded
"COMPOZY_AGENT_NAME" entry with the shared envAgentName constant already defined
in internal/cli/whoami.go, preserving the existing allowlist behavior and
avoiding a duplicate literal.
In `@internal/api/core/skill_scope.go`:
- Around line 51-54: In the handler branch around resolved, collapse the
duplicated SkillsRegistry.ForWorkspace calls into one call using resolved, then
pass its returned error through mapSkillScopeError before converting it with
StatusForSkillError. Preserve the existing workspace resolution behavior while
ensuring both resolved and nil workspace paths receive consistent error
classification.
In `@internal/api/core/skills_test.go`:
- Around line 1331-1341: Update the AuthorizeFn fixture used by
ValidateWorkspaceAccess to record or log requests instead of calling t.Fatalf on
TargetWorkspaceID. Preserve the denied Decision{Source:
workspaceaccess.SourceDenied} return, and move target-workspace assertions into
each relevant subtest so empty or same-workspace requests remain valid without
invoking the policy.
In `@internal/cli/client_managed_transport_unix_test.go`:
- Around line 74-81: Update the client calls in the test, including GetSkill and
GetSkillContent, to use t.Context() instead of context.Background() so requests
are cancelled with the test. Remove the context import if it is no longer used.
In `@internal/cli/client_test.go`:
- Around line 1975-2001: The TestUnixSocketClientSkillMethodsSendIdentityHeaders
test currently covers only GetSkillContent despite claiming coverage for all
skill methods. Add equivalent subtests and round-tripper header assertions for
ListSkills, GetSkill, and GetSkillShadows, passing credentials through each
method’s query.Caller and validating the expected request so dropped identity
propagation fails the test.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: e7baf0c5-1372-4280-876d-c688a6bdf32d
⛔ Files ignored due to path filters (8)
docs/qa/charters/CH-managed-session-skill-loading.mdis excluded by!**/*.mddocs/qa/journeys/J-load-skill-in-managed-session.mdis excluded by!**/*.mddocs/qa/reports/2026-08-05-issue-314-managed-skill-loading.mdis excluded by!**/*.mddocs/qa/scenarios/ET-managed-session-skill-loading.mdis excluded by!**/*.mdopenapi/compozy.jsonis excluded by!**/*.jsonpackages/site/content/docs/skills/index.mdxis excluded by!**/*.mdxskills/compozy/references/tools-and-skills.mdis excluded by!**/*.mdweb/src/generated/compozy-openapi.d.tsis excluded by!**/generated/**,!**/generated/**,!**/*.d.ts
📒 Files selected for processing (41)
internal/acp/agent_process.gointernal/acp/agent_transport.gointernal/acp/agent_transport_unix.gointernal/acp/agent_transport_windows.gointernal/acp/client_integration_test.gointernal/acp/client_process.gointernal/acp/client_process_lifecycle_test.gointernal/acp/client_test_support_test.gointernal/acp/launcher.gointernal/acp/launcher_tool_host_test.gointernal/acp/start_process.gointernal/acp/terminal.gointernal/acp/terminal_agent_transport_proxy.gointernal/acp/terminal_identity_env.gointernal/acp/types.gointernal/agentidentity/identity.gointernal/agentidentity/protocol/constants.gointernal/api/core/errors.gointernal/api/core/skill_scope.gointernal/api/core/skills.gointernal/api/core/skills_test.gointernal/api/spec/agent_identity_contract.gointernal/api/spec/registry_skills.gointernal/api/spec/spec_test.gointernal/cli/client_agent_actions.gointernal/cli/client_agent_kernel_types.gointernal/cli/client_managed_transport_unix.gointernal/cli/client_managed_transport_unix_test.gointernal/cli/client_managed_transport_windows.gointernal/cli/client_resources_skills_hooks.gointernal/cli/client_settings_vault.gointernal/cli/client_test.gointernal/cli/skill_daemon_test.gointernal/cli/skill_workspace.gointernal/daemon/session_manager_deps.gointernal/daemon/session_manager_factory.gointernal/session/manager_defaults.gointernal/session/manager_options.gointernal/session/manager_start_env.gointernal/session/manager_test.gointernal/session/manager_types.go
💤 Files with no reviewable changes (1)
- internal/api/core/skills.go
| func (h *agentSkillTransportHandler) writeProxyError(writer http.ResponseWriter, _ *http.Request, err error) { | ||
| message := "daemon is not reachable through the managed transport" | ||
| if err == nil { | ||
| message = "managed transport failed" | ||
| } | ||
| writeAgentTransportError(writer, http.StatusServiceUnavailable, "managed_transport_unavailable", message) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Errors in the transport error path are dropped.
Two problems on the same path:
- Lines 100-107: the
ifguard on the encode error is inert. Its body contains onlyreturn, andreturnis already the end of the function. Thenet.ErrClosedtest changes nothing. Any JSON encode failure disappears. - Lines 69-75:
writeProxyErrornever useserr.httputil.ReverseProxycallsErrorHandleronly with a non-nil error, so theerr == nilbranch is unreachable and the real upstream failure is never logged. A managed session that cannot reach the daemon socket producesmanaged_transport_unavailablewith no diagnostic trace on the daemon side.
Give agentSkillTransportHandler a *slog.Logger and log both errors. Keep the client-facing message generic so the daemon socket path is not exposed to the managed process.
🐛 Proposed fix
type agentSkillTransportHandler struct {
proxy *httputil.ReverseProxy
transport *http.Transport
sessionID string
agentName string
+ logger *slog.Logger
}-func (h *agentSkillTransportHandler) writeProxyError(writer http.ResponseWriter, _ *http.Request, err error) {
- message := "daemon is not reachable through the managed transport"
- if err == nil {
- message = "managed transport failed"
- }
- writeAgentTransportError(writer, http.StatusServiceUnavailable, "managed_transport_unavailable", message)
+func (h *agentSkillTransportHandler) writeProxyError(writer http.ResponseWriter, request *http.Request, err error) {
+ if h.logger != nil {
+ h.logger.Warn(
+ "acp: managed transport proxy failed",
+ "path", request.URL.Path,
+ "session_id", h.sessionID,
+ "error", err,
+ )
+ }
+ writeAgentTransportError(
+ writer,
+ http.StatusServiceUnavailable,
+ "managed_transport_unavailable",
+ "daemon is not reachable through the managed transport",
+ )
}-func writeAgentTransportError(writer http.ResponseWriter, status int, code string, message string) {
+func writeAgentTransportError(
+ writer http.ResponseWriter,
+ status int,
+ code string,
+ message string,
+ logger *slog.Logger,
+) {
writer.Header().Set("Content-Type", "application/json")
writer.WriteHeader(status)
if err := json.NewEncoder(writer).Encode(map[string]any{
EventTypeError: map[string]any{
"code": code,
"message": message,
},
- }); err != nil && !errors.Is(err, net.ErrClosed) {
- return
+ }); err != nil && !errors.Is(err, net.ErrClosed) && logger != nil {
+ logger.Warn("acp: managed transport error response failed", "code", code, "error", err)
}
}As per coding guidelines: "Never discard errors with _ in production code or tests; handle every error or document a justification."
Also applies to: 97-108
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/acp/terminal_agent_transport_proxy.go` around lines 69 - 75, Update
agentSkillTransportHandler to hold a *slog.Logger, use it in writeProxyError to
log the non-nil transport error while retaining the generic client-facing
response, and remove the inert encode-error guard in the related
response-writing path so encode failures are logged or otherwise handled rather
than discarded.
Source: Coding guidelines
| func assertManagedSkillIdentityHeaders(t *testing.T, operation *openapi3.Operation) { | ||
| t.Helper() | ||
| assertParameter(t, operation, agentidentity.HeaderSessionID, openapi3.ParameterInHeader, false) | ||
| assertParameter(t, operation, agentidentity.HeaderAgent, openapi3.ParameterInHeader, false) | ||
| assertResponseStatus(t, operation, http.StatusUnauthorized) | ||
| assertResponseStatus(t, operation, http.StatusForbidden) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Assert the documented authorization error bodies.
assertResponseStatus only proves that the 401 and 403 response entries exist. It does not prove that either entry has the declared contract.ErrorPayload body and required fields.
Use jsonResponseSchema and assert the required error fields for both responses.
As per coding guidelines, “assert both status codes and bodies”; path instructions require “specific error assertions.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/api/spec/spec_test.go` around lines 3177 - 3183, Update
assertManagedSkillIdentityHeaders to validate the documented bodies for both
unauthorized and forbidden responses, not only their status entries. Use
jsonResponseSchema to obtain each response schema and assert the required
contract.ErrorPayload fields with the existing specific error assertion helpers.
Sources: Coding guidelines, Path instructions
| return &http.Client{Transport: transport, Timeout: defaultUnixSocketClientTimeout}, | ||
| &http.Client{Transport: transport}, true, nil |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline internal/cli/client_managed_transport_unix.go --items all
rg -n -C 6 '\bmanagedAgentTransportClients\s*\(' internal/cliRepository: compozy/compozy
Length of output: 3217
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- internal/cli/client_managed_transport_unix.go ---\n'
cat -n internal/cli/client_managed_transport_unix.go
printf '\n--- usages of managedHTTPClient and managedStreamClient ---\n'
rg -n -C 5 'managedHTTPClient|managedStreamClient|NewClient\(' internal/cli
printf '\n--- unixSocketClient definitions/usages in internal/cli ---\n'
rg -n -C 5 'type unixSocketClient|func \(.*unixSocketClient|managedHTTPClient|managedStreamClient' internal/cli
printf '\n--- relevant transport timeout definitions ---\n'
rg -n 'defaultUnixSocketClientTimeout|idle|keep|timeout|Timeout:' internal/cli client* internal -g '*.go' | head -n 120Repository: compozy/compozy
Length of output: 244
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- internal/cli/client_managed_transport_unix.go ---'
cat -n internal/cli/client_managed_transport_unix.go
echo
echo '--- uses managedHTTPClient and managedStreamClient ---'
rg -n -C 5 'managedHTTPClient|managedStreamClient|NewClient\(' internal/cli
echo
echo '--- unixSocketClient definitions and managed client uses ---'
rg -n -C 5 'type unixSocketClient|func \(.*unixSocketClient|managedHTTPClient|managedStreamClient' internal/cli
echo
echo '--- timeout definitions in relevant CLI files ---'
rg -n 'defaultUnixSocketClientTimeout|idle|keep|timeout|Timeout:' internal/cli -g '*.go' | head -n 180Repository: compozy/compozy
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- internal/cli/client_managed_transport_unix.go ---'
cat -n internal/cli/client_managed_transport_unix.go
echo
echo '--- NewClient implementation ---'
sed -n '1,80p' internal/cli/client_settings_vault.go | cat -n
echo
echo '--- unixSocketClient struct and Do helpers ---'
fd 'client_.*\.go$' internal/cli -E 'client_automation.go' -E 'client_task_lifecycle.go' -E 'client_marketplace.go' -E 'client_extension_scoped_reads.go' -E 'client_bridge_target_resolution.go' -E 'client_extension_commands.go' -E 'client_memory_decisions.go' -E 'client_task_management.go' -E 'client_bridge_setup.go' -E 'client_session_commands.go' -E 'client_managed_transport_unix.go' -E 'client_managed_transport_windows.go' | sort | while read -r f; do
rg -n -C 5 'type unixSocketClient|func \(c \*unixSocketClient\).*Do|http\.Client' "$f" || true
done
echo
echo '--- focused Do and client fields search'
rg -n -C 4 'type unixSocketClient|managedHTTPClient|managedStreamClient|func \(c \*unixSocketClient\) Do|DoWithContext|DoJSON|DoStream|http\.Client' internal/cli -g 'client_*.go' --glob '!**/*.md' --glob '!**/client_automation.go' --glob '!**/client_task_lifecycle.go' --glob '!**/client_marketplace.go' --glob '!**/client_extension_scoped_reads.go' --glob '!**/client_bridge_target_resolution.go' --glob '!**/client_extension_commands.go' --glob '!**/client_memory_decisions.go' --glob '!**/client_task_management.go' --glob '!**/client_bridge_setup.go' --glob '!**/client_session_commands.go' --glob '!**/client_managed_transport_unix.go' --glob '!**/client_managed_transport_windows.go' | head -n 220Repository: compozy/compozy
Length of output: 48801
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- internal/cli/client_transport.go relevant sections ---'
rg -n 'func \(c \*unixSocketClient\) (doRequest|doJSON|doSSE|streamHTTPClient)|func doRequest|func (doRequestWithContext|doJSONWithContext|doJSON|doSSE|doSSEWithContext)' internal/cli/client_transport.go
sed -n '1,140p' internal/cli/client_transport.go | cat -n
sed -n '140,270p' internal/cli/client_transport.go | cat -n
echo
echo '--- const/default values and stream definitions ---'
rg -n 'defaultUnixSocketClientTimeout|type ClientContext|func \(.*ClientContext.*Context|context\.With|WithoutCancel|streamClient|streamHTTPClient|doSSE\(' internal/cli -g '*.go' | head -n 220
echo
echo '--- behavioral probe for Go http.Client unbounded timeout with context timeout ---'
tmpdir="$(mktemp -d)"
trap 'rm -f "$tmpdir"/server "$tmpdir"/client.go "$tmpdir"/read' EXIT
cat > "$tmpdir/server.go" <<'GO'
package main
import (
"context"
"log"
"net/http"
"net/http/httptest"
)
func main() {
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
<-r.Context().Done()
log.Println("server seen context done:", r.Context().Err())
w.WriteHeader(200)
w.Write([]byte("ok"))
}))
server.StartUnix(fixPath(server.Listener.Addr().String()))
select {}
}
func fixPath(addr string) string {
if len(addr) >= 2 && addr[:2] == "@" {
return "\x00" + addr[2:]
}
return addr
}
GO
cat > "$tmpdir/client.go" <<'GO'
package main
import (
"context"
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
if len(os.Args) != 3 {
fmt.Println("usage client <with-timeout-client|without-timeout-client> <socket>")
os.Exit(2)
}
mode := os.Args[1]
socket := os.Args[2]
transport := &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
var dialer net.Dialer
return dialer.DialContext(context.Background(), "unix", socket)
},
}
var client *http.Client
if mode == "with-timeout-client" {
client = &http.Client{Transport: transport, Timeout: 500 * time.Millisecond}
} else {
client = &http.Client{Transport: transport}
}
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", "http://socket/test", nil)
if err != nil {
fmt.Println("new request", err)
os.Exit(2)
}
start := time.Now()
resp, err := client.Do(req)
elapsed := time.Since(start)
switch err := err.(type) {
case nil:
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
fmt.Println("complete", elapsed, "error", "none")
case *net.OpError:
fmt.Println("error", elapsed, "operror", err)
default:
fmt.Println("error", elapsed, "kind", fmt.Sprintf("%T", err), "msg", err)
}
client.CloseIdleConnections()
time.Sleep(1 * time.Second)
}
GO
printf '\nServer pid:'
go run "$tmpdir/server.go" &
server_pid=$!
sleep 0.4
socket_path="$tmpdir/server"
printf 'Without HTTP Timeout client:'
go run "$tmpdir/client.go" without-timeout-client "$socket_path" | sed -u 's/^/[client without-timeout-client] /'
printf 'With HTTP Timeout client:'
go run "$tmpdir/client.go" with-timeout-client "$socket_path" | sed -u 's/^/[client with-timeout-client] /'
kill "$server_pid" 2>/dev/null || trueRepository: compozy/compozy
Length of output: 14820
🌐 Web query:
Go net/http Client Timeout zero request deadline indefinite documentation net/http Do
💡 Result:
In Go's net/http package, the Timeout field of an http.Client specifies a time limit for requests executed by that client [1][2]. The official documentation explicitly states that a Timeout of zero means no timeout [3][4]. When the Timeout is set to zero (which is the default behavior for http.DefaultClient), there is no deadline applied to the request [1][5]. The client will wait indefinitely for the server to respond, or until the underlying network connection times out on its own [6][5]. Key points regarding the Timeout field: - Scope: The Timeout covers the entire HTTP exchange, including connection establishment (dialing), any redirects, and reading the response body [1][6]. - Behavior: If a timeout is set (non-zero), the timer continues to run after the Client's methods (like Do, Get, or Post) return, meaning it can interrupt the reading of the response body if the specified duration is exceeded [1][4]. - Cancellation: When a timeout occurs, the client cancels the request to the underlying Transport as if the request's context had ended [1][2]. - Default: Because http.DefaultClient uses a zero value for Timeout, it does not impose a deadline on requests by default, which can lead to hanging requests in production environments if not explicitly configured [1][5]. For production applications, it is generally recommended to use a non-zero Timeout or, for more granular control, to use context.WithTimeout to manage request deadlines [6][5].
Citations:
- 1: https://pkg.go.dev/net/http
- 2: https://godocs.io/net/http
- 3: https://go.dev/src/net/http/client.go
- 4: https://github.com/golang/go/blob/master/src/net/http/client.go
- 5: https://www.golinuxcloud.com/golang-http-client-timeout/
- 6: https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/
Set a client timeout or handle cancellation for managedStreamClient.
managedStreamClient is returned with a zero Timeout and is used by doSSE() via streamHTTPClient() for unbounded SSE streams. If the stream is not expected to stay open indefinitely, give the client an appropriate bound; otherwise document that context cancellation drives the read deadline.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/cli/client_managed_transport_unix.go` around lines 28 - 29, Update
the managedStreamClient returned by the Unix transport constructor to either set
an appropriate non-zero Timeout for bounded SSE streams or explicitly document
that doSSE cancellation via context controls the read deadline when indefinite
streaming is intentional. Keep the existing default timeout on the regular HTTP
client and ensure streamHTTPClient uses the chosen behavior.
Source: Coding guidelines
| managedHTTPClient, managedStreamClient, managed, err := managedAgentTransportClients() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if managed { | ||
| return &unixSocketClient{ | ||
| socketPath: managedAgentTransportLabel, | ||
| httpClient: managedHTTPClient, | ||
| streamClient: managedStreamClient, | ||
| }, nil | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Map NewClient callers and the managed transport discovery contract.
set -euo pipefail
rg -nP -C 3 '\bNewClient\s*\(' internal --glob '!**/*_test.go'
fd -t f 'client_managed_transport_unix.go|client_managed_transport_windows.go' internal/cli --exec cat -n
rg -nP -C 6 'isAgentSkillTransportRequest|managed_transport_denied' internal/acpRepository: compozy/compozy
Length of output: 16436
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the daemon client implementation and transport selection.
sed -n '1,140p' internal/cli/client_settings_vault.go
printf '\n--- callers of internal/cli client constructor ---\n'
rg -nP -C 3 'client_settings_vault|DaemonClient|NewClient\(\s*' internal --glob '!**/*_test.go' | sed -n '1,220p'
printf '\n--- managed transport route predicate ---\n'
sed -n '45,92p' internal/acp/terminal_agent_transport_proxy.goRepository: compozy/compozy
Length of output: 19902
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- requireExtensionDaemonClient definitions ---'
rg -nP -C 10 'func requireExtensionDaemonClient|requireExtensionDaemonClient\s*=' internal/cli
printf '%s\n' '--- newDaemonClient/requireDaemonClient definitions ---'
rg -nP -C 10 'func require|newDaemonClient|DaemonClient\s*=' internal/cli --glob '!**/*_test.go'Repository: compozy/compozy
Length of output: 26849
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- requireExtensionDaemonClient implementation ---'
sed -n '99,120p' internal/cli/extension_state.go
printf '%s\n' '--- daemonClientIfRunning implementation ---'
sed -n '1,60p' internal/cli/daemon_client_reachability.go
printf '%s\n' '--- NewClient call construction in root_runtime.go ---'
sed -n '1,60p' internal/cli/root_runtime.goRepository: compozy/compozy
Length of output: 3441
Scope the managed transport to skill reads and keep the daemon socket for other CLI commands.
NewClient always selects the managed transport when an agent socket is present, then all DaemonClient methods that call non-/api/skills/* routes get managed_transport_denied. These commands only need the managed transport for skill-related reads; keep NewClient on the daemon socket or add a separate constructor for the skill read path. Also, failed discovery should not stop commands; log the cause and build the daemon client when the managed socket is unavailable or stale.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/cli/client_settings_vault.go` around lines 34 - 44, Update NewClient
so managedAgentTransportClients is used only for skill-read requests under
/api/skills/*, while all other DaemonClient methods continue using the daemon
socket. If managed transport discovery fails or returns a stale/unavailable
socket, log the discovery error and fall back to constructing the daemon client
instead of returning the error; preserve managed transport selection for valid
skill-read requests.
54edf3d to
f14fefb
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/mcp/hosted.go (1)
209-210: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winKeep unarmed launch records bounded.
Armed launch cleanup is covered by the existing bind and stop paths, but failed arms can leave
s.launches[sessionID]without settingexpiresAtorarmed. ReturnCancelLaunch(sessionID)for failedArmLaunchor add a bounded sweeper for unarmed records. An unarmed record cannot reachBindcleanup via the currentErrHostedNonceInvalidpath.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/mcp/hosted.go` around lines 209 - 210, Update the failed ArmLaunch handling around the launch record initialized with nonceHash and expectedBin so every failed arm invokes CancelLaunch(sessionID), or otherwise ensure unarmed records are removed by a bounded sweeper. Preserve cleanup for successfully armed launches while preventing unarmed entries from remaining indefinitely in s.launches.
🧹 Nitpick comments (5)
internal/cli/skill_daemon_test.go (1)
422-481: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd the operator-shell negative case.
Every subtest here asserts rejection. No test in the changed range asserts that
skillsubcommands still run whenCOMPOZY_SESSION_IDandCOMPOZY_AGENTare both empty. The change details state that the previous session-scoped tests were replaced, so the allowed path may now be uncovered. A future inversion of the condition inensureSkillCLIUsesSupportedSurfacewould keep this suite green.Add one subtest that leaves both markers empty and asserts that
skill listreaches the daemon client.💚 Proposed additional subtest
t.Run("Should allow skill list from an operator shell", func(t *testing.T) { t.Parallel() clientCalls := 0 deps := newWorkspaceTestDeps(t, &stubClient{}) deps.newClient = func(string) (DaemonClient, error) { clientCalls++ return &stubClient{}, nil } deps.getenv = func(string) string { return "" } if _, _, err := executeRootCommand(t, deps, "skill", "list"); err != nil { t.Fatalf("skill list error = %v, want success from operator shell", err) } if clientCalls == 0 { t.Fatal("skill list client calls = 0, want daemon access from operator shell") } })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/cli/skill_daemon_test.go` around lines 422 - 481, Add an operator-shell subtest alongside TestSkillCommandsRejectManagedSessionCLI that leaves both session and agent environment markers empty, executes “skill list,” asserts no error, and verifies the configured daemon client is called at least once. Keep the existing managed-session rejection cases unchanged.internal/cli/skill_managed_guard.go (1)
10-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAlign the guidance text with the blocked command set.
The guard blocks every
skillsubcommand, includinginstall,remove,create,enable,disable, andupdate. The error text only names the read-only toolscompozy__skill_list,compozy__skill_search, andcompozy__skill_view. A managed session that runscompozy skill installreceives guidance that does not apply to that operation.Consider separating the two cases: point read operations at the canonical tools, and state that mutating skill operations are operator-shell only.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/cli/skill_managed_guard.go` around lines 10 - 13, Update errManagedSessionSkillCLIUnsupported to distinguish read-only skill operations from mutating commands: direct list, search, and view requests to the canonical compozy tools, while explicitly stating that install, remove, create, enable, disable, and update require an operator shell.internal/testutil/acpmock/fixture_test.go (1)
696-696: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReuse the local constant instead of inlining the text.
Line 653 in this same file already uses
currentSkillsCatalogFinalLine. These three sites inline the identical string. Replace the literals with the constant so a text change updates all fixtures at once.Also applies to: 921-921, 946-946
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/testutil/acpmock/fixture_test.go` at line 696, Replace the three duplicated prompt string literals in the fixture entries with the existing currentSkillsCatalogFinalLine constant used earlier in the file, including the sites near the reported locations, so all fixtures share one maintainable value.internal/skills/catalog_test.go (1)
458-465: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThese substring assertions are brittle and partly redundant.
TestBuildCatalogFormatsCatalogSortedEscapedAndWithUsageInstructionsat line 33 already asserts the exact catalog output, including the managed loading line. Lines 458 and 464 restate parts of that same golden expectation.Line 461 is the weaker part. The bare
strings.Contains(got, "fallback")check fails on any future instruction that uses the word for an unrelated reason, and the lowercase"usecompozy skill view"` check misses other phrasings such as "invoke" or "run". Consider keeping only the negative assertion that carries the invariant: the catalog must not tell a managed session to run the CLI.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/skills/catalog_test.go` around lines 458 - 465, In TestBuildCatalogFormatsCatalogSortedEscapedAndWithUsageInstructions, remove the redundant substring assertions already covered by the exact catalog expectation, including the broad “fallback” and lowercase “use” checks. Retain a focused negative assertion in the BuildCatalog validation that rejects guidance telling managed sessions to invoke, run, or otherwise execute `compozy skill view`.internal/testutil/acpmock/fixture.go (1)
34-36: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winThe managed skill-loading instruction has no single source of truth. The same sentence now exists as
catalogManagedLoadingInstructionsininternal/skills/catalog.go, ascurrentSkillsCatalogFinalLinein the ACP mock, and as three inline literals in the mock tests. The mock uses this text to strip prompt augmentation incanonicalUserText, so any drift silently changes turn selection instead of failing loudly.
internal/testutil/acpmock/fixture.go#L34-L36: export the instruction text frominternal/skillsand reference it here, or add a test that assertscurrentSkillsCatalogFinalLineequals the catalog constant.internal/testutil/acpmock/fixture_test.go#L696-L696: replace the inline literal withcurrentSkillsCatalogFinalLine, matching line 653.internal/testutil/acpmock/fixture_test.go#L921-L921: replace the inline literal withcurrentSkillsCatalogFinalLine.internal/testutil/acpmock/fixture_test.go#L946-L946: replace the inline literal withcurrentSkillsCatalogFinalLine.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/testutil/acpmock/fixture.go` around lines 34 - 36, The managed skill-loading instruction is duplicated, allowing drift between production and mock behavior. In internal/testutil/acpmock/fixture.go lines 34-36, reuse an exported instruction constant from internal/skills/catalog.go (or otherwise enforce equality with it); in internal/testutil/acpmock/fixture_test.go lines 696, 921, and 946, replace each inline literal with currentSkillsCatalogFinalLine, matching the existing usage around line 653.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/acp/client_start_contract_test.go`:
- Around line 53-88: Refactor
TestStartActivatesMCPAfterInitializeBeforeSessionNegotiation into parallel
t.Run("Should …") success and failure subtests. Keep the success assertions for
activation ordering and ACP session/new, and add a failure subtest whose
ActivateMCPServers returns a known error, asserting the returned error wraps it
and that AgentMethodSessionNew is not captured.
---
Outside diff comments:
In `@internal/mcp/hosted.go`:
- Around line 209-210: Update the failed ArmLaunch handling around the launch
record initialized with nonceHash and expectedBin so every failed arm invokes
CancelLaunch(sessionID), or otherwise ensure unarmed records are removed by a
bounded sweeper. Preserve cleanup for successfully armed launches while
preventing unarmed entries from remaining indefinitely in s.launches.
---
Nitpick comments:
In `@internal/cli/skill_daemon_test.go`:
- Around line 422-481: Add an operator-shell subtest alongside
TestSkillCommandsRejectManagedSessionCLI that leaves both session and agent
environment markers empty, executes “skill list,” asserts no error, and verifies
the configured daemon client is called at least once. Keep the existing
managed-session rejection cases unchanged.
In `@internal/cli/skill_managed_guard.go`:
- Around line 10-13: Update errManagedSessionSkillCLIUnsupported to distinguish
read-only skill operations from mutating commands: direct list, search, and view
requests to the canonical compozy tools, while explicitly stating that install,
remove, create, enable, disable, and update require an operator shell.
In `@internal/skills/catalog_test.go`:
- Around line 458-465: In
TestBuildCatalogFormatsCatalogSortedEscapedAndWithUsageInstructions, remove the
redundant substring assertions already covered by the exact catalog expectation,
including the broad “fallback” and lowercase “use” checks. Retain a focused
negative assertion in the BuildCatalog validation that rejects guidance telling
managed sessions to invoke, run, or otherwise execute `compozy skill view`.
In `@internal/testutil/acpmock/fixture_test.go`:
- Line 696: Replace the three duplicated prompt string literals in the fixture
entries with the existing currentSkillsCatalogFinalLine constant used earlier in
the file, including the sites near the reported locations, so all fixtures share
one maintainable value.
In `@internal/testutil/acpmock/fixture.go`:
- Around line 34-36: The managed skill-loading instruction is duplicated,
allowing drift between production and mock behavior. In
internal/testutil/acpmock/fixture.go lines 34-36, reuse an exported instruction
constant from internal/skills/catalog.go (or otherwise enforce equality with
it); in internal/testutil/acpmock/fixture_test.go lines 696, 921, and 946,
replace each inline literal with currentSkillsCatalogFinalLine, matching the
existing usage around line 653.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 51ef9ca0-6e82-47a6-b330-831c65936a49
⛔ Files ignored due to path filters (9)
docs/articles/2026-05/03-one-daemon-four-surfaces.mdxis excluded by!**/*.mdxdocs/qa/bugs/BUG-20260805-hosted-mcp-cold-start-nonce-expiry.mdis excluded by!**/*.mddocs/qa/charters/CH-managed-session-skill-loading.mdis excluded by!**/*.mddocs/qa/journeys/J-load-skill-in-managed-session.mdis excluded by!**/*.mddocs/qa/reports/2026-08-05-issue-314-managed-skill-loading.mdis excluded by!**/*.mddocs/qa/scenarios/ET-managed-session-skill-loading.mdis excluded by!**/*.mdpackages/site/content/docs/skills/index.mdxis excluded by!**/*.mdxpackages/site/content/docs/skills/skill-md.mdxis excluded by!**/*.mdxskills/compozy/references/tools-and-skills.mdis excluded by!**/*.md
📒 Files selected for processing (23)
internal/acp/client_start_contract_test.gointernal/acp/client_test_support_test.gointernal/acp/start.gointernal/acp/types.gointernal/api/udsapi/hosted_mcp_test.gointernal/cli/skill_commands.gointernal/cli/skill_daemon_test.gointernal/cli/skill_managed_guard.gointernal/daemon/hosted_mcp.gointernal/daemon/native_tools_test.gointernal/daemon/prompt_skills_test.gointernal/mcp/hosted.gointernal/mcp/hosted_records.gointernal/mcp/hosted_test.gointernal/session/manager_start_env.gointernal/session/manager_start_mcp.gointernal/session/manager_test.gointernal/session/manager_test_support_test.gointernal/session/manager_types.gointernal/skills/catalog.gointernal/skills/catalog_test.gointernal/testutil/acpmock/fixture.gointernal/testutil/acpmock/fixture_test.go
Status notice
The rebased branch was pushed at commit
f14fefb1. A finalmake gate-fullwas started but canceled with exit code 130 by explicit user instruction. This PR does not claim a current full-gate PASS. The strict QA evidence audit therefore still reports C14 (missing finalmake verifyevidence).Summary
compozy__skill_viewtool only, including skills omitted from the prompt catalog.compozy skillremains an operator-shell interface; managed environment markers trigger an early supported-path UX guard before resolution, filesystem work, or a daemon request.Fixes #314
Context and reproduction
The original implementation attempted to provide a managed CLI fallback through a private read-only Unix socket and caller identity headers. That design could not enforce its security promise: arbitrary provider code runs with the daemon user's UID, can remove or replace environment variables, and can open the normal operator UDS directly. Environment values, headers, PID ancestry, and mode
0600cannot distinguish those same-UID requests without a real OS isolation boundary or a credential the provider cannot obtain.The supported managed path is therefore the existing daemon-native tool seam. A second failure appeared during behavioral QA: the hosted-MCP bind nonce was minted before ACP initialization. A cold Codex ACP launch took longer than the 30-second TTL, so the native tool seam expired before the provider could bind it.
Solution design
Native-only managed skill loading
compozy__skill_view.Operator CLI supported-path guard
compozy skillverb (list,view,info,inspect,where,search,install,remove,update,create,enable, anddisable) checks for managed-session markers before doing client, UDS, registry, or filesystem work.compozy__skill_list,compozy__skill_search, andcompozy__skill_view.Hosted-MCP nonce activation
Launchcreates an unarmed hosted-MCP launch record.initializesucceeds and immediately beforesession/neworsession/load.ArmLaunchstarts the bind TTL once and idempotently.Hard-cut delete targets
The unsafe managed CLI fallback was removed rather than retained as compatibility code:
COMPOZY_AGENT_TRANSPORT_SOCKETselection and managed CLI client transport;The normal agent-kernel identity contract already present on
origin/mainis unchanged.Changes by surface
origin/main; the proposed managed skill headers and generated schema changes were deleted.Invariants and tests
initialize → activation → session negotiation.Focused race-enabled checks completed before publication:
internal/cliinternal/mcpinternal/acpinternal/sessioninternal/skillsinternal/testutil/acpmockinternal/daemoninternal/api/udsapi4 successful, 4 total)Behavioral QA
zz-native-seam-marker.sess-08797076cb99e02f) used a provider wrapper with a 35-second startup delay.session/newtook 443 ms, and hosted bind succeeded in 79 ms without retry or nonce expiry.compozy__tool_infoat sequence 11,compozy__skill_viewat sequence 15, and the exact body at sequence 16.9eb83bd03cfa8e4763ff8c1808dda9f5305fae0f066a88e606fa07bb08e4ebca.compozy skill viewstill worked.clean: true, zero survivors, and removed the daemon and tmux sockets.Full report:
docs/qa/reports/2026-08-05-issue-314-managed-skill-loading.mdVerification status
clean: truemake gate-full— canceled by explicit user instruction; no PASS claimedCompozy Impact Audit
compozy__tool_infoandcompozy__skill_viewprojection; live persisted events prove the call path.skills/compozy/references/tools-and-skills.mdnow documents native-only managed loading, operator-only CLI use, the UX guard, and the same-UID non-claim.Compatibility and remaining risk
This is a zero-legacy hard cut: managed CLI fallback is unsupported and removed. Operator CLI behavior remains available from a normal shell. The UX guard is bypassable by arbitrary same-UID code that clears its environment or opens the operator UDS; preventing that requires OS-level isolation or an unforgeable credential and is outside this PR. No fallback to the normal UDS was added because that would widen privilege.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation