-
Notifications
You must be signed in to change notification settings - Fork 275
codemode: fix Start() fail-fast and use tools.As for wrapper unwrapping #2007
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -103,27 +103,23 @@ func (c *codeModeTool) Tools(ctx context.Context) ([]tools.Tool, error) { | |
| } | ||
|
|
||
| func (c *codeModeTool) Start(ctx context.Context) error { | ||
| for _, t := range c.toolsets { | ||
| if startable, ok := t.(tools.Startable); ok { | ||
| if err := startable.Start(ctx); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| return c.forEachStartable(func(s tools.Startable) error { return s.Start(ctx) }) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 NOTABLE: No state tracking for cleanup after partial startup failures When The
Recommendation: Either:
|
||
| } | ||
|
|
||
| func (c *codeModeTool) Stop(ctx context.Context) error { | ||
| var errs []error | ||
| return c.forEachStartable(func(s tools.Startable) error { return s.Stop(ctx) }) | ||
| } | ||
|
|
||
| // forEachStartable applies fn to every toolset that implements Startable, | ||
| // collecting all errors so that one broken toolset doesn't block the others. | ||
| func (c *codeModeTool) forEachStartable(fn func(tools.Startable) error) error { | ||
| var errs []error | ||
| for _, t := range c.toolsets { | ||
| if startable, ok := t.(tools.Startable); ok { | ||
| if err := startable.Stop(ctx); err != nil { | ||
| if s, ok := tools.As[tools.Startable](t); ok { | ||
| if err := fn(s); err != nil { | ||
| errs = append(errs, err) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return errors.Join(errs...) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 CRITICAL: Semantic breaking change — fail-fast → collect-all-errors
The original
Start()implementation returned immediately on the first error:The new implementation attempts all toolsets regardless of failures:
Problem: If toolset A starts successfully but toolset B fails, the system is left in a partially-started state. There's no tracking of which toolsets succeeded, so:
Stop()will attempt to stop all toolsets regardless of whether they startedImpact: This changes startup semantics from "abort on first failure" to "try everything and report all failures". While the PR description mentions this is intentional ("one broken toolset shouldn't prevent others"), this could leave the system in an inconsistent state.
Recommendation: Consider adding state tracking (e.g., a
started []tools.Startablefield) to enable cleanup/rollback when Start() returns an error, or document that toolset Stop() implementations must handle being stopped without being started.