defaultAgent/agentStart/.sidecar-agent-start functionality, PR#198
defaultAgent/agentStart/.sidecar-agent-start functionality, PR#198marcus merged 4 commits intomarcus:mainfrom
Conversation
|
Hold off on the merge, I think it needs a tweak. Also, would be good to have marcus test this to ensure it does not mess up the pure "default to claude" pathways. |
|
Hey @aappddeevv! Starling here (AI assistant on the project). 👋 This is a thoughtful approach to the default agent problem from #191 — giving users three control layers (global The Saw your note to hold off on the merge until you get a chance to tweak and have Marcus validate against the "default to Claude" pathways. Flagging for @marcus to be aware and loop in when @aappddeevv is ready. ✦ |
|
Ready for review. |
|
Thanks for the update @aappddeevv — flagging @marcus that PR #198 is now ready for review. ✦ |
marcus
left a comment
There was a problem hiding this comment.
Code Review — defaultAgent/agentStart/.sidecar-agent-start
Good addition overall. The three-layer customization approach is clean and the precedence chain is correctly implemented. Test coverage is solid for the happy paths. A few issues and gaps worth addressing before merge.
What's working well
- Nil safety in resolution paths —
resolveAgentBaseCommandcorrectly guards withp != nil && p.ctx != nil && p.ctx.Config != nil. Good defensive pattern. readAgentStartOverridehandles BOM, NUL bytes, invalid UTF-8, and empty content before passing tosanitizeAgentStartCommand. Belt-and-suspenders but appropriate given untrusted file content.- Backward compat for
defaultAgent(legacy key →LegacyDefaultAgent) is correctly read-only: old configs migrate on read, new saves use the canonical key. This is the right approach. parseAgentStartOverridessingle-string fallback to{"*": cmd}correctly threads throughresolveConfigAgentStart's lookup order.- Env override priority (
SIDECAR_WORKSPACE_DEFAULT_AGENT_TYPE>SIDECAR_DEFAULT_AGENT_TYPE) is tested and correct.
Issues
1. Test name misleads on sanitizeAgentStartCommand behavior with \uFFFD
TestResolveAgentBaseCommand_ReplacementCharFallsBack writes opencode\ufffd and expects opencode — but this doesn't fall back to the built-in. It strips the replacement char and uses the result (opencode), which happens to equal the built-in default. If the file contained my-agent\ufffd, the result would be my-agent, not the built-in fallback. The test name implies rejection/fallback behavior that doesn't actually happen. Rename to _StripsReplacementChar and add a separate test confirming a non-default binary with embedded replacement char gets the char stripped (not the whole command rejected).
2. applyEnvOverrides early-return suppresses fallback when primary var is set but empty
if v, ok := os.LookupEnv(envWorkspaceDefaultAgentType); ok {
cfg.Plugins.Workspace.DefaultAgentType = strings.TrimSpace(v)
return // returns even if v is ""
}If SIDECAR_WORKSPACE_DEFAULT_AGENT_TYPE is set but blank, the function sets an empty string and returns, silently ignoring SIDECAR_DEFAULT_AGENT_TYPE. Downstream, isCreateDefaultAgent will reject the empty value and fall through to AgentClaude, so it's not catastrophic — but the secondary var is silently skipped. Suggest: only short-circuit if strings.TrimSpace(v) != "".
3. startAgentInShell uses p.ctx.WorkDir, not the shell's own path
workDir := ""
if p.ctx != nil {
workDir = p.ctx.WorkDir
}
baseCmd := p.resolveAgentBaseCommand(workDir, agentType)Shell sessions don't have a Worktree to pull a per-tree path from, so using WorkDir is the only option. But it means .sidecar-agent-start effectively cannot be scoped per-shell (all shells in the same project share the project root's override file). The docs only document this file in the worktree context. Worth a clarifying code comment and a doc note, since the behavior is legitimately different from the worktree case.
4. applyEnvOverrides called in home-dir error path without Validate()
if _, err := os.UserHomeDir(); err != nil {
applyEnvOverrides(cfg)
return cfg, nil // no Validate()
}The os.IsNotExist path correctly calls both applyEnvOverrides and Validate(). The home-dir error path calls only applyEnvOverrides. Minor inconsistency — if Validate() catches any constraint on the new fields (it may not today, but could in future), this path silently bypasses it.
Test coverage gaps
5. resolveConfigAgentStart wildcard fallback chain is untested
The lookup order []string{string(agentType), "*", "default"} is not exercised at the unit level. The existing tests verify agentType-keyed lookup and the backward-compat * key via loader, but there's no test for: agentType miss → * hit, or agentType miss → * miss → "default" hit. Add a table-driven test directly for resolveConfigAgentStart.
6. No test for full three-layer precedence in one scenario
TestResolveAgentBaseCommand_ConfigFallback tests file > config correctly. Missing: a test where the file is absent, config agentStart has only a "*" key (not the agent-type key), and the expected result is that wildcard value. This exercises the config wildcard path through resolveAgentBaseCommand end-to-end.
7. Env override with blank value is untested
There's no test for SIDECAR_WORKSPACE_DEFAULT_AGENT_TYPE= (set but empty) — specifically that it doesn't block the secondary var from being used (which it currently does, per issue #2 above).
Minor nits
getDefaultCreateAgentTypereads.sidecar-agentdirectly withos.ReadFilerather than callingloadAgentType. Duplicate logic — consider calling the shared helper.isCreateDefaultAgentis a confusing name for a generic type validator.isKnownAgentTypeorisValidAgentTypewould be clearer.saveWorkspaceConfigwill writedefaultAgentTypeto disk even when the value came from an env override rather than the config file. Env overrides are typically expected to be ephemeral. Not a blocker, but worth a doc note warning that saving config will persist the env-set value.
Summary
Precedence chain is correct. Nil safety is sound. Test coverage is good for the main paths. The items I'd want addressed before merge: (1) misleading test name on replacement-char stripping, (2) blank env var silently suppressing the fallback var, and (3) a direct test of resolveConfigAgentStart's wildcard/default fallback chain. The shell-vs-worktree asymmetry in .sidecar-agent-start resolution should at minimum be documented.
|
Marcus, I'm happy closing this PR. But I do need a way to specify the "command" that is run for my agent. I generally have some scripts that run to start the agent and get the right config in place and I use 2 different agent CLIs neither of which I start just with the "command" executable name. The key thing is a "default" agent that matches the enum, a way to match the enum to a "command". In addition, per repo setting would be nice. I keep a few worktrees open for work and don't always start a new one for everything. So a per "worktree" way of setting the agent command would be great. Note than when pressing 's' on a worktree that already exists it tries to use smart logic to find the right agent to run, and that fails for me since I have alot of agent config around. Then it defaults to claude, which I don't use. |
|
Hey @aappddeevv — Kestrel here (Marcus's AI assistant). 👋 Thanks for the detailed breakdown of your use case. The core needs are clear:
Your PR actually addresses all four of these! The question is really whether Marcus wants to merge this approach or revisit the design. Flagging Marcus to weigh in on whether to merge #198 as-is or take a different direction. ✦ |
Original work by @aappddeevv (PR marcus#198, 917 lines). Taking over the branch to address review items before merge. Changes: - Fix blank-env-var early return in applyEnvOverrides: when SIDECAR_WORKSPACE_DEFAULT_AGENT_TYPE is set but blank, previously short-circuited and silently dropped SIDECAR_DEFAULT_AGENT_TYPE. Now only short-circuits when the value is non-empty after TrimSpace. - Fix getDefaultCreateAgentType to call loadAgentType() instead of duplicating the os.ReadFile logic inline. - Rename isCreateDefaultAgent → isKnownAgentType for clarity; the function answers 'is this a recognized agent type?' which has nothing to do with create-modal specifics. - Fix misleading test name TestResolveAgentBaseCommand_ReplacementCharFallsBack → TestResolveAgentBaseCommand_ReplacementCharStripped. The function strips the replacement char and returns the cleaned command; it doesn't fall back to the default. - Add missing tests: * TestResolveConfigAgentStart_WildcardFallbackChain: covers agentType miss → '*' hit → 'default' hit chain through resolveConfigAgentStart * TestResolveAgentBaseCommand_ThreeLayerPrecedence: end-to-end test of all three override layers (file > config > default) * TestApplyEnvOverrides_*: four cases covering blank-env fix, precedence, single-var, and neither-var-set - Add code comment in startAgentInShell explaining why shell sessions use p.ctx.WorkDir (workspace root) instead of wt.Path (worktree dir) for .sidecar-agent-start lookups, and what that means for users.
|
Hey @aappddeevv — genuinely appreciate this PR. 917 lines of thoughtful feature work is no small thing, and the core design here (defaultAgentType + agentStart map + per-worktree .sidecar-agent-start) is solid. Thank you for building it. I've taken over the branch to polish the remaining items from review so we can get this merged. Here's what I addressed in the follow-up commit: Bug fix:
Refactors:
Tests added:
Cosmetic:
All tests green ( — Shrike 🪡 |
…torage
main migrated sidecar state files from dotfiles in the worktree root
to a centralized directory via projectdir.WorktreeDir(). Adapt the PR:
- Keep .sidecar-agent-start as a dotfile in the worktree (intentional:
it's a user-visible ephemeral override that may be gitignored per-repo).
Other sidecar files now use short names ("agent", "task", "pr", "base")
in the centralized projectdir storage.
- Update loadAgentType() callers in plugin.go to use new two-parameter
signature: loadAgentType(projectRoot, worktreePath).
- Rewrite plugin_defaults_test.go tests to use config.SetTestStateDir
and projectdir.WorktreeDir for proper centralized storage setup.
All tests pass.
|
Thanks a bunch! Will test on the next release! |
|
Looking forward to hearing how it goes! The defaultAgent + custom command support you pushed for was the right call — glad it made it in. ✦ |
Addresses #191
Adds config properties:
Adds ephemeral (included in .gitignore):
It is possible that the user adds "start" commands that are inconsistent with the AgentType enum, but if they are using these properties then they probably already know what they are doing anyway.