Commit Graph

258 Commits

Author SHA1 Message Date
Carlos Florêncio 49c482bd4d fix: prevent leaked agent processes on shutdown (#1498) 2026-06-26 10:09:23 +01:00
Carlos Florêncio b3e8af5519 fix: update ACP SDK large frame handling (#1496) 2026-06-25 19:17:56 +01:00
Clément Fiere 18366382b4 fix(backend): expand ~ home links and report missing file paths cleanly (#1468) 2026-06-23 12:13:46 +01:00
José Almeida 2781556ead perf: tame browser-grinding cumulative diffs during large rebases (#1430)
* perf(backend): cap cumulative diff bytes and file count during large rebases

* perf(web): coalesce cumulative-diff refetches and skip redundant status compares

* feat(web): warn when the changes panel hides files from a capped diff

* fix(backend): enforce cumulative diff total byte budget strictly

* fix(web): keep coalesced refetch timer shared across envKey subscribers

* fix(web): drop unsound git-status SHA fast-path that staled live diffs

* fix(web): cancel coalesced timer on mid-flight drain to avoid duplicate refetch

* refactor(web): move changes-panel banner and helper to a sibling file

* docs(backend): clarify cumulative diff truncated-files-count semantics

* fix(web): tighten useSelectedFileKey mode param to the all|file union
2026-06-18 17:28:21 +01:00
Clément Fiere fce3b421ea fix: split multi-file agent read links and strip line-range selectors (#1426) 2026-06-18 16:28:06 +01:00
Clément Fiere a71ee2e2c1 fix: open agent file links that carry line-range selectors (#1412) 2026-06-17 20:32:40 +01:00
Carlos Florêncio 92ed76b438 feat: auggie subagent detection and single-repo review diff (#1385) 2026-06-15 20:50:11 +01:00
Carlos Florêncio 3a3fca7f0e feat: add resource metrics topbar (#1373) 2026-06-14 22:33:34 +01:00
Carlos Florêncio d8b8d2b7c6 fix: map acp todo tool results to session todos (#1376) 2026-06-14 21:59:33 +01:00
Carlos Florêncio 010948767a feat: add file-backed prompt attachments (#1367) 2026-06-14 21:40:02 +01:00
Carlos Florêncio cf3469b4d1 feat: allow read-only absolute file paths (#1371) 2026-06-14 19:32:36 +01:00
Carlos Florêncio 2dd03b5431 feat: expose ACP session debug metadata (#1359) 2026-06-13 23:38:07 +01:00
Carlos Florêncio c724e38be9 fix: apply profile auto-approve to agentctl instances (#1358) 2026-06-13 23:22:54 +01:00
Nicolas Lenepveu 66e2ed226d fix(orchestrator): apply max inflight cap to Sentry watcher tasks (#1326) 2026-06-11 23:13:56 +01:00
Carlos Florêncio 326297c3e7 fix(agent): avoid oversized env spawn failures (#1344) 2026-06-11 22:25:58 +01:00
Carlos Florêncio 00500cca05 fix(acp): persist session model and drop composite id logic (#1327) 2026-06-11 11:07:22 +01:00
Carlos Florêncio dd182acfd2 fix(acp): bound long session resumes and restore legacy model surfaces (#1324) 2026-06-10 21:20:28 +01:00
Carlos Florêncio 68e7fc7c27 fix(acp): handle config-option-only model state (#1319) 2026-06-09 00:04:40 +01:00
Carlos Florêncio 64140ae942 fix(acpdbg): read claude agent models and modes from configOptions (#1310) 2026-06-08 22:44:02 +01:00
Carlos Florêncio 3e92d3abc6 fix: allow office mcp mode for existing tasks (#1315) 2026-06-08 21:22:30 +01:00
Carlos Florêncio c68323c9e0 fix: preserve settings sidebar on refresh (#1303) 2026-06-08 17:40:54 +01:00
Carlos Florêncio f81b5133bf fix(agentctl): preserve codex reasoning model ids (#1296) 2026-06-07 23:06:35 +01:00
Clément Fiere 0b40d09b10 fix(utility): list opencode models when ACP probe is empty (#1278) 2026-06-07 22:52:12 +01:00
João Salavisa f6f6fc3877 fix(agentctl): read models from configOptions fallback (#1291)
* fix(agentctl): read models/modes from configOptions fallback

claude-agent-acp v0.42 dropped the unstable `models` field from
session/new and publishes models only via `configOptions[]` with
category=model. The probe still relied on the legacy field, so
the inference-agents endpoint reported zero models and the UI
showed "Claude advertised no models."

Read the legacy fields first, then fall back to configOptions
when they are absent. Same fallback applied to modes for parity
with future agents.

* fix(agentctl): gate configOptions fallback on nil legacy field

Switch the fallback guard in `applySessionProbeFields` from
`len(out.Models) == 0` to `sessionResp.Models == nil` (same for
modes). A non-nil `Models` struct with an empty `AvailableModels`
slice is schema-valid: the legacy branch already stamps
`CurrentModelID`, and the previous guard would then fire the
configOptions fallback and overwrite it with a different source.

Adds `TestApplySessionProbeFields_LegacyEmptyModelsBlocksFallback`
covering the edge case.

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-06-07 14:36:51 +01:00
João Salavisa d83c1dd982 feat(diff): use stored base_branch for diff stats + per-task compare picker (#1273)
* feat(diff): use stored base_branch for diff stats + per-task compare picker

Task-card additions/deletions and the Changes panel were computed against a
hardcoded origin/main → master priority list inside agentctl. For tasks
branched off a release/staging branch this inflated the counts by every
commit since the actual fork point, and the Changes panel grouped unrelated
files alongside the task's real work.

This wires the per-repo task_repositories.base_branch end-to-end into the
agentctl WorkspaceTracker so each repo's diff stats resolve against the
recorded base instead of the integration-branch fallback. A picker on the
Changes panel branch hover card lets the user change the base mid-task; the
new value is persisted via PATCH /tasks/:id/repositories/:repo_id, pushed
live to the running agentctl instance, and the session's cached
base_commit_sha is cleared so the commits panel + cumulative diff refetch
against the new base without a session restart.

Falls back to the existing origin/main → master list for legacy tasks
(empty base_branch) and to the branch tip when no merge-base exists with
HEAD (unrelated histories — local rebase backups, etc.) so the stats and
commits panels stay anchored on the same SHA in every failure mode.

* fix(agentctl): sanitize user-controlled base_branch refs before git exec

CodeQL flagged the picker's user-controlled base_branch payload reaching
exec.Command("git", ..., baseBranch) inside workspace_git_cmd.go as a
command-built-from-user-controlled-source vulnerability. A malicious caller
could smuggle a leading-"-" argument like "-upload-pack=..." that git
itself reinterprets as a flag, even though exec.Command keeps args
separate from the shell.

Adds an IsSafeGitRef sanitizer at every boundary the value crosses on its
way to git: the service write path, the agentctl HTTP handlers
(/workspace/base-branches, /git/log, /git/cumulative-diff), and
WorkspaceTracker.SetBaseBranch itself. Unsafe values are rejected at the
service tier and silently downgraded to the no-override fallback inside
agentctl so a misbehaving caller can't half-break the tracker.

* fix(diff): address PR review blockers and CI lint

- Split UpdateRepositoryBaseBranch into validate/load/apply helpers to drop
  cyclomatic complexity below the cyclop limit that gated Run Backend Tests.
- Replace direct m.cfg.BaseBranches reads/writes with getBaseBranches /
  setBaseBranches under a dedicated RWMutex. UpdateBaseBranches used to
  write under repoTrackersMu while rescan + lazy-subpath callers read under
  workspaceTrackersMu, leaving an unsynchronized data race on the map.
- Drop the false 'PR target is auto-wired' claim from the MCP tool
  description — GitOperator.CreatePR takes baseBranch as an explicit arg
  and never reads the tracker's value.
- Picker no-op guard now compares against currentBase instead of storedBase
  so selecting the displayed fallback (legacy task before any picker
  change) is treated as no-op instead of triggering a spurious save.
- HTTP handler now maps non-validation service errors to 500 instead of
  400, classifying via the existing isValidationError helper plus the new
  unsafe-git-ref message.

* fix(diff): regex-based ref sanitiser at every git subprocess sink

CodeQL kept flagging workspace_git_cmd.go:43 even after SetBaseBranch
rejected unsafe values — the taint tracker doesn't follow sanitised stores
through struct fields, so it still saw user input flowing into
exec.Command("git", ..., baseBranch).

Switches IsSafeGitRef to a single regex check (a pattern CodeQL recognises
as a sanitiser barrier) and adds SanitizeGitRef calls inline at every
agentctl call site that splices a user-controlled ref into a git arg:

  - workspace_git_status.resolveStoredRef (before the rev-parse probes)
  - workspace_git_status.computeBaseCommit (before merge-base/rev-parse)
  - workspace_git_status.getAheadBehindCounts (before rev-list)
  - agentctl/server/api/git.go runGitLogForRepo + handleGitCumulativeDiff
    (before computeMergeBase/GetRevParse on the HTTP TargetBranch)

Behaviour is unchanged for valid refs — only narrows the allowlist (now
[A-Za-z0-9_./-]) so the sanitiser barrier is recognisable to static
analysis. SetBaseBranch's field-store sanitiser stays as defence in depth.

* fix(diff): transform sanitiser at every map read for CodeQL taint flow

CodeQL still flagged workspace_git_cmd.go:43 because:
- handleSetBaseBranches used an IsSafeGitRef bool guard before a verbatim
  map[k] = v copy. CodeQL recognises regex-transforming sanitisers but not
  bool-guarded passthroughs.
- lookupBaseBranch returned m.cfg.BaseBranches[k] verbatim. Even though
  the map was sanitised on write, taint flow across map stores + field
  stores loses the sanitised state in the analyser.

Switches both sites to the transforming SanitizeGitRef helper so every
read produces a freshly-sanitised value before it flows to a downstream
git argument. Behaviour is unchanged for valid refs.

* fix(diff): regex transformer sanitiser + guard live push from empty map

CodeQL still flagged the previous SanitizeGitRef because it returned the
input string unchanged on success — an identity passthrough that the taint
tracker doesn't recognise as a sanitiser barrier.

Switches to safeGitRefPattern.FindString so the output comes from the
regex match (not the parameter). IsSafeGitRef now wraps SanitizeGitRef so
the predicate and the transformer share one allowlist.

Also addresses two Claude review blockers:

- collectTaskBaseBranches returns a nil/empty map when the task has no
  recorded base_branches; pushing that to agentctl wipes every tracker's
  override. Now skipped — the DB row is the source of truth for the next
  session launch.

- applyBaseBranchSideEffects now runs on context.WithoutCancel so an HTTP
  / WS request cancel after the task_repositories UPDATE commits can't
  leave session.base_commit_sha stale + the live agentctl tracker map
  un-pushed, drifting from the persisted row.

* fix(diff): share securityutil.IsValidBranchName + harden error classification

CodeQL still flagged the picker's regex sanitiser as taint passthrough.
Refactors SanitizeGitRef / isSafeBaseBranchRef to delegate to the existing
securityutil.IsValidBranchName helper that agentctl's Rebase / Merge /
RenameBranch already use without being flagged. The shared allowlist also
fixes a Claude review blocker: 'feature/@2024' used to pass the service
validator but get dropped by the agentctl-side check at push time,
silently wiping every tracker's override.

Plus the two error-classification blockers Claude raised on the previous
HEAD:

- WS handler (task_ws_handlers.go wsUpdateTaskRepository) now distinguishes
  validation errors (surfaced verbatim) from internal faults (opaque
  500-style message). HTTP path already did this via the existing
  isValidationError helper.
- MCP handler (handlers.handleUpdateRepositoryBaseBranch) gets the same
  treatment via a new isValidationError local to the mcp/handlers
  package so raw service error strings stop leaking across the MCP
  boundary.

* fix(diff): inline securityutil.IsValidBranchName at every git subprocess sink

CodeQL's interprocedural taint tracking wasn't recognising SanitizeGitRef
as a sanitiser even after it delegated to securityutil.IsValidBranchName
— the wrapper helper + map/field hops between source and sink kept the
flow tainted in the analyser.

Inlines the IsValidBranchName allowlist check (already used inline by
Rebase/Merge in git.go without being flagged) at every call site that
splices a user-controlled ref into a git subprocess argument:

- workspace_git_status.resolveStoredRef (before rev-parse --verify)
- workspace_git_status.computeBaseCommit (before merge-base / rev-parse)
- workspace_git_status.getAheadBehindCounts (before rev-list)
- agentctl/server/api/git.go runGitLogForRepo + handleGitCumulativeDiff
- agentctl/server/api/workspace_base_branches.go handleSetBaseBranches

Behaviour unchanged: same allowlist as Rebase / Merge / RenameBranch
already use. SanitizeGitRef / IsSafeGitRef survive as defence-in-depth
helpers for callers that want a one-line check; they delegate to the same
securityutil helper so the rules stay aligned.

* fix(diff): inline regexp.MatchString at every git subprocess sink

CodeQL still flagged workspace_git_cmd.go:43 even with the inline
securityutil.IsValidBranchName helper — the taint tracker apparently does
not follow into helper functions for the go/command-injection rule on
this path.

Hoists the regex to package-scope vars (safeBranchRefPattern in both
process and api packages) and calls regexp.MatchString directly at each
sink site. Behaviour is identical to securityutil.IsValidBranchName; only
the call form changes.

* fix(diff): SHA-shape allowlist on BaseCommit before downstream git diff

CodeQL still flagged workspace_git_cmd.go:43 because update.BaseCommit
is the OUTPUT of an earlier merge-base / rev-parse call whose argument
list contained a user-controlled branch ref — the analyser treats that
command's output as still tainted even when the input ref was sanitised.

Adds an inline 40-hex SHA-shape allowlist check in enrichWithBranchDiff
right before `git diff --numstat <update.BaseCommit>` so the sanitiser
barrier sits in the same function as the next subprocess call. Behaviour
is unchanged: merge-base / rev-parse already produce SHA-shaped output.

* fix(diff): async tracker refresh + tighten init spread order

Two CodeRabbit/Claude review fixes:

- UpdateBaseBranches stamps each tracker's new baseBranch synchronously
  then runs RefreshGitStatus on a background goroutine instead of inside
  the picker-save HTTP handler. Per-tracker git subprocesses can each
  take ~3-5 calls; serialising them on the request goroutine made the
  PATCH response wait for every refresh to finish. The picker UI re-fetches
  via the existing WS stream once Refresh emits, so the handler doesn't
  need to block.

- updateTaskRepositoryBaseBranch now spreads options.init BEFORE the
  required method/body fields so an upstream caller's init overrides
  can't replace the PATCH method or the base_branch payload. Matches
  the convention deleteTask / moveTask already follow.

* fix(diff): narrow mcp isValidationError + opaque 500 + post-sanitize log count

Three final Claude review threads on the latest commit:

- mcp/handlers isValidationError drops the broad 'invalid' substring so
  DB / IO errors whose messages happen to contain 'invalid' don't
  misclassify as ErrorCodeValidation.

- task_http_handlers httpUpdateTaskRepository now logs the raw service
  error server-side and returns an opaque 'failed to update task
  repository' on the 500 path. Stops potential DB / IO detail leakage
  through the HTTP boundary.

- workspace_base_branches debug log reports accepted + rejected counts
  (post-sanitisation) instead of the pre-filter total so the metric
  actually reflects what hit UpdateBaseBranches.

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-06-05 15:53:39 +01:00
Carlos Florêncio df9a68a103 fix(agentctl): unblock next prompt when agent does not acknowledge cancel (#1259) 2026-06-03 22:36:24 +01:00
Carlos Florêncio 7917c95496 feat(backend): inject MCP servers in CLI passthrough mode per agent (#1158) 2026-06-03 00:09:05 +01:00
Carlos Florêncio 3e2aba45ed fix(agentctl): sticky-max context window size with reset on model switch (#1254) 2026-06-02 22:58:31 +01:00
João Salavisa 6a684211fc fix(agentctl): reap disconnected ACP sessions + MCP child trees (#1249)
* fix(agentctl): reap disconnected ACP sessions + MCP child trees (#1247)

Two independent leaks let opencode acp sessions accumulate indefinitely:

1. instance.Manager never reaped disconnected sessions. StopInstance
   only ran via the kandev DELETE route, so any session whose backend
   went away (UI closed mid-run, WS dropped, opencode failed to emit
   complete) was orphaned forever.
2. Even when DELETE fired, the ACP adapter unconditionally returned
   RequiresProcessKill=false. opencode acp keeps an HTTP server + MCP
   child tree alive after stdin EOF, so closing stdin was a no-op and
   the children re-parented to init.

Fix both:

- Plumb RuntimeConfig.RequiresProcessKill from agents.OpenCodeACP
  through the ExecutorCreateRequest -> CreateInstanceRequest ->
  InstanceConfig -> adapter.Config -> shared.Config chain. acp.Adapter
  now reads cfg.RequiresProcessKill instead of hardcoding false.
- Belt-and-braces: process.Manager.waitForProcessExit timeout fallback
  now killProcessGroup first (only falls back to single-process kill
  if that errors), so any future ACP CLI that ignores stdin EOF still
  gets reaped.
- Add an idle-instance reaper to instance.Manager. Per-instance
  activity middleware tracks inflight HTTP requests + lastActivity;
  a background goroutine sweeps every minute and StopInstance's any
  instance idle past KANDEV_ACP_IDLE_TIMEOUT (default 1h, set to 0
  to disable). Long-lived /agent/stream WS connections keep the
  inflight counter above zero so active sessions are never reaped.
- Validate stdio MCP commands at CreateInstance time via
  exec.LookPath; missing entries are dropped with a warn instead of
  spawning a permanently broken child every session (fixes the
  stale /snap/bin/brave MCP repro in the issue).

Regression tests: process-group kill (parent+child fixture), idle
reaper end-to-end, MCP validation, opencode-acp opt-in vs other
ACP agents' default.

* fix: address PR review feedback on #1247 fix

- idle_reaper: extract reapSingleIdleInstance helper with defer cancel(),
  add reaperStop polling between sweep iterations so Shutdown drains in
  one StopInstance window instead of N * timeout (cubic, claude, greptile).
- instance.Manager: use sync.Once instead of m.mu to gate reaper-stop
  channel close (claude).
- mcpStdioValidationError: tolerate compound Command strings by
  resolving only the first whitespace-delimited token; reject entries
  with neither URL nor Command outright; document agentctl-vs-agent
  PATH skew in Docker/SSH executor modes (claude, greptile, cubic).
- manager_killgroup_test: treat zombie state as dead via /proc/<pid>/stat
  on Linux + bump Eventually window to 15s — CI containers' PID 1 can
  delay harvesting orphans long enough to flake signal(0) probes.
- instance package: add goleak TestMain for the new reaper goroutine
  (coderabbit).
- AGENTS.md: correct SIGKILL vs SIGTERM wording; clarify the timeout
  fallback only fires on ctx.Done (coderabbit, cubic).

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-06-02 21:46:49 +01:00
Carlos Florêncio fac2ad5039 fix(backend): stabilize ACP session resume and workspace git poll under contention (#1242) 2026-06-02 21:22:45 +01:00
João Salavisa a1143aa525 fix(acp): handle claude-acp async_launched subagents end-to-end (#1244)
* fix(acp): handle claude-acp async_launched subagents end-to-end

Claude Agent SDK's Task tool with run_in_background=true returns
session/prompt with stop_reason=end_turn while subagents stay alive
in the background, reporting `_meta.claudeCode.toolResponse.status =
"async_launched"` instead of a terminal top-level status. kandev
previously treated stop_reason as authoritative, cancelled the
subagent card on prompt return, and fired an "agent finished without
producing any output" notice — even though the dispatch did produce
work and the subagent's child events would arrive seconds later.

Backend:
- `SubagentTaskPayload` gains `IsAsync` / `OutputFile` /
  `CanReadOutputFile` so the async-launched envelope is preserved.
- `convertToolCallResultUpdate` recognizes async_launched as terminal
  for the Task tool itself (dispatch is the completion).
- `cancelActiveToolCalls` preserves subagent_task tool calls through
  the prompt-return sweep so a late terminal tool_call_update from
  the SDK still lands correctly (covers the in-turn-complete case).

Frontend:
- `subagentMetaChips` emits a "background" chip when `is_async` or
  `status == "async_launched"` so the card is visually distinct.
- `computeEmptyTurnNotice` suppresses the notice when any subagent
  task message exists for the turn (the dispatch IS output).

Reproduced via `acpdbg prompt` against `claude-acp` 0.31.1; captured
frames in acp-debug/claude-acp-prompt-20260602-110119.jsonl drive the
backend test fixtures verbatim.

* fix(acp): harden async_launched override against future SDK frames

Address PR review feedback (greptile #1244, claude review):

- Move the async_launched override inside the mutex lock so it can
  inspect the stored payload kind before flipping the status.
- Drop the `status == ""` precondition. The earlier in_progress
  synthesis (Title/RawInput/Content/Locations) was implicitly trumping
  the override; a future SDK frame that combines async_launched with
  any of those fields would have left the card stuck in_progress
  forever. The override is now unconditional for async_launched
  subagent_task frames.
- Gate the override on `payload.Kind() == ToolKindSubagentTask`. A
  hypothetical future claude-acp tool that reuses the `async_launched`
  literal in its own `_meta.claudeCode.toolResponse.status` would have
  been incorrectly marked complete by the prior unscoped check.

Pinned by two new tests:
- TestConvertToolCallResultUpdate_AsyncLaunched_OverridesInProgress
- TestConvertToolCallResultUpdate_AsyncLaunched_NonSubagentNotTerminated

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-06-02 14:30:36 +01:00
João Salavisa cb7b5429d5 fix(tasks): harden multi-branch add_branch and surface it in UI (#1239)
Three areas were broken in the MCP add_branch_to_task flow:

1. Predictable branch names. autoBranchName counted up from existing
   branch-N rows. Concurrent add_branch calls raced onto the same name
   and the worktree directory leaked ordering. Now uses a random
   branch-<3 random chars> suffix via worktree.SmallSuffix.

2. Sibling materialize did not flow to the UI. The materializer
   published AgentctlReady with the NEW sibling worktree's path, which
   the frontend handler wrote onto the session row — clobbering the
   primary's worktree_id/branch and pointing the file browser at the
   sibling instead of the task root. Added TaskWorkspacePath to the
   payload, detected the sibling case on the frontend (existing
   worktree_id != payload's), kept the primary's id/branch, repointed
   worktree_path to the task root, bumped commits refetch, and dropped
   the pre-multi-repo legacy git-status entry so the Changes panel
   stops surfacing the frozen single-repo snapshot. File browser's
   resetKey now includes the worktree count so the tree reloads when
   the sibling lands.

3. workspace_path promotion + agentctl rescan refused to act on
   historical envs. A few envs had workspace_path pointing at the
   source repo's local_path (orchestrator computeWorkspacePath fallback
   to req.RepositoryPath when resp.WorktreePath is empty) instead of
   the primary worktree. The materializer's promotion gate compared
   against env.WorkspacePath's parent, so those envs were never
   promoted, and agentctl's resolveRescanPath only accepted
   parent-of-current so the rescan to the real task root was rejected.
   Trackers stayed on the wrong directory and the sibling's commits
   never appeared in the Commits panel even after a full reload.
   Anchored promotion on env.TaskDirName (basename cross-check) and
   added a recovery branch to resolveRescanPath that accepts any
   absolute directory which actually contains git-repo subdirs — the
   bearer-token middleware already gates the endpoint and
   scanRepositorySubdirs validates the children, so the relaxed check
   stays safe.

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 00:14:00 +01:00
Carlos Florêncio a8db7478ac fix(backend): cap gh/git fork concurrency via shared subproc throttles (#1216) 2026-06-01 20:35:43 +01:00
João Salavisa fd79d6170f feat(tasks): multi-branch tasks — N (repo, branch) pairs per task (#1226)
* feat(tasks): multi-branch tasks — N (repo, branch) pairs per task

Extends multi-repo into multi-branch so a single task can hold multiple
worktrees on the same repo (one per branch). Sibling worktrees layout at
<task>/<repo>/ + <task>/<repo>-<slug>/, dedicated PRWatch per branch,
live agentctl tracker rescan + frontend store updates so the UI splits
PRs / files / commits per branch without a session restart. MCP
add_branch_to_task_kandev tool is shielded to the worktree executor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(web): unify multi-branch repo label as <repo> · <branch> across panels

PR Changes and Commits panels rendered repo labels differently for multi-branch
tasks — Commits showed "kandev · feature/x" while PR Changes leaked the raw
"kandev-feature-x" agentctl tracker tag. Reformat tracker-tagged repo names via
formatRepoLabel using the longest known-repo prefix so kandev-cli-* doesn't
collapse to kandev · cli-*.

* fix(tasks): address multi-branch PR review — data races, slug collisions, MCP error codes

Backend
- agentctl process.Manager: add repoTrackersMu RWMutex guarding repoTrackers
  + workspaceTracker. Reads snapshot under RLock; RescanRepositories builds
  trackers first, takes Lock for swap/append. Validates newWorkDir via
  os.Stat before publishing cfg.WorkDir.
- worktree.Manager: reject invalid explicit BranchSlug up-front; cache key
  uses sanitized wt.BranchSlug so read/write paths are symmetric.
- worktree.store: narrow isDuplicateColumnError to "duplicate column name".
- task service AddBranchToTask: slug-collision guard rejects feat/a vs feat-a.
- orchestrator resolvePushRepo: two-pass exact-name match before subdir
  prefix fallback so "backend" doesn't swallow pushes for "backend-admin".
- executor buildRepoSpecs: duplicate repos always get a non-empty BranchSlug
  (falls back to branch-N when both branch fields sanitize to empty).
- MCP handler: classifyAddBranchError maps service errors to NOT_FOUND /
  CONFLICT / VALIDATION_ERROR; add ErrorCodeConflict constant.
- MCP server: extract registerAddBranchToTaskTool, register in task mode
  only (external mode contract: config tools + create_task_kandev).
- workspace_rescan handler: reject malformed JSON with 400 instead of
  silently rescanning the current root.
- branch_materializer: split MaterializeBranch into prepare + finalize
  helpers to satisfy the 80-line cap.

Frontend
- existingPrUrl returns undefined when a task has >1 PR so per-repo View PR
  buttons take over (no silent linking to a sibling).
- formatRepoLabel checks exact known-repo match before prefix splitting so
  "kandev-cli" isn't mislabeled as "kandev · cli".
- Repo chip key now includes base_branch so siblings differing only on
  base render as distinct chips.

Docs
- multi-branch spec uniqueness wording aligned with the canonical 4-column
  key (task_id, repository_id, base_branch, checkout_branch).
- worktree.CreateRequest.BranchSlug doc describes the sibling layout
  (<repo>-<slug>) instead of the obsolete nested form.

* fix(tasks): scope slug-collision check to repo only + use errors.Is for ErrTaskNotFound

- service_branches: drop base_branch from the slug-collision check. Worktree
  path is (repo, slug); two rows on the same repo cut from different bases
  still collide on disk.
- service_branches: prepareBranchAdd wraps taskrepo.ErrTaskNotFound via
  fmt.Errorf with %w so classifiers don't have to substring-match the
  formatted task UUID.
- mcp handlers: classifyAddBranchError uses errors.Is(err, ErrTaskNotFound).

* fix(agentctl): sanitize rescan work_dir before os.Stat to clear CodeQL path-injection

CodeQL flagged the rescan endpoint as a path-injection sink (the
HTTP-supplied work_dir flowed straight into os.Stat and downstream
scanRepositorySubdirs). Add validateRescanPath which:
- requires the input to be a clean absolute path with no ".." segments
- rejects anything that isn't equal to or an ancestor of the current
  cfg.WorkDir — the only legitimate use case is the branch materializer
  promoting <task>/<repo>/ to <task>/

Defense in depth: agentctl binds to localhost, but any colocated process
could otherwise direct it at /etc or the user's home by POSTing
/api/v1/workspace/rescan.

* fix(agentctl): tighten validateRescanPath, close TOCTOU on existingTrackers, add tests

- Drop the strings.Contains(clean, "..") guard. filepath.Clean already
  resolves traversal; the substring check rejects legitimate names like
  /home/u/..hidden without adding defense (the ancestor check via
  filepath.Rel is the real boundary).
- Drop the rel == "." branch in the ancestor check — early return for
  clean == currentClean already covers identity.
- Read existingTrackers inside the write-lock section (not under RLock
  then re-Lock) so two concurrent rescans can't both see 0 trackers and
  double-bootstrap the multi-repo set.
- Add table-driven TestValidateRescanPath covering rejection paths
  (relative, descendant, sibling, traversal-cleaned, empty input),
  legitimate ancestor promotion, exact match, no-anchor first-launch,
  and a /home/u/..hidden dotted-segment case.

* fix(agentctl): break taint flow into os.Stat — derive rescan path from cfg, not HTTP body

CodeQL's go/path-injection trace still flagged manager_rescan.go after the
prior sanitizer because the user-supplied newWorkDir flowed (after
validation) into os.Stat and then into cfg.WorkDir → tracker paths →
workspace_monitor os.Stat sinks.

Replace validateRescanPath with resolveRescanPath: it returns a path
DERIVED from currentWorkDir (no-op = currentClean, promotion =
filepath.Dir(currentClean)) and uses newPath only for comparison, never
as a source for the returned string. os.Stat now receives a path whose
provenance is trusted manager config.

Functionally the legitimate caller (branch materializer promoting
<task>/<repo>/ to <task>/) is unchanged.

* test(agentctl): make TestResolveRescanPath windows-portable

The Linux Backend Tests job passed but the Backend (windows) job rejected
every "absolute" case because filepath.IsAbs("/task/repo") is false on
Windows (no volume letter).

Build inputs via filepath.Join against absRoot() (filepath.Abs of "/" /
"\\") so the same table covers POSIX (`/...`) and Windows (`C:\...`)
without per-OS branches. Keep the traversal-after-Clean case POSIX-only
since filepath.Clean is the shared primitive and that input form needs a
volume to be considered absolute on Windows anyway.

* fix(agentctl): serialize RescanRepositories + re-check trackers under write lock

Two concurrent RescanRepositories calls could both observe an empty
tracker set (between the snapshot read and the bootstrap branch) and both
enter transitionToMultiRepoMode, leaking a duplicate bare-root tracker
and duplicate per-repo trackers. Same race in appendNewRepoTrackers would
double-append the same new child across both callers.

Fix:
- Add rescanMu sync.Mutex on Manager. Acquired for the full body of
  RescanRepositories so the snapshot → branch → mutate sequence is
  serialized. repoTrackersMu still guards individual field reads from
  unrelated goroutines (subscribe/unsubscribe/poll-mode).
- appendNewRepoTrackers re-checks membership inside the write-lock
  before appending and drops already-present trackers (Stop + detach
  outside the lock). Defense in depth.
- Test: add `dotted segment is legal as promotion` case so the
  ..hidden-segment promotion path stays exercised.

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 19:36:20 +01:00
Carlos Florêncio 0f87f7a56b fix(acp): enrich tool call metadata per agent with structured ACP fields (#1212) 2026-06-01 19:12:32 +01:00
Carlos Florêncio 13e6e2d89b fix(acp): serialize wakeup prompts to prevent turn misalignment (#1202) 2026-05-31 21:12:36 +01:00
Carlos Florêncio d1c73d2f2c feat: surface subagent task tool calls as cards in task chat (#1132) 2026-05-31 17:53:21 +01:00
Carlos Florêncio 700ff4199c fix(agentctl): write ACP debug logs under KANDEV_HOME_DIR (#1196) 2026-05-31 17:10:08 +01:00
Carlos Florêncio af3b26b6d9 fix: track OS PIDs and clean up shells on task archive/delete (#1191) 2026-05-31 14:38:52 +01:00
Carlos Florêncio a10e81c0d7 fix: preserve whitespace in acp message chunks (#1190) 2026-05-31 12:31:56 +01:00
Carlos Florêncio 4da076c43e feat(backend): per-session ACP debug logs with rotation and retention (#1172) 2026-05-30 19:10:09 +01:00
João Salavisa cab7b28ed9 chore: round 4 dead-code cleanup (transport helpers, detect, orphan methods) (#1156)
* chore(agentctl): delete duplicate procattr files in adapter package

setProcGroup and killProcessGroup are defined in both
agentctl/server/adapter/ and agentctl/server/process/. The process/
copies are the live ones (called by process/runner.go, manager.go,
vscode.go); the adapter/ copies have zero callers and are removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(agentctl): remove orphan helpers from transport/shared

Left over from the ACP refactor. All had zero callers:
- DebugEnabled(): internal debugMode var is checked directly by
  LogRawEvent/LogNormalizedEvent — the exported wrapper was unused.
- GetBool, GetMap, GetSlice: only GetString/GetInt are called in acp/normalize.go.
- NormalizeShellResult, NormalizeReadResult, NormalizeCodeSearchResult,
  NormalizeModifyResult: normalization moved to acp/normalize.go.
- TruncateIfNeeded, MaxContentLength, MaxFileCount: only used by the
  four Normalize* helpers above; removed transitively.
- TraceControlMessage: zero callers in the adapter transport layer.

GenerateUnifiedDiff, SplitLines, DetectLanguage, Tracer, ShutdownTracing,
TraceProtocolEvent, TraceProtocolRequest are alive and kept.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(agentctl): remove unused tool_payload constructors

NewHttpRequest, NewCreateTask, NewSubagentTask, NewShowPlan,
NewManageTodos, and NewMisc have zero callers — ACP normalization
builds NormalizedPayload via struct literals and JSON unmarshaling,
not these factory functions. NewReadFile, NewModifyFile, NewShellExec,
NewCodeSearch, and NewGeneric are kept (live callers in acp/normalize.go).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(agents/detect): remove dead fluent options and OSPaths type

WithFileExists, WithCommandOutput, WithEnvVar, expandHomePath (their
transitive helper), and OSPaths (struct + Resolve + Expanded) have
zero callers. Only WithCommand and WithNpxRunnable are used by agent
IsInstalled implementations. Imports trimmed to context and os/exec.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(office): remove dead channel methods superseded by service/

ListComments, DeleteComment, and UpdateChannelStatus on ChannelService
have zero callers — the live implementations live in
internal/office/service/comments.go and service/channels.go.
CreateComment is kept: it is called by HandleChannelInbound.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove scattered orphan helpers

Remove dead single-symbol exports verified to have zero callers:
- ParseCronExpression (office/shared/cron.go): NextCronTime covers
  all live usages; the validator wrapper was unused.
- IsValidPathComponent, MustJSON (office/shared/helpers.go): zero
  callers; GenerateSlug is kept.
- DiscoverUserSkillsAt (office/skills/userhome_discovery.go): exported
  wrapper over the private discoverUserSkillsAt; only the internal
  DiscoverUserSkills method is wired up.
- Manager.Get (agent/loginpty/manager.go): zero callers; GetByID and
  Stop are kept (Stop is tested in manager_test.go).
- DetachedWithValues (common/appctx/appctx.go): zero callers; Detached
  is kept.
- Repository.Close no-ops (task/share, terminal/repository): both are
  documented no-ops with zero callers.
- TaskBranchName (worktree/config.go): zero callers; the deterministic
  TaskBranchNameWithSuffix is kept.

Note: dialect.Now was initially targeted but is tested in
dialect_test.go (same-package call — missed by package-qualified grep).
It remains unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(share): remove stale Close reference from provider.go comment

Repository.Close() was removed as a no-op in the round-4 cleanup.
The comment in provider.go still referenced it by name; updated to
reflect that the repo simply doesn't own its DB connections.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(office/shared): delete helpers.go — GenerateSlug had zero callers

The triage step mistakenly identified skills/service.go as a caller,
but that package defines its own local GenerateSlug (different regex,
adds TrimSpace and a 'skill' fallback default). shared.GenerateSlug
was never called anywhere. helpers.go is now empty; delete the file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(worktree): fix stale TaskBranchName reference in TaskBranchNameWithSuffix doc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:18:10 +01:00
José Almeida b109c84e4e fix(backend): pass HTTP MCP servers to ACP agents via AssumeMcpHttp (#1152)
* fix(backend): pass HTTP MCP servers to ACP agents via AssumeMcpHttp

* test(backend): cover AssumeMcpHttp override path with effectiveMcpCapabilities helper
2026-05-29 12:58:51 +01:00
João Salavisa de92557f46 test(backend): wire goleak goroutine-leak detection across 8 packages (#1139)
* chore(backend): add go.uber.org/goleak test dependency

Promotes the previously-indirect goleak v1.3.0 to a direct require so
per-package TestMain calls can use it for goroutine leak detection.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(backend/lifecycle): drain workspace-stream backoff on Manager.Stop

StreamManager.connectWorkspaceStream slept the full retry backoff in
time.Sleep between failed attempts. SessionTraceContext is deliberately
uncancellable (it carries a long-lived span), so if the Manager began
shutting down mid-backoff the goroutine outlived Stop() and stranded
until the timer fired.

Thread Manager.stopCh through the StreamManager constructor and add a
sleepOrStop helper that selects between the timer and the shutdown
signal. ReconnectAll's startup sleep gets the same treatment.

TestConnectWorkspaceStream_BackoffDrainsOnStop closes stopCh while a
failing-port retry is in flight and asserts the goroutine exits within
500ms — fails without the fix, passes with it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(backend/github): close sqlite DB in newTestStore to drain connectionOpener

newTestStore opened *sqlx.DB via db.OpenSQLite but never registered a
Close on cleanup, leaving database/sql.(*DB).connectionOpener goroutines
parked across test boundaries. Add t.Cleanup(sqlxDB.Close); goleak's
per-package TestMain (added in a follow-up commit) is the regression
check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(backend): wire goleak.VerifyTestMain across leak-prone packages

Per-package TestMain in:
- internal/integrations/healthpoll (auth-health ticker loop)
- internal/jira, internal/linear (auth + issue-watch loops)
- internal/github (PR / review / issue poller loops)
- internal/gateway/websocket (per-conn pumps + terminal bridge)
- internal/orchestrator (detached event-handler goroutines)
- internal/agent/runtime/lifecycle (remote-status loop, StreamManager
  helpers; ignores OTel batch-span processor and OTLP retry helper —
  initialised early in the process and only exit on shutdown)
- internal/agentctl/server/process (extends the existing fixture-binary
  TestMain so subprocess + PTY pump leaks surface there)

Catches regressions where a Start/Stop forgets to drain on WaitGroup,
or a detached goroutine forgets to honour ctx/stopCh cancellation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(backend): document goroutine ownership and leak-testing convention

New "Goroutine ownership and leak testing" subsection under Conventions
in apps/backend/AGENTS.md (CLAUDE.md symlinks to it). Covers:

- Lifecycle: Start/Stop + WaitGroup, both idempotent.
- Cancellation: every long wait selects on ctx.Done() or stopCh; never
  time.Sleep in retry loops (reference lifecycle.StreamManager.sleepOrStop).
- Detached helpers: must respect the owning type's shutdown signal.
- Leak testing: new packages spawning goroutines add goleak.VerifyTestMain;
  IgnoreTopFunction is reserved for genuine third-party background
  goroutines and must carry a comment explaining the suppression.

Lists the packages currently instrumented so future contributors can
mirror the pattern.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(backend/lifecycle): drop dead OTel IgnoreTopFunction from goleak setup

Review follow-up. agentctl/tracing.initTracing only spawns the OTel SDK
provider when OTEL_EXPORTER_OTLP_ENDPOINT is set; in tests it isn't, so
the noop provider is used and no batchSpanProcessor / OTLP retry
goroutines ever exist. The IgnoreTopFunction calls were dead
suppressions and the comment ("called by NewManager via the agent
execution trace path") misdescribed when the SDK init runs. Drop the
ignores and correct AGENTS.md to stop citing OTel as the canonical
suppression example.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(backend/lifecycle): drop unneeded 50ms sleep from backoff-drain test

QA follow-up. The 50ms time.Sleep before close(stopCh) in
TestConnectWorkspaceStream_BackoffDrainsOnStop was meant to ensure the
goroutine had entered the backoff sleep before shutdown — but that
ordering doesn't affect the test outcome:

  - With the fix: sleepOrStop sees stopCh already closed and returns
    false on the first call, so the goroutine drains within one attempt
    regardless of when stopCh closes.
  - Without the fix: time.Sleep ignores stopCh, the goroutine burns
    its full 5-retry backoff (~31s), and the 500ms timeout fires.

Removing the sleep makes the test ~12x faster (15ms vs 200ms total) and
removes the only time.Sleep-based wait from the new tests, aligning
with the codebase's "channel-based sync, no sleep waits" convention.
Verified by 50x -race and 5x revert-and-fail.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(backend/github): drain HTTP idle conns before goleak inspects

CI surfaced a leaked goroutine on the Linux runner that local runs
missed: a single net/http.(*http2ClientConn).readLoop parked on a
TLS read, created by net/http.(*http2Transport).newClientConn. The
package's anonymousHTTPClient and PATClient's rewriteTransport both
route through http.DefaultTransport's connection pool, so an idle
HTTP/2 connection in that pool keeps the reader goroutine alive past
m.Run() — invisible on sandboxed local runs where outbound network
is blocked.

Replace github/goleak_test.go's plain VerifyTestMain with an
expanded TestMain that drains http.DefaultTransport's idle
connections before goleak.Find runs. This is the stdlib's
documented teardown for the connection pool, keeps the assertion
strict (no IgnoreTopFunction), and doesn't leak into production
code. Other in-scope packages don't need this — they don't use
http.DefaultTransport for HTTPS calls.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(backend): drain ws.Done on stopCh; always run goleak in github tests

- lifecycle/streams.go: replace bare <-ws.Done() with a select that also
  watches sm.stopCh so a connected goroutine drains when Manager.Stop()
  fires before the remote WS closes. A nil stopCh (isolated tests) blocks
  the nil channel case forever, degrading to plain <-ws.Done() semantics.
- github/goleak_test.go: remove the exitCode==0 guard around goleak.Find
  so leaks are always surfaced even when a test has already failed; original
  exit code is preserved (leaks only promote it to 1).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(backend/lifecycle): close ws on stopCh so agentctl WS loops drain

cubic flagged that the previous shutdown path observed stopCh but never
called ws.Close(), so the underlying WebSocket and its agentctl-side
read/write goroutines (workspace_stream.go:67-68) kept running past
Manager.Stop(). ClearWorkspaceStream only removes the WS from the
execution's tracking — it doesn't close the connection.

Call ws.Close() on the stopCh path. ws.Close is idempotent via
closeOnce, so it's safe even if the remote end already closed.

TestConnectWorkspaceStream_ClosesWSOnStop connects to a mock workspace
server that stays open until the client closes its side; after stopCh
fires, the test asserts ws.Done() closes within 500ms — which is only
true if connectWorkspaceStream invoked ws.Close(). Verified to fail
without the fix and pass with it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(backend/lifecycle): use channel sync instead of sleep polling in ws-close test

cubic flagged that TestConnectWorkspaceStream_ClosesWSOnStop polled
execution.GetWorkspaceStream() in a 500ms sleep loop, which is brittle
under CI load. connectWorkspaceStream already accepts a ready
channel that closes after SetWorkspaceStream — pass it in and wait on
the channel instead.

synctest isn't viable here (real network I/O), so channel-based sync is
the testing convention per AGENTS.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 10:23:18 +01:00
Carlos Florêncio b38dc21fb0 fix: stop changes panel from showing stale or flickering content (#1128) 2026-05-28 23:35:32 +01:00
João Salavisa 7b75d4c39d refactor(acp): split adapter.go into per-concern files (#1054)
* refactor(acp): split adapter.go into per-concern files

Pure file split of the 2,083-line transport/acp/adapter.go into sibling
files inside the same package — no behavior change, no new types, no
signature changes. The struct, constructor, lifecycle, and locking-
sensitive helpers (sendUpdate, trace ctx, Close) stay co-located in
adapter.go (422 lines); session/mcp/auth, prompt/cancel/wakeup, update
dispatch, tool-call conversion, permission flow, and cross-file helpers
each move to their own file, all under the 800-line lint cap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(acp): restore struct doc wording on availableAuthMethods

independent-review fix: the file split should have been byte-identical for
comments. Restore "surface a picker" (matches origin/main) — accidentally
rewritten as "drive a picker" during the adapter.go rewrite in ccfad56d.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci(acp): extract toolStatusInProgress / toolStatusCancelled constants

CI golangci-lint flagged 'in_progress' (9 occurrences) and 'cancelled'
(4 occurrences) as goconst violations after the file split surfaced them
as "new" lines. Extend the existing toolStatus* constant set in
normalize.go and use the new constants at every production call site
(adapter_tools.go, adapter_prompt.go, monitor.go). Behavior unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 23:22:21 +01:00
Carlos Florêncio 21db57bdfd fix(orchestrator): repair worktree state and prompt persistence on resume (#1121) 2026-05-28 21:48:12 +01:00
Carlos Florêncio a068c88b99 fix: show human-readable title and details in permission prompts (#1101) 2026-05-28 16:24:26 +01:00
João Salavisa 991a6315c3 fix(backend): force fresh git status on session subscribe (#1092)
* fix(backend): force fresh git status on session subscribe

Every new WS subscriber to a session now triggers a fresh `git status
--porcelain` in agentctl instead of replaying the cached snapshot.
This makes the cache an optimization for already-subscribed observers
only — a fresh observer revalidates ground truth on every connect, so
a stale cache (e.g. left over when the poll loop missed a HEAD change
in paused mode or dropped a tick) self-heals on the next subscribe.

- `?fresh=true` on `/api/v1/git/status/multi` propagates through to
  `WorkspaceTracker.GetGitStatus(ctx, true)`.
- New `GetGitStatusMultiFresh` client method; `tryGetLiveGitStatus`
  (the session-subscribe hydration path) uses it.
- Regression test reproduces the bug: dirty file → cache primed →
  commit without refresh → cached read still shows the file as
  modified; fresh=true returns the clean truth.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: address PR review feedback

- Reframe regression-test precondition comment so it reads as scenario
  setup (Claude review).
- Document GetGitStatusMultiFresh's read-only-vs-cache contract in its
  godoc so a future reader doesn't try to write the result back into
  the tracker's currentStatus (Claude review).
- Trim multi-line block comment in helpers.go to a single line per the
  backend AGENTS.md convention (Claude review).
- Parallelize handleGitStatusMulti's per-repo fan-out via sync.WaitGroup
  to keep the fresh-path wall-clock close to the slowest single repo
  regardless of repo count, so multi-repo workspaces don't blow past
  the 2s subscribe-path timeout (Greptile review). Pre-sized result
  slice means each goroutine writes its own slot — no shared-slice
  contention. collectStatusForRepo now takes context.Context directly
  instead of *gin.Context.
- Add a third assertion in the regression test that locks in the
  documented cache-unchanged contract — after fresh=true, a subsequent
  GetGitStatus(ctx, false) call must still return the stale cached
  value, so any future change that silently writes through fresh would
  fail the test (Greptile review).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: address round 2 review feedback

- Trim the parallelization rationale in handleGitStatusMulti to a
  single line per AGENTS.md convention (Claude review).
- Drop unused GetGitStatusMulti client method (cached variant) and its
  internal getGitStatusMulti helper — only GetGitStatusMultiFresh has
  a caller after this PR, and AGENTS.md says to delete unused code
  outright (Claude review).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:44:44 +01:00
Carlos Florêncio f1dbad1dbd feat(cli-mode): auto-inject task prompt + dynamic submit sequence + passthrough UX fixes (#923) 2026-05-25 19:14:36 +01:00