Skip to content

Merge upstream Komodo v2.1.1 into agentic CLI#8

Merged
FarisZR merged 4 commits into
agentic-clifrom
feature/merge-upstream-v2.1.1-agentic
Apr 9, 2026
Merged

Merge upstream Komodo v2.1.1 into agentic CLI#8
FarisZR merged 4 commits into
agentic-clifrom
feature/merge-upstream-v2.1.1-agentic

Conversation

@FarisZR
Copy link
Copy Markdown
Owner

@FarisZR FarisZR commented Apr 9, 2026

Summary

  • merge upstream Komodo changes through v2.1.1, including the required workspace, client, core, periphery, and CLI updates
  • preserve the agentic CLI customizations, including agent-safe JSON output and the custom ssh / exec command behavior with remote exit-code passthrough
  • update CI to run for agentic-cli PRs while keeping cargo cache disabled to avoid disk pressure during larger workspace builds

Validation

  • cargo fmt --all
  • cargo build --workspace --all-targets
  • cargo test --workspace
  • cargo run -p komodo_cli -- --help
  • cargo run -p komodo_cli -- exec --help
  • cargo run -p komodo_cli -- ssh --help
  • cargo run -p komodo_cli -- stack --help
  • cargo run -p komodo_cli -- sync --help
  • cargo run -p komodo_cli -- procedure --help
  • cargo run -p komodo_cli -- variable --help
  • cargo run -p komodo_cli -- config

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Swarm node update functionality with role, availability, and label management.
    • Added environment file support for Docker stack operations.
    • Added configurable logging timestamps option.
  • Bug Fixes

    • Fixed toggle button behavior in stack/sync/build editors.
    • Improved error handling and display for Swarm and Server operations.
    • Fixed deployment update detection for Swarm stacks.
  • Improvements

    • Enhanced error representation with detailed trace information.
    • Added searchable organization selector and improved label display.
    • Updated development tooling versions (Rust, Node.js, TypeScript).

mbecker20 and others added 4 commits April 1, 2026 16:08
* fix swarm config content hidden

* 2.0.1-dev-1

* bump ts deps

* align swarm config links

* StackServiceRun command is actually optional

* fix swarm not included in read resource toml

* deploy 2.0.1-dev-2

* logger support disabling timestamps (eg if they are already provided by docker logs)

* full opacity tag color selector

* Add UpdateSwarmNode execution and convert several large repetive blocks to macro handling

* add node update functionality

* deploy 2.0.1-dev-3

* deploy 2.1.0-dev-1

* toml variable export sorted by name

* deploy 2.1.0-dev-2

* dont use detach=false because it can hang indefinitely on misconfigured stacks

* deploy 2.1.0-dev-3

* swarm stack deploy explicitly '--detach=true' to avoid future changes in behavior

* fmt

* UI: Stack config: Fix Add Env File button

* fix swarm error propogation, add server connection error hover message

* deploy 2.1.0-dev-4

* fix service rm (should be stack rm

* fix container ports not displaying when server address not available for link

* fix build registry custom org configuration

* github action: comment out cache step (leads to no space on device)

* fix missing nullish check when selecting stack (moghtech#1287)

* fix container selector null crash

* provider selector custom input label adheres to showLabel

* fix: show/hide button non-functional in stack, sync, and build info views (moghtech#1267)

Co-authored-by: twalts <t.mwalton@yahoo.com>

* swarm stack support env vars through shell source file method

* tweaks

* bump deps

* deploy 2.1.0-dev-5

* auto update should work with swarm

* deploy 2.1.0-dev-6

* 2.1.0

---------

Co-authored-by: Shen Li <dustet@gmail.com>
Co-authored-by: T <github@mail.taylor.media>
Co-authored-by: twalts <t.mwalton@yahoo.com>
* start 2.1.1 to fix swarm auto updates

* correctly extract image and digest from swarm stacks / deployments

* 2.1.1 fix swarm auto updates not picking up

* fmt
Bring the fork forward to Komodo v2.1.1 while preserving the agent-safe CLI behavior and custom ssh/exec workflow.
Copilot AI review requested due to automatic review settings April 9, 2026 22:02
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

This PR implements a new swarm node update feature (UpdateSwarmNode), refactors large match blocks into macros for improved maintainability, introduces structured error types (Serror) throughout error fields, adds logging timestamp configuration via environment variables, enhances environment variable handling for stack deployment, and updates Rust toolchain and dependencies across all components.

Changes

Cohort / File(s) Summary
Version & Build Updates
Cargo.toml, client/core/ts/package.json, ui/package.json, bin/.../aio.Dockerfile, bin/binaries.Dockerfile, bin/chef.binaries.Dockerfile, example/.../Dockerfile
Bumped workspace version from 2.0.0 to 2.1.1, updated several Rust dependencies (mogh_config, mogh_logger, toml, uuid, aws-sdk-ec2), bumped Docker base images from rust:1.94.0 to rust:1.94.1 across multiple Dockerfiles, updated TypeScript to ^6.0.2, and updated UI dependencies (lucide-react, @vitejs/plugin-react, vite).
UpdateSwarmNode Feature - Core
bin/core/src/api/execute/mod.rs, bin/core/src/api/execute/swarm.rs, bin/periphery/src/api/swarm/mod.rs
Added new UpdateSwarmNode variant to ExecuteRequest enum, implemented Resolve<ExecuteArgs> for UpdateSwarmNode to handle swarm node updates with permission checks, update tracking, and cache refresh; updated periphery client to construct Docker swarm node update commands with label management.
UpdateSwarmNode Feature - Client
client/core/rs/src/api/execute/mod.rs, client/core/rs/src/api/execute/openapi.rs, client/core/rs/src/api/execute/swarm.rs, client/core/rs/src/entities/mod.rs, client/periphery/rs/src/api/swarm.rs
Added UpdateSwarmNode execution variant, OpenAPI endpoint registration, and CLI/request struct with swarm/node identifiers plus optional availability, role, and label management fields; registered new Operation::UpdateSwarmNode and updated periphery client struct to use Vec<String> for label handling.
UpdateSwarmNode Feature - UI
client/core/ts/src/responses.ts, client/core/ts/src/types.ts, ui/public/client/responses.d.ts, ui/public/client/types.d.ts, ui/public/client/types.js, ui/src/resources/swarm/docker/nodes/index.tsx, ui/src/resources/swarm/docker/nodes/update.tsx
Extended ExecuteResponses and type definitions to include UpdateSwarmNode operation, added new TypeScript interface and UI component for updating swarm node properties with role, availability, and label management via modal-based form.
Error Type Refactoring (Serror)
bin/core/src/state.rs, client/core/rs/src/entities/alert.rs, client/core/rs/src/entities/server.rs, client/core/rs/src/entities/swarm.rs, client/core/ts/src/types.ts, ui/public/client/types.d.ts, ui/src/ui/hover-error.tsx
Changed error fields from Option<String> to Option<Serror> in CachedSwarmStatus, AlertData::SwarmUnhealthy, ServerListItemInfo, and SwarmListItemInfo; added new HoverError component for rendering structured errors with stack traces; updated error imports and type definitions across client and UI.
Logging & Configuration
bin/cli/src/config.rs, bin/core/src/config.rs, bin/periphery/src/config.rs, client/core/rs/src/entities/logger.rs, client/core/rs/src/entities/config/cli/mod.rs, client/core/rs/src/entities/config/core.rs, client/core/rs/src/entities/config/periphery.rs
Added timestamps field to LogConfig (defaults to true), configured environment variable overrides (komodo_cli_logging_timestamps, komodo_logging_timestamps, periphery_logging_timestamps) with fallback to config values, updated logging configuration across CLI, core, and periphery services.
Core Refactoring - Macros
bin/cli/src/command/execute.rs, bin/core/src/helpers/procedure.rs, bin/core/src/helpers/update.rs, bin/core/src/resource/procedure.rs
Replaced large match blocks with macro-driven code generation (handle_execution!, resolve_execute!, batch_not_implemented!, init_execution_match!, check_execution_perms!) to centralize variant handling, reduce repetition, and improve maintainability; retained special-case logic for self-referential and write-only operations.
Procedure Stage ID Replacement
bin/core/src/helpers/procedure.rs, bin/core/src/sync/resources.rs, bin/core/src/sync/toml.rs
Added new public helper function replace_procedure_stage_ids_with_names that converts procedure stage execution identifier fields to resource names and maps alerter IDs to names; refactored sync logic to delegate ID-to-name replacement to the new helper instead of inline macro-based pattern matching.
Alert System & Error Formatting
bin/core/src/alert/discord.rs, bin/core/src/alert/mod.rs, bin/core/src/alert/slack.rs
Swapped match arms for ServerVersionMismatch and ServerUnreachable variants in alert formatting, updated error rendering to use debug format ({e:#?}) instead of display format for SwarmUnhealthy and ServerUnreachable paths, adjusted message content and severity level handling.
Stack Deployment & Environment
bin/periphery/src/api/swarm/stack.rs, bin/core/src/api/write/stack.rs, lib/environment/src/lib.rs
Added support for additional environment files in stack deployment via env_file_args helper, changed docker stack deploy to use --detach=true, updated auto-update logic to force empty deploy_services for Swarm stacks, ensured generated environment files end with newline.
Maintenance & Cache Updates
bin/core/src/api/execute/maintenance.rs, bin/core/src/monitor/resources.rs, bin/core/src/monitor/swarm.rs, bin/core/src/connection/server.rs, bin/core/src/api/read/toml.rs
Added swarm/server status reachability checks before auto-update, refactored error handling flow, removed format_serror usage in swarm monitoring, added image digest parsing/normalization in cache updates, implemented MongoDB sort on variables export, added external_address preservation in server fix, populated server error in list item info.
UI Components & Enhancements
ui/src/components/docker/container-ports.tsx, ui/src/components/docker/container-selector.tsx, ui/src/components/docker/containers-section.tsx, ui/src/components/stack-service-selector.tsx, ui/src/pages/swarm/node/index.tsx, ui/src/ui/labels-group.tsx, ui/src/resources/server/index.tsx, ui/src/resources/swarm/index.tsx
Added optional chaining for safer optional access in container/service selectors, updated swarm node page to render UpdateSwarmNode component, added new LabelsGroup component for rendering label badges, introduced HoverError component for error display in server/swarm views, refactored error UI rendering from inline markup to reusable components.
UI Configuration & Selectors
ui/src/components/config/organization-selector.tsx, ui/src/components/config/provider-selector.tsx, ui/src/pages/settings/tags/color-selector.tsx, ui/src/resources/stack/config/index.tsx, ui/src/resources/swarm/config.tsx
Added explicit label to organization selector, conditionally hides provider domain label, updated color tag selector background token format to .9 variant, adjusted stack environment group visibility and description for Swarm vs Compose, moved swarm links config labels to list component level.
Stack & Deployment Views
ui/src/resources/stack/info.tsx, ui/src/resources/stack/update-available.tsx, ui/src/resources/deployment/update-available.tsx, ui/src/resources/build/config.tsx, ui/src/resources/build/info.tsx
Unified ShowHideButton toggle logic across stack and sync info components, removed swarm_id check from update-available display conditions, changed registry key from composite value-based to index-based for React reconciliation, fixed ShowHideButton toggle in build editor, adjusted deployment/stack update-available UI logic.
Procedure Execution Types
ui/src/resources/procedure/config/executions.tsx
Added "UpdateSwarmNode" to excluded execution types in ProcedureMinExecutionType, updated RunStackService placeholder text from "Required" to "Optional".
TypeScript & Configuration
client/core/ts/tsconfig.json, ui/tsconfig.json, lib/command/src/lib.rs
Updated TypeScript moduleResolution to bundler and added rootDir, removed baseUrl from UI config, refactored shell helper to return resolved executable paths instead of command names.
File Sync & Extensions
bin/core/src/sync/file.rs
Introduced macro_rules! extend_filtered to replace repetitive resource field extension logic with macro-driven iteration, improving maintainability and making it easier to add new resource types.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: merging upstream Komodo v2.1.1 into the agentic CLI branch.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/merge-upstream-v2.1.1-agentic
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feature/merge-upstream-v2.1.1-agentic

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Merges upstream Komodo changes through v2.1.1 into the agentic CLI fork, updating the Rust workspace (core/periphery/CLI/client) and the UI to match upstream while retaining agentic CLI custom behaviors.

Changes:

  • Adds UpdateSwarmNode support end-to-end (API types, core/periphery execution plumbing, UI controls for selecting/updating nodes).
  • Switches server/swarm error payloads to a structured error type (message + trace) and introduces reusable UI components for displaying errors/labels.
  • Updates logging configuration to include timestamps, refactors core sync/procedure helpers, and adjusts CI workflow triggers/caching.

Reviewed changes

Copilot reviewed 50 out of 86 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
ui/tsconfig.json Updates TS compiler options / path mapping config.
ui/src/ui/section.tsx Section header layout change to support titleRight rendering.
ui/src/ui/labels-group.tsx New UI component for rendering key/value labels as badges.
ui/src/ui/hover-error.tsx New UI component for structured error + trace hover display.
ui/src/resources/sync/info.tsx Fixes Show/Hide toggle wiring.
ui/src/resources/swarm/index.tsx Refactors swarm error UI to reuse HoverError.
ui/src/resources/swarm/docker/nodes/update.tsx New modal/UI to update swarm node role/availability/labels.
ui/src/resources/swarm/docker/nodes/index.tsx Adds node selection + update action + labels column.
ui/src/resources/swarm/config.tsx Tweaks config UI labeling for links.
ui/src/resources/stack/update-available.tsx Adjusts update-available visibility conditions.
ui/src/resources/stack/info.tsx Fixes Show/Hide toggle wiring.
ui/src/resources/stack/config/index.tsx Updates wording/layout for stack config UI.
ui/src/resources/server/index.tsx Adds structured server error display via HoverError.
ui/src/resources/procedure/config/executions.tsx Adds UpdateSwarmNode execution type + placeholder wording tweak.
ui/src/resources/deployment/update-available.tsx Adjusts update-available visibility conditions.
ui/src/resources/build/info.tsx Fixes Show/Hide toggle wiring.
ui/src/resources/build/config.tsx Registry config list keying change.
ui/src/pages/swarm/node/index.tsx Adds UpdateSwarmNodes action to node subpage.
ui/src/pages/settings/tags/color-selector.tsx Adjusts tag color token usage.
ui/src/components/stack-service-selector.tsx Fixes optional chaining access.
ui/src/components/docker/containers-section.tsx Removes explicit wrap prop usage for ContainerPorts.
ui/src/components/docker/container-selector.tsx Fixes optional chaining access.
ui/src/components/docker/container-ports.tsx Refactors ports rendering + hovercard/link behavior.
ui/src/components/config/provider-selector.tsx Makes “Domain” label conditional on showLabel.
ui/src/components/config/organization-selector.tsx Adds label in custom mode + makes selector searchable.
ui/public/client/types.js Adds UpdateSwarmNode operation to generated client JS.
ui/public/client/types.d.ts Adds UpdateSwarmNode types + structured error type updates.
ui/public/client/responses.d.ts Adds UpdateSwarmNode execute response typing.
ui/package.json Updates UI toolchain/deps and pins packageManager.
lib/environment/src/lib.rs Ensures written env files end with a newline.
lib/command/src/lib.rs Changes default shell selection to explicit absolute paths.
example/update_logger/Dockerfile Bumps Rust image version.
example/alerter/Dockerfile Bumps Rust image version.
client/periphery/rs/src/api/swarm.rs Updates UpdateSwarmNode API shape (label_add as Vec).
client/core/ts/yarn.lock Updates TypeScript version in lockfile.
client/core/ts/tsconfig.json Switches to bundler resolution + sets rootDir.
client/core/ts/src/types.ts Updates core TS types for UpdateSwarmNode + structured errors.
client/core/ts/src/responses.ts Adds UpdateSwarmNode to execute responses.
client/core/ts/package.json Bumps komodo_client TS package version to 2.1.1.
client/core/rs/src/entities/swarm.rs Switches swarm list error field to structured error type.
client/core/rs/src/entities/server.rs Adds structured error field to server list info.
client/core/rs/src/entities/mod.rs Adds UpdateSwarmNode operation enum variant.
client/core/rs/src/entities/logger.rs Adds timestamps to logger config with defaults and trait impl.
client/core/rs/src/entities/docker/node.rs Adds hostname/labels fields + clap ValueEnum derives.
client/core/rs/src/entities/config/periphery.rs Adds env override for periphery logging timestamps.
client/core/rs/src/entities/config/core.rs Adds env override for core logging timestamps + reorders env fields.
client/core/rs/src/entities/config/cli/mod.rs Adds env override for CLI logging timestamps.
client/core/rs/src/entities/alert.rs Switches alert swarm error payload to structured error type.
client/core/rs/src/api/execute/swarm.rs Adds UpdateSwarmNode execute request + OpenAPI metadata.
client/core/rs/src/api/execute/openapi.rs Registers UpdateSwarmNode path in OpenAPI.
client/core/rs/src/api/execute/mod.rs Adds UpdateSwarmNode to Execution enum.
Cargo.toml Bumps workspace/crate dependency versions to v2.1.1 line.
Cargo.lock Lockfile updates for workspace version bumps and deps.
bin/periphery/src/docker/node.rs Includes hostname/labels in node list items.
bin/periphery/src/config.rs Adds timestamps logging override wiring.
bin/periphery/src/api/swarm/stack.rs Adds env-file sourcing for stack config/deploy + adjusts deploy args.
bin/periphery/src/api/swarm/mod.rs Updates label-add argument formatting for node update.
bin/periphery/aio.Dockerfile Bumps Rust image version.
bin/core/src/sync/toml.rs Refactors procedure ID->name replacement helper usage.
bin/core/src/sync/resources.rs Refactors procedure sync logic via helper.
bin/core/src/sync/file.rs Macro refactor for extending resources by tag.
bin/core/src/state.rs Switches swarm cache error type to structured error.
bin/core/src/resource/server.rs Exposes structured server error in list info.
bin/core/src/resource/procedure.rs Refactors execution permission checks via macros + adds UpdateSwarmNode.
bin/core/src/monitor/swarm.rs Switches swarm monitor error storage to structured error type.
bin/core/src/monitor/resources.rs Improves image digest extraction and formatting logic.
bin/core/src/helpers/update.rs Refactors execute-request -> update init via macro + adds UpdateSwarmNode.
bin/core/src/helpers/procedure.rs Large refactor of execution dispatch + adds helper for ID->name replacement.
bin/core/src/connection/server.rs Adjusts onboarding “fix server” logic (address/external_address).
bin/core/src/config.rs Adds timestamps logging override wiring.
bin/core/src/api/write/stack.rs Prevents per-service deploy for swarm stacks.
bin/core/src/api/read/toml.rs Sorts variables by name when exporting to TOML.
bin/core/src/api/execute/swarm.rs Implements UpdateSwarmNode execute handler (core -> periphery).
bin/core/src/api/execute/mod.rs Adds UpdateSwarmNode execute request variant.
bin/core/src/api/execute/maintenance.rs Improves GlobalAutoUpdate checks for server/swarm reachability.
bin/core/src/alert/slack.rs Uses debug formatting for structured error details in alerts.
bin/core/src/alert/mod.rs Updates standard alert content for structured errors + reorder cases.
bin/core/src/alert/discord.rs Updates Discord alerts for structured errors + reorder cases.
bin/core/aio.Dockerfile Bumps Rust image version.
bin/cli/src/config.rs Adds timestamps logging override wiring.
bin/cli/src/command/execute.rs Refactors execution printing/execution via macros; adds UpdateSwarmNode.
bin/cli/aio.Dockerfile Bumps Rust image version.
bin/chef.binaries.Dockerfile Bumps cargo-chef Rust image version.
bin/binaries.Dockerfile Bumps Rust image version.
.github/workflows/ci.yml Runs CI on agentic-cli branch; disables cargo cache to reduce disk usage.
Comments suppressed due to low confidence (2)

ui/tsconfig.json:28

  • compilerOptions.paths is configured, but baseUrl was removed. TypeScript requires baseUrl to be set when using paths mappings; otherwise TS will error and @/... imports won’t resolve in type-checking/IDE. Restore baseUrl (typically ".") or rework the aliasing approach so TS and Vite stay in sync.
    ui/src/resources/swarm/docker/nodes/index.tsx:55
  • selectKey can produce non-unique IDs (e.g., multiple nodes with missing fields will all become "Unknown", and Name/Hostname aren’t guaranteed unique). Because DataTable uses this as getRowId, collisions will break row selection (selecting one row may select another). Prefer a guaranteed-unique key (ideally node.ID, with a safe fallback like ${Hostname ?? Name ?? "unknown"}-${index} if needed).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +35 to +37
if (Object.keys(portsMap).length > 0) {
console.log("mapped", portsMap);
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several console.log statements left in this component. This will spam the browser console for any container with ports and can leak potentially sensitive info in shared screenshots/logs. Please remove these debug logs (or gate them behind an explicit dev-only flag).

Copilot uses AI. Check for mistakes.
Comment on lines 62 to +67
if (!groupedPorts.length) {
return null;
}

console.log("grouped", groupedPorts);

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console.log("grouped", groupedPorts); is unconditional, so it will always log whenever this component renders with ports. Please remove this log before merging.

Copilot uses AI. Check for mistakes.
Comment on lines 25 to 31
export default function ContainerPorts({
ports,
serverId,
hoverCardProps,
gap = "xs",
wrap = "nowrap",
...groupProps
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrap is destructured from props but not actually respected: DividedChildren is hard-coded to wrap="nowrap". This makes the wrap prop (and any wrap passed via groupProps) ineffective. Use the wrap variable (defaulting to "nowrap") when rendering DividedChildren so callers can override wrapping behavior.

Copilot uses AI. Check for mistakes.
Comment on lines 123 to +130
<Group
renderRoot={(props) => <a target="_blank" href={link} {...props} />}
renderRoot={(props) =>
link ? (
<a target="_blank" href={link} {...props} />
) : (
<div {...props} />
)
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Links opened with target="_blank" should include rel="noopener noreferrer" to prevent reverse-tabnabbing. Add the rel attribute on both anchor renderRoots when link is present.

Copilot uses AI. Check for mistakes.
Comment on lines 243 to 246
{image_registries?.map((registry, index) => (
<ImageRegistryConfig
key={
(registry.domain ?? "") +
(registry.organization ?? "") +
(registry.account ?? "") +
index
}
key={index}
registry={registry}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the array index as a React key for ImageRegistryConfig can cause incorrect UI state when users insert/remove registries (React will reuse component instances for different items). Prefer a stable key derived from registry identity (e.g., an explicit id/uuid, or a concatenation of stable fields if guaranteed unique).

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +34
const [opened, { open, close }] = useDisclosure();
const { data: inspect, isPending } = useRead(
"InspectSwarmNode",
{ swarm, node: nodes[0] },
{ enabled: opened && nodes.length === 1 },
);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useRead("InspectSwarmNode", { swarm, node: nodes[0] }, ...) passes nodes[0] which is string | undefined. Even though the query is disabled unless nodes.length === 1, this still violates the request type and can cause TS errors / confusing cache keys. Consider using node: nodes[0] ?? "" (or another safe placeholder) while keeping the enabled guard.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +57
const { mutate: update, isPending: updatePending } = useExecute(
"UpdateSwarmNode",
{
onSuccess: () => {
close();
resetForm();
},
},
);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When updating multiple nodes, onSubmit calls mutate in a loop, but the mutation onSuccess closes the modal and resets the form on the first successful update. This can hide failures for later nodes and makes it hard to tell whether all updates completed. Consider aggregating the updates (e.g., track remaining count / use mutateAsync + Promise.allSettled) and only close/reset once all nodes have completed successfully (or present per-node results).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 25

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
client/core/rs/src/entities/alert.rs (1)

92-99: ⚠️ Potential issue | 🟠 Major

Backward-compatibility risk for persisted alerts (err shape change).

Line 98 changes SwarmUnhealthy.err to a structured type. Existing alert documents with legacy string err can fail deserialization unless you add compatibility handling (custom deserializer accepting both string/object) or run a data migration before rollout.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/core/rs/src/entities/alert.rs` around lines 92 - 99, SwarmUnhealthy's
err field was changed to a structured type (_Serror), which breaks
deserialization of persisted alerts that store err as a legacy string; update
the deserialization to accept both shapes by adding a compatibility serde
handler (e.g., implement Deserialize for _Serror that accepts either a string or
an object, or declare an untagged enum wrapper) for the _Serror type used by
SwarmUnhealthy.err, or add a migration step to convert legacy string errors into
the new structured form before rollout; locate the SwarmUnhealthy definition and
the _Serror type in alert.rs and apply the compatibility deserializer or
migration accordingly.
bin/core/src/connection/server.rs (1)

319-334: ⚠️ Potential issue | 🔴 Critical

Constructed config is not used; external_address change is dead code.

The PartialServerConfig built on lines 299–318 (including the new external_address logic) is only used for the is_none() check on line 319. The actual UpdateServer call on lines 320–333 constructs a new hardcoded config that ignores external_address:

UpdateServer {
  id: server.id,
  config: PartialServerConfig {
    enabled: Some(true),
    address: Some(String::default()),
    ..Default::default()  // <-- external_address is None here
  },
}

The fix should use the constructed config:

   if !config.is_none() {
     UpdateServer {
       id: server.id,
-      config: PartialServerConfig {
-        enabled: Some(true),
-        address: Some(String::default()),
-        ..Default::default()
-      },
+      config,
     }
     .resolve(&args)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/core/src/connection/server.rs` around lines 319 - 334, The constructed
`config` (the PartialServerConfig that may include external_address) is never
passed into the UpdateServer call; instead a new hardcoded PartialServerConfig
is created. Replace the inline PartialServerConfig in the UpdateServer { id:
server.id, config: ... } expression with the previously built `config` value
(ensuring it has enabled set to Some(true) and address populated as needed), so
UpdateServer uses the constructed config (and keep the
.resolve(&args).await.map_err(...) flow unchanged).
ui/src/resources/stack/config/index.tsx (1)

531-546: ⚠️ Potential issue | 🟡 Minor

Point the compose branch at the compose docs.

Lines 534-536 now describe docker compose up, but Line 545 still links the non-swarm path to docker/service/create. That sends users to the wrong option set when they follow “See docker docs.”

🔧 Suggested change
                   to={
                     currSwarmId
                       ? "https://docs.docker.com/reference/cli/docker/stack/deploy/#options"
-                      : "https://docs.docker.com/reference/cli/docker/service/create/#options"
+                      : "https://docs.docker.com/reference/cli/docker/compose/up/#options"
                   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/resources/stack/config/index.tsx` around lines 531 - 546, The
non-swarm branch of the Link in the description block still points to the
service/create docs; update the ternary that sets Link's to (where currSwarmId
is checked) so the false/compose branch points to the Docker Compose "up" docs
(e.g. the compose up reference URL) instead of "docker/service/create"; locate
the JSX using currSwarmId and the Link component inside the description prop and
replace only the non-swarm URL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/core/src/alert/mod.rs`:
- Around line 261-264: The code is formatting errors with Debug ("{e:#?}") when
building the alert body; change the mapping closure that produces err (the let
err = err.as_ref().map(|e| format!("\nerror: {e:#?}")).unwrap_or_default();) to
use Display/sanitized text instead (e.g. format!("\nerror: {}", e) or
e.to_string()) so user-facing channels receive clean, non-debug output; apply
the same replacement for the other occurrence that uses "{e:#?}" (the similar
map/format block around lines reported).

In `@bin/core/src/alert/slack.rs`:
- Around line 111-129: The header currently uses the full multiline `text`
(created in the `match` on `alert.level`) which places version details into a
Slack header; change this so `Block::header` receives a short one-line
`header_text` (e.g., "{level} | *{name}*{region}" with the ✅ or ⚠️ marker) and
build a separate `section_text` for the version details (e.g., "Periphery:
{server_version} | Core: {core_version}") that you pass to `Block::section`;
update the `blocks` vector to include `Block::header(header_text)` followed by
the `Block::section(resource_link(...))` and another
`Block::section(section_text)` only for the mismatch branch so Ok still shows a
short header without extra section.

In `@bin/core/src/api/execute/maintenance.rs`:
- Around line 271-299: The reachability check for SwarmOrServer is duplicated;
extract it into a helper function (e.g., is_reachable(swarm_or_server,
server_status_cache, swarm_status_cache) -> bool) that encapsulates the current
logic that checks server_status_cache for ServerState::Ok and swarm_status_cache
for SwarmState::Healthy | SwarmState::Unhealthy, then replace both match blocks
(the one around SwarmOrServer in execute/maintenance.rs and the similar block
used for stacks/deployments) to call that helper and continue when it returns
false; reference SwarmOrServer, server_status_cache, swarm_status_cache,
ServerState::Ok, and SwarmState::Healthy|Unhealthy when implementing the helper.

In `@bin/core/src/api/write/stack.rs`:
- Around line 942-945: The deploy_services condition currently checks
stack.config.swarm_id directly (alongside
stack.config.auto_update_all_services); replace that raw-config swarm detection
with the stack's resolved execution target instead (e.g., use the resolved
execution target accessor on the Stack such as stack.execution_target /
stack.resolved_target or the helper is_swarm() on the resolved target) so the
clause becomes: if stack.config.auto_update_all_services ||
resolved_target.is_swarm() (keeping the existing Swarm comment), ensuring deploy
logic uses the actual resolved target rather than stack.config.swarm_id to avoid
config-drift edge cases.

In `@bin/core/src/connection/server.rs`:
- Around line 310-316: The conditional is inverted: currently it sets
external_address to server.config.address only when
server.config.external_address is non-empty; change the logic so
external_address is set to Some(server.config.address) when
server.config.external_address IS empty (i.e., when no external_address has been
configured) and None otherwise. Locate the construction that assigns
external_address (reference server.config.external_address and the
external_address field in the server struct) and flip the condition to check for
emptiness before moving server.config.address into external_address.

In `@bin/core/src/helpers/procedure.rs`:
- Around line 665-783: The current replace_procedure_stage_ids_with_names
function (and its replace_id_with_name! macro) uses .unwrap_or_default() which
turns cache misses into empty strings; change those fallbacks to preserve the
original identifier by using the existing id/name from the config when a lookup
fails (e.g. assign config.$field = all.$collection.get(&config.$field).map(|r|
r.name.clone()).unwrap_or_else(|| config.$field.clone())); do the analogous
change inside the SendAlert mapping so each alerter maps to a.name.clone() or
falls back to the original alerter id string instead of "" so missing resources
remain unchanged.

In `@bin/core/src/monitor/resources.rs`:
- Around line 80-96: The tag-detection logic around the tuple assignment for
(image, image_digests) is incorrect because image.contains(':') treats registry
host:port as a tag; update the conditional that currently uses
image.contains(':') (in the closure producing the fallback image and
image_digests) to detect a tag only if a colon appears after the last slash
(i.e., a ':' index greater than the last '/' index) so registry ports are
ignored; keep ImageDigest::parse(...) behavior and ensure when no tag is found
you append ":latest" to the image string and set image_digests to None.

In `@bin/core/src/sync/file.rs`:
- Around line 253-266: The manual list passed to the extend_filtered! macro can
drift when ResourcesToml gains new tag-filtered fields; add a regression test
that constructs a ResourcesToml instance with every tag-filtered field populated
(seed each of servers, swarms, stacks, deployments, builds, repos, procedures,
actions, alerters, builders, resource_syncs), runs the same merge/extend logic
(exercise the extend_filtered! path), and asserts the merged result contains
entries for every seeded field to fail if any field is missing; implement this
as a unit test that programmatically enumerates the ResourcesToml fields you
expect and compares them to the items processed by the extend_filtered!
invocation so future additions to ResourcesToml will cause the test to fail
until the macro call list is updated.

In `@bin/periphery/src/api/swarm/mod.rs`:
- Around line 102-104: The loop that builds the shell command concatenates raw
label values from label_add and label_rm into the command string (see label_add
and label_rm usage), which breaks tokenization in run_komodo_standard_command;
fix by shell-escaping or quoting each value before concatenation (e.g., use
shlex::try_quote(value) or wrap the value in quotes and handle errors), so
replace direct concatenation of &key_value with the escaped/quoted variant for
both the label_add and label_rm loops to ensure each label stays a single
argument.

In `@bin/periphery/src/api/swarm/stack.rs`:
- Around line 367-397: The env_file_args function currently injects raw
AdditionalEnvFile.path values directly into shell source commands (write! calls
building res), allowing shell metacharacter injection when the resulting string
is passed to shell -c; fix by ensuring paths are sanitized and safely quoted
before concatenation — either validate/normalize AdditionalEnvFile.path upstream
to reject unsafe characters and enforce absolute/relative path patterns, or
escape single quotes and wrap each path in single quotes when building res (and
apply the same treatment to the env_file_path branch), and add unit tests for
env_file_args to assert that inputs with metacharacters do not produce
executable sequences.

In `@client/core/rs/src/api/execute/swarm.rs`:
- Around line 83-88: The doc comment for the label_rm field is incorrect: it
currently says "Add labels" and shows a `key=value` example; update the doc for
the pub label_rm: Option<Vec<String>> field to clearly state it removes labels
(e.g., "Remove labels from node") and adjust the example to the expected input
shape (likely just `key` or comma-separated keys) so the CLI/OpenAPI help
reflects removal semantics; keep the existing #[arg(long, alias = "lr")]
attributes and alias note intact.

In `@client/core/rs/src/entities/server.rs`:
- Around line 40-42: The doc comment for the field `err: Option<_Serror>`
incorrectly describes it as a message string; update the comment to state that
this field holds an optional structured error object (`_Serror`) representing
details about failures reaching the server (e.g., error code, message,
metadata), rather than a plain message text. Modify the comment above `err` in
server.rs to mention the structured error type and the fact it may be None on
success, and keep the wording concise and factual to avoid implying it's just a
message.

In `@client/core/rs/src/entities/swarm.rs`:
- Around line 29-31: The doc comment for the Swarm struct field `err` is stale —
it describes a message string but the field is now `pub err: Option<_Serror>`;
update the comment to state that `err` contains an optional structured error
payload of type `_Serror` (or similar structured error info) rather than a plain
message, so callers know to deserialize/inspect the `_Serror` fields.

In `@client/core/ts/src/types.ts`:
- Around line 5343-5347: Update the doc comment for the err field to reflect
that it is a structured error object, not a plain message: change the comment on
the err?: _Serror property (and the identical comment near lines referencing the
same type) to describe the shape/purpose of the _Serror payload (e.g., contains
code, message, details fields) and note it may be undefined when there is no
network/server error; also update the upstream source that generates this file
so the generated TS docs match the _Serror structure.
- Around line 10502-10505: The doc comment for the TypeScript property label_rm
is incorrect: it currently states it accepts `key=value` pairs like label_add,
but Docker expects only label keys to remove; update the comment for the
label_rm field (and mention its alias `lr`) to state it accepts label keys
(e.g., `"key1"`, `"key2"`) rather than `key=value` pairs so generated clients
build correct requests.

In `@ui/package.json`:
- Around line 45-55: Add a CI job in ci.yml to validate the UI: configure
setup-node to use Node.js v20.19+ or >=22.12, enable Corepack (pinned yarn), run
yarn install in the ui/ directory, then run tsc --noEmit and vite build there;
reference the UI toolchain changes in package.json (vite, `@vitejs/plugin-react`,
typescript) and ensure the job runs before merge to verify the upgraded tool
versions against the required Node runtime.

In `@ui/public/client/types.d.ts`:
- Line 1697: The public type for the `err` field was changed to `_Serror`, which
is a breaking contract; restore backward compatibility by making the `err`
property accept both shapes (e.g., change `err?: _Serror` to `err?: string |
_Serror`) or provide a backward-compatible union/alias so existing consumers
expecting a string continue to compile; update all occurrences noted (the `err`
property instances including the one currently declared as `err?: _Serror` and
the other places referenced in the review) to use the union type or an alias
type (e.g., `type Err = string | _Serror`) to avoid breaking changes.
- Around line 9921-9924: Documentation for label_rm is incorrect: it currently
says "Add labels" but should indicate removing labels by key. Update the JSDoc
for the label_rm property in types.d.ts (symbol: label_rm) to describe that it
removes labels by key (e.g., "Remove labels from node (key names only)"). Also
correct the alias note if needed (alias: `lr`) and ensure the distinction from
label_add (which accepts `key=value`) is clear.

In `@ui/src/components/config/organization-selector.tsx`:
- Line 28: The TextInput in the OrganizationSelector component always renders a
label in custom mode, ignoring the showLabel prop; update the
OrganizationSelector (or the JSX where TextInput is used) to forward the
showLabel prop (or conditionally render the label) when in custom mode so both
custom and non-custom modes respect showLabel; specifically locate the TextInput
usage in organization-selector.tsx and either pass showLabel={showLabel} into
TextInput or wrap the label rendering with a conditional that checks showLabel.

In `@ui/src/components/docker/container-ports.tsx`:
- Around line 25-31: The component ContainerPorts extracts the caller-provided
prop wrap but then ignores it by passing a hard-coded "nowrap" to
DividedChildren; update the DividedChildren usage(s) in ContainerPorts to pass
the extracted wrap variable instead of the string literal so callers can control
wrapping (check both occurrences around the component render and at the other
noted location). Ensure the prop name wrap is forwarded unchanged to
DividedChildren.
- Around line 35-37: Remove the leftover debug console.log calls in the Docker
container ports component: delete the console.log("mapped", portsMap) inside the
render logic and any other console.log instances noted (around lines handling
ports grouping at the block that uses portsMap and the later mapping/rendering
code), i.e., clean up in the docker/container-ports.tsx component where portsMap
is computed and where port groups are rendered (functions/variables: portsMap,
the mapping/render loop that renders grouped ports), ensuring no
console.debug/console.log calls remain before committing.

In `@ui/src/pages/settings/tags/color-selector.tsx`:
- Line 80: The option swatch uses bg={`Tag${color}.9`} while the selected swatch
still uses bg={`Tag${color}`}, causing inconsistent shades; make both use the
same shared token format (either `Tag${color}.9` or `Tag${color}`) so the
ColorSelector renders consistent colors—update the selected swatch usage (the
Box/render for the active selection) to match the option swatch bg token format
(or vice versa) wherever `Tag${color}`/`Tag${color}.9` appears.

In `@ui/src/resources/build/config.tsx`:
- Line 245: The list rendering uses index-based keys in the image_registries.map
that causes incorrect reconciliation; change the mapping that renders
<ImageRegistryConfig> to use a stable per-item identifier instead of key={index}
— add a unique id (e.g., UUID) to each registry object when created/loaded or
derive a stable composite key from registry properties (like registry.url +
registry.name) and use that value as the key; update any creation code that
builds image_registries to populate the id and ensure ImageRegistryConfig
remains keyed by that id.

In `@ui/src/resources/swarm/docker/nodes/index.tsx`:
- Around line 43-55: The DataTable is using a non-unique selection key
(selectOptions.selectKey) that prefers Name/Hostname and falls back to
"Unknown", causing collisions and mis-selection; change selectOptions.selectKey
to return the node's ID (node.ID) as the primary and only key so selections and
UpdateSwarmNode (prop swarm={id} nodes={...}) map to unique rows; update the
selectKey implementation referenced in DataTable/selectOptions to use node.ID
(and only fallback to a safe unique value if ID is truly missing) so
multi-select and updates target the correct nodes.

In `@ui/src/resources/swarm/docker/nodes/update.tsx`:
- Around line 49-57: The onSuccess handler for the per-node mutate from
useExecute("UpdateSwarmNode") (the mutate alias update()) closes the modal and
calls resetForm() immediately on the first successful request, which hides the
dialog before the whole batch completes; change the logic so that you aggregate
per-node updates (e.g., collect the promises returned by multiple update(...)
calls and await Promise.all or maintain a counter/promise that resolves when
every node's mutate has succeeded), only invoke close() and resetForm() after
the entire batch Promise resolves successfully, and surface any per-node
failures instead of closing; apply the same aggregation fix to the other similar
useExecute block around the 66-79 region.

---

Outside diff comments:
In `@bin/core/src/connection/server.rs`:
- Around line 319-334: The constructed `config` (the PartialServerConfig that
may include external_address) is never passed into the UpdateServer call;
instead a new hardcoded PartialServerConfig is created. Replace the inline
PartialServerConfig in the UpdateServer { id: server.id, config: ... }
expression with the previously built `config` value (ensuring it has enabled set
to Some(true) and address populated as needed), so UpdateServer uses the
constructed config (and keep the .resolve(&args).await.map_err(...) flow
unchanged).

In `@client/core/rs/src/entities/alert.rs`:
- Around line 92-99: SwarmUnhealthy's err field was changed to a structured type
(_Serror), which breaks deserialization of persisted alerts that store err as a
legacy string; update the deserialization to accept both shapes by adding a
compatibility serde handler (e.g., implement Deserialize for _Serror that
accepts either a string or an object, or declare an untagged enum wrapper) for
the _Serror type used by SwarmUnhealthy.err, or add a migration step to convert
legacy string errors into the new structured form before rollout; locate the
SwarmUnhealthy definition and the _Serror type in alert.rs and apply the
compatibility deserializer or migration accordingly.

In `@ui/src/resources/stack/config/index.tsx`:
- Around line 531-546: The non-swarm branch of the Link in the description block
still points to the service/create docs; update the ternary that sets Link's to
(where currSwarmId is checked) so the false/compose branch points to the Docker
Compose "up" docs (e.g. the compose up reference URL) instead of
"docker/service/create"; locate the JSX using currSwarmId and the Link component
inside the description prop and replace only the non-swarm URL.
🪄 Autofix (Beta)

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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9ad391a6-e043-4058-ad01-b82d251602d9

📥 Commits

Reviewing files that changed from the base of the PR and between 1d22b63 and 78970b2.

⛔ Files ignored due to path filters (3)
  • Cargo.lock is excluded by !**/*.lock
  • client/core/ts/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • ui/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (83)
  • .github/workflows/ci.yml
  • Cargo.toml
  • bin/binaries.Dockerfile
  • bin/chef.binaries.Dockerfile
  • bin/cli/aio.Dockerfile
  • bin/cli/src/command/execute.rs
  • bin/cli/src/config.rs
  • bin/core/aio.Dockerfile
  • bin/core/src/alert/discord.rs
  • bin/core/src/alert/mod.rs
  • bin/core/src/alert/slack.rs
  • bin/core/src/api/execute/maintenance.rs
  • bin/core/src/api/execute/mod.rs
  • bin/core/src/api/execute/swarm.rs
  • bin/core/src/api/read/toml.rs
  • bin/core/src/api/write/stack.rs
  • bin/core/src/config.rs
  • bin/core/src/connection/server.rs
  • bin/core/src/helpers/procedure.rs
  • bin/core/src/helpers/update.rs
  • bin/core/src/monitor/resources.rs
  • bin/core/src/monitor/swarm.rs
  • bin/core/src/resource/procedure.rs
  • bin/core/src/resource/server.rs
  • bin/core/src/state.rs
  • bin/core/src/sync/file.rs
  • bin/core/src/sync/resources.rs
  • bin/core/src/sync/toml.rs
  • bin/periphery/aio.Dockerfile
  • bin/periphery/src/api/swarm/mod.rs
  • bin/periphery/src/api/swarm/stack.rs
  • bin/periphery/src/config.rs
  • bin/periphery/src/docker/node.rs
  • client/core/rs/src/api/execute/mod.rs
  • client/core/rs/src/api/execute/openapi.rs
  • client/core/rs/src/api/execute/swarm.rs
  • client/core/rs/src/entities/alert.rs
  • client/core/rs/src/entities/config/cli/mod.rs
  • client/core/rs/src/entities/config/core.rs
  • client/core/rs/src/entities/config/periphery.rs
  • client/core/rs/src/entities/docker/node.rs
  • client/core/rs/src/entities/logger.rs
  • client/core/rs/src/entities/mod.rs
  • client/core/rs/src/entities/server.rs
  • client/core/rs/src/entities/swarm.rs
  • client/core/ts/package.json
  • client/core/ts/src/responses.ts
  • client/core/ts/src/types.ts
  • client/core/ts/tsconfig.json
  • client/periphery/rs/src/api/swarm.rs
  • example/alerter/Dockerfile
  • example/update_logger/Dockerfile
  • lib/command/src/lib.rs
  • lib/environment/src/lib.rs
  • ui/package.json
  • ui/public/client/responses.d.ts
  • ui/public/client/types.d.ts
  • ui/public/client/types.js
  • ui/src/components/config/organization-selector.tsx
  • ui/src/components/config/provider-selector.tsx
  • ui/src/components/docker/container-ports.tsx
  • ui/src/components/docker/container-selector.tsx
  • ui/src/components/docker/containers-section.tsx
  • ui/src/components/stack-service-selector.tsx
  • ui/src/pages/settings/tags/color-selector.tsx
  • ui/src/pages/swarm/node/index.tsx
  • ui/src/resources/build/config.tsx
  • ui/src/resources/build/info.tsx
  • ui/src/resources/deployment/update-available.tsx
  • ui/src/resources/procedure/config/executions.tsx
  • ui/src/resources/server/index.tsx
  • ui/src/resources/stack/config/index.tsx
  • ui/src/resources/stack/info.tsx
  • ui/src/resources/stack/update-available.tsx
  • ui/src/resources/swarm/config.tsx
  • ui/src/resources/swarm/docker/nodes/index.tsx
  • ui/src/resources/swarm/docker/nodes/update.tsx
  • ui/src/resources/swarm/index.tsx
  • ui/src/resources/sync/info.tsx
  • ui/src/ui/hover-error.tsx
  • ui/src/ui/labels-group.tsx
  • ui/src/ui/section.tsx
  • ui/tsconfig.json
💤 Files with no reviewable changes (4)
  • ui/tsconfig.json
  • ui/src/resources/deployment/update-available.tsx
  • ui/src/resources/stack/update-available.tsx
  • ui/src/components/docker/containers-section.tsx

Comment thread bin/core/src/alert/mod.rs
Comment on lines 261 to 264
let err = err
.as_ref()
.map(|e| format!("\nerror: {e}"))
.map(|e| format!("\nerror: {e:#?}"))
.unwrap_or_default();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Don't switch alert bodies to Debug formatting.

{e:#?} will send Rust debug output to user-facing channels, which is noisier and can expose internal structure. Keep these messages on Display formatting (or a sanitized string) instead.

Also applies to: 285-288

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/core/src/alert/mod.rs` around lines 261 - 264, The code is formatting
errors with Debug ("{e:#?}") when building the alert body; change the mapping
closure that produces err (the let err = err.as_ref().map(|e| format!("\nerror:
{e:#?}")).unwrap_or_default();) to use Display/sanitized text instead (e.g.
format!("\nerror: {}", e) or e.to_string()) so user-facing channels receive
clean, non-debug output; apply the same replacement for the other occurrence
that uses "{e:#?}" (the similar map/format block around lines reported).

Comment on lines +111 to +129
let text = match alert.level {
SeverityLevel::Ok => {
format!(
"{level} | *{name}*{region} | Periphery version now matches Core version ✅"
)
}
_ => {
format!(
"{level} | *{name}*{region} | Version mismatch detected ⚠️\nPeriphery: {server_version} | Core: {core_version}"
)
}
};
let blocks = vec![
Block::header(text.clone()),
Block::section(resource_link(
ResourceTargetVariant::Server,
id,
)),
];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep the mismatch details out of the header block.

Lines 118-120 build a multiline body string, then Line 124 reuses that whole payload as the header. This makes the rich Slack rendering depend on a header-sized block for the version details instead of a normal section, which is much more brittle and harder to scan. Split this into a short header plus a section for the Periphery/Core versions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/core/src/alert/slack.rs` around lines 111 - 129, The header currently
uses the full multiline `text` (created in the `match` on `alert.level`) which
places version details into a Slack header; change this so `Block::header`
receives a short one-line `header_text` (e.g., "{level} | *{name}*{region}" with
the ✅ or ⚠️ marker) and build a separate `section_text` for the version details
(e.g., "Periphery: {server_version} | Core: {core_version}") that you pass to
`Block::section`; update the `blocks` vector to include
`Block::header(header_text)` followed by the
`Block::section(resource_link(...))` and another `Block::section(section_text)`
only for the mismatch branch so Ok still shows a short header without extra
section.

Comment on lines +271 to 299
// Ensure server / swarm is reachable
match &swarm_or_server {
SwarmOrServer::None => continue,
SwarmOrServer::Server(server) => {
if !server_status_cache
.get(&server.id)
.await
.map(|s| matches!(s.state, ServerState::Ok))
.unwrap_or_default()
{
continue;
}
}
SwarmOrServer::Swarm(swarm) => {
if !swarm_status_cache
.get(&swarm.id)
.await
.map(|s| {
matches!(
s.state,
SwarmState::Healthy | SwarmState::Unhealthy
)
})
.unwrap_or_default()
{
continue;
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Extract the reachability gate into one helper.

These two SwarmOrServer matches are now identical. Keeping the server/swarm “reachable” predicate duplicated here will drift the next time the status rules change. Please centralize it behind a small helper and reuse it for both stacks and deployments.

Also applies to: 351-379

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/core/src/api/execute/maintenance.rs` around lines 271 - 299, The
reachability check for SwarmOrServer is duplicated; extract it into a helper
function (e.g., is_reachable(swarm_or_server, server_status_cache,
swarm_status_cache) -> bool) that encapsulates the current logic that checks
server_status_cache for ServerState::Ok and swarm_status_cache for
SwarmState::Healthy | SwarmState::Unhealthy, then replace both match blocks (the
one around SwarmOrServer in execute/maintenance.rs and the similar block used
for stacks/deployments) to call that helper and continue when it returns false;
reference SwarmOrServer, server_status_cache, swarm_status_cache,
ServerState::Ok, and SwarmState::Healthy|Unhealthy when implementing the helper.

Comment on lines +942 to +945
let deploy_services = if stack.config.auto_update_all_services
// Swarm stacks don't support individual service deploy
|| !stack.config.swarm_id.is_empty()
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Prefer using resolved execution target over raw config for swarm detection.

This avoids config-drift edge cases and keeps deploy logic coupled to the actual resolved target.

♻️ Proposed refactor
-  let deploy_services = if stack.config.auto_update_all_services
-    // Swarm stacks don't support individual service deploy
-    || !stack.config.swarm_id.is_empty()
+  let deploy_services = if stack.config.auto_update_all_services
+    // Swarm stacks don't support individual service deploy
+    || swarm_or_server.swarm_id().is_some()
   {
     Vec::new()
   } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let deploy_services = if stack.config.auto_update_all_services
// Swarm stacks don't support individual service deploy
|| !stack.config.swarm_id.is_empty()
{
let deploy_services = if stack.config.auto_update_all_services
// Swarm stacks don't support individual service deploy
|| swarm_or_server.swarm_id().is_some()
{
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/core/src/api/write/stack.rs` around lines 942 - 945, The deploy_services
condition currently checks stack.config.swarm_id directly (alongside
stack.config.auto_update_all_services); replace that raw-config swarm detection
with the stack's resolved execution target instead (e.g., use the resolved
execution target accessor on the Stack such as stack.execution_target /
stack.resolved_target or the helper is_swarm() on the resolved target) so the
clause becomes: if stack.config.auto_update_all_services ||
resolved_target.is_swarm() (keeping the existing Swarm comment), ensuring deploy
logic uses the actual resolved target rather than stack.config.swarm_id to avoid
config-drift edge cases.

Comment on lines +310 to +316
// Move address to external_address if not set.
// This helps preserve container port link behavior.
external_address: if !server.config.external_address.is_empty() {
Some(server.config.address)
} else {
None
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Logic appears inverted relative to the comment.

The comment states "Move address to external_address if not set," but the condition !server.config.external_address.is_empty() triggers when external_address is already set (non-empty). This seems backwards—if the intent is to preserve the container port link address by moving address to external_address only when external_address hasn't been configured yet, the condition should be:

-    external_address: if !server.config.external_address.is_empty() {
+    external_address: if server.config.external_address.is_empty() && !server.config.address.is_empty() {
       Some(server.config.address)

Please verify the intended behavior.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Move address to external_address if not set.
// This helps preserve container port link behavior.
external_address: if !server.config.external_address.is_empty() {
Some(server.config.address)
} else {
None
},
// Move address to external_address if not set.
// This helps preserve container port link behavior.
external_address: if server.config.external_address.is_empty() && !server.config.address.is_empty() {
Some(server.config.address)
} else {
None
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/core/src/connection/server.rs` around lines 310 - 316, The conditional is
inverted: currently it sets external_address to server.config.address only when
server.config.external_address is non-empty; change the logic so
external_address is set to Some(server.config.address) when
server.config.external_address IS empty (i.e., when no external_address has been
configured) and None otherwise. Locate the construction that assigns
external_address (reference server.config.external_address and the
external_address field in the server struct) and flip the condition to check for
emptiness before moving server.config.address into external_address.

Comment on lines +35 to +37
if (Object.keys(portsMap).length > 0) {
console.log("mapped", portsMap);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Remove the debug console.logs before merging.

These logs run on normal renders and dump port mapping/grouping details into the browser console. They look like leftover debugging noise rather than intentional client-side observability.

Also applies to: 44-46, 66-66

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/components/docker/container-ports.tsx` around lines 35 - 37, Remove
the leftover debug console.log calls in the Docker container ports component:
delete the console.log("mapped", portsMap) inside the render logic and any other
console.log instances noted (around lines handling ports grouping at the block
that uses portsMap and the later mapping/rendering code), i.e., clean up in the
docker/container-ports.tsx component where portsMap is computed and where port
groups are rendered (functions/variables: portsMap, the mapping/render loop that
renders grouped ports), ensuring no console.debug/console.log calls remain
before committing.

<Group justify="space-between">
<Text>{fmtUpperCamelcase(color)}</Text>
<Box w={25} h={25} bg={"Tag" + color} bdrs="md" />
<Box w={25} h={25} bg={`Tag${color}.9`} bdrs="md" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use one shared swatch token format for selected + options.

Line 80 uses Tag${color}.9, but the selected swatch (Line 55) still uses Tag${color}. This can show different shades for the same color depending on location.

🎯 Proposed fix
 export default function TagColorSelector({
   tagId,
   color,
   disabled,
   ...comboboxProps
 }: TagColorSelectorProps) {
+  const getTagSwatchBg = (c: Types.TagColor) => `Tag${c}.9`;
   const { search, setSearch, combobox } = useSearchCombobox();
@@
         <Button
           onClick={() => combobox.toggleDropdown()}
-          rightSection={<Box w={25} h={25} bg={"Tag" + color} bdrs="md" />}
+          rightSection={<Box w={25} h={25} bg={getTagSwatchBg(color)} bdrs="md" />}
           loading={isPending}
           w={200}
           justify="space-between"
         >
@@
               <Group justify="space-between">
                 <Text>{fmtUpperCamelcase(color)}</Text>
-                <Box w={25} h={25} bg={`Tag${color}.9`} bdrs="md" />
+                <Box w={25} h={25} bg={getTagSwatchBg(color)} bdrs="md" />
               </Group>
             </Combobox.Option>
           ))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/pages/settings/tags/color-selector.tsx` at line 80, The option swatch
uses bg={`Tag${color}.9`} while the selected swatch still uses
bg={`Tag${color}`}, causing inconsistent shades; make both use the same shared
token format (either `Tag${color}.9` or `Tag${color}`) so the ColorSelector
renders consistent colors—update the selected swatch usage (the Box/render for
the active selection) to match the option swatch bg token format (or vice versa)
wherever `Tag${color}`/`Tag${color}.9` appears.

(registry.account ?? "") +
index
}
key={index}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify whether ImageRegistryConfig keeps local/uncontrolled state
fd -i 'image-registry-config' --extension ts --extension tsx --exec sh -c '
  echo "===== {} ====="
  sed -n "1,260p" "{}"
'

# Inspect callsites using index keys in registry list rendering
rg -n -C3 --type=tsx 'image_registries\?\.map|key=\{index\}|onRemove'

Repository: FarisZR/komodo-agentic-cli

Length of output: 3109


🏁 Script executed:

# Read the config.tsx file around line 245 to see the context
head -n 260 ./ui/src/resources/build/config.tsx | tail -n 40

Repository: FarisZR/komodo-agentic-cli

Length of output: 1371


🏁 Script executed:

# Search for image_registries rendering in the file to see the full context
rg -n 'image_registries' ./ui/src/resources/build/config.tsx -B 5 -A 5

Repository: FarisZR/komodo-agentic-cli

Length of output: 1881


🏁 Script executed:

# Look at the broader structure to understand the registry data
rg -n 'key=' ./ui/src/resources/build/config.tsx -B 2 -A 2

Repository: FarisZR/komodo-agentic-cli

Length of output: 284


🏁 Script executed:

# Check where registry items are created/initialized
rg -n 'image_registry' ./ui/src/resources/build/config.tsx | head -20

Repository: FarisZR/komodo-agentic-cli

Length of output: 248


🏁 Script executed:

# Verify ImageRegistryConfig component doesn't have useState or useRef
rg -n 'useState|useRef|useReducer' ./ui/src/components/config/image-registry-config.tsx

Repository: FarisZR/komodo-agentic-cli

Length of output: 52


🏁 Script executed:

# Check the full context of how image_registry is defined in the config
rg -n 'image_registry:' ./ui/src/resources/build/config.tsx -A 2 -B 2

Repository: FarisZR/komodo-agentic-cli

Length of output: 921


Replace index-based key with a stable per-item identifier.

At line 245, key={index} causes React reconciliation to break during add/remove operations. When a registry item is deleted, all subsequent indices shift, causing React to incorrectly reuse component instances. Since registry objects currently lack a unique ID field, consider adding one (e.g., UUID) or using a stable composite key based on content.

{image_registries?.map((registry, index) => (
  <ImageRegistryConfig
    key={index}  // ← Replace with stable ID
    registry={registry}
    ...
  />
))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/resources/build/config.tsx` at line 245, The list rendering uses
index-based keys in the image_registries.map that causes incorrect
reconciliation; change the mapping that renders <ImageRegistryConfig> to use a
stable per-item identifier instead of key={index} — add a unique id (e.g., UUID)
to each registry object when created/loaded or derive a stable composite key
from registry properties (like registry.url + registry.name) and use that value
as the key; update any creation code that builds image_registries to populate
the id and ensure ImageRegistryConfig remains keyed by that id.

Comment on lines +43 to +55
titleRight={
<UpdateSwarmNode swarm={id} nodes={Object.keys(selectState[0])} />
}
actions={<SearchInput value={search} onSearch={setSearch} />}
>
<DataTable
tableKey="swarm-nodes"
data={filtered}
selectOptions={{
selectKey: (node) =>
node.Name ?? node.Hostname ?? node.ID ?? "Unknown",
state: selectState,
}}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use the node ID as the selection key.

Line 44 forwards the selection keys to UpdateSwarmNode, but Lines 52-53 derive those keys from Name/Hostname before ID. Those fields are display-oriented and can collide, and the "Unknown" fallback guarantees collisions when metadata is missing. Key the rows by ID so multi-select cannot collapse rows or update the wrong node.

🔧 Suggested change
       <DataTable
         tableKey="swarm-nodes"
         data={filtered}
         selectOptions={{
-          selectKey: (node) =>
-            node.Name ?? node.Hostname ?? node.ID ?? "Unknown",
+          selectKey: (node) =>
+            node.ID ?? node.Name ?? node.Hostname ?? "Unknown",
           state: selectState,
         }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
titleRight={
<UpdateSwarmNode swarm={id} nodes={Object.keys(selectState[0])} />
}
actions={<SearchInput value={search} onSearch={setSearch} />}
>
<DataTable
tableKey="swarm-nodes"
data={filtered}
selectOptions={{
selectKey: (node) =>
node.Name ?? node.Hostname ?? node.ID ?? "Unknown",
state: selectState,
}}
<DataTable
tableKey="swarm-nodes"
data={filtered}
selectOptions={{
selectKey: (node) =>
node.ID ?? node.Name ?? node.Hostname ?? "Unknown",
state: selectState,
}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/resources/swarm/docker/nodes/index.tsx` around lines 43 - 55, The
DataTable is using a non-unique selection key (selectOptions.selectKey) that
prefers Name/Hostname and falls back to "Unknown", causing collisions and
mis-selection; change selectOptions.selectKey to return the node's ID (node.ID)
as the primary and only key so selections and UpdateSwarmNode (prop swarm={id}
nodes={...}) map to unique rows; update the selectKey implementation referenced
in DataTable/selectOptions to use node.ID (and only fallback to a safe unique
value if ID is truly missing) so multi-select and updates target the correct
nodes.

Comment on lines +49 to +57
const { mutate: update, isPending: updatePending } = useExecute(
"UpdateSwarmNode",
{
onSuccess: () => {
close();
resetForm();
},
},
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Wait for the whole batch before closing the modal.

update() is fired once per node, but the shared onSuccess closes and resets the form after the first success. If later node updates fail, the dialog is already gone and the user gets a false “all done” signal. Please aggregate the per-node requests and only close/reset after the batch has fully succeeded.

Also applies to: 66-79

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/resources/swarm/docker/nodes/update.tsx` around lines 49 - 57, The
onSuccess handler for the per-node mutate from useExecute("UpdateSwarmNode")
(the mutate alias update()) closes the modal and calls resetForm() immediately
on the first successful request, which hides the dialog before the whole batch
completes; change the logic so that you aggregate per-node updates (e.g.,
collect the promises returned by multiple update(...) calls and await
Promise.all or maintain a counter/promise that resolves when every node's mutate
has succeeded), only invoke close() and resetForm() after the entire batch
Promise resolves successfully, and surface any per-node failures instead of
closing; apply the same aggregation fix to the other similar useExecute block
around the 66-79 region.

@FarisZR FarisZR merged commit a98cde7 into agentic-cli Apr 9, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants