Skip to content

Pr/rpc fixes#140

Open
moshloop wants to merge 15 commits into
mainfrom
pr/rpc-fixes
Open

Pr/rpc fixes#140
moshloop wants to merge 15 commits into
mainfrom
pr/rpc-fixes

Conversation

@moshloop

@moshloop moshloop commented Jul 7, 2026

Copy link
Copy Markdown
Member

Description

Brief description of the changes in this PR.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Performance improvement
  • Code refactoring

Testing

  • Tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have tested the CLI with example data

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • Any dependent changes have been merged and published in downstream modules

Breaking Changes

If this is a breaking change, please describe the impact and migration path for existing users:

Additional Notes

Add any additional notes, screenshots, or context about the changes here.

Summary by CodeRabbit

  • New Features

    • Added request-scoped performance timing via Server-Timing response headers.
    • Expanded AI model catalog and default model selection (including model temperature info in model responses).
    • Enhanced tool metadata/permission hints surfaced through OpenAPI/UI behavior.
    • Added UI linting support for the entity demo web app.
  • Bug Fixes

    • Hidden flags are no longer shown in help output or included in generated schemas/operation parameters.
    • Route registration avoids conflicts when routes differ only by wildcard parameter names.
    • Markdown “pretty” output now preserves schema field order (and safely handles nil input).
    • Improved CLI/task rendering stability (no duplicate/final output; trailing/blank output preservation).

moshloop added 7 commits July 6, 2026 17:25
A completed task kept its runFunc closure for the manager's 10-minute
run-retention window. When the closure captured large state (parsed
inputs, accumulators, intermediate outputs), that state stayed live for
every retained task — a leak that scales with the number of past runs.

Release runFunc once executeTask finishes: it is invoked nowhere else and
retries are already exhausted by then. Completed tasks keep only their
lightweight result and snapshot for viewing.
Implement request-scoped timing accumulation for HTTP handlers with Server-Timing header emission. Provides AddTiming and Track APIs for measuring operation phases within the RPC executor and custom handlers, summing durations by phase name and emitting millisecond-precision metrics in the response header. The middleware preserves http.Flusher for streaming responses and integrates without importing rpc package dependencies.: feat
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds frontend lint tooling, updates the entity webapp icon wiring, refreshes aichat model and dependency data, adds hidden flag propagation, changes markdown ordering, filters RPC converter flags, adds HTTP timing instrumentation, normalizes route deduplication, updates entity tool-hint metadata plumbing, and refactors task manager/render lifecycle behavior.

Changes

Clicky-UI Lint Tooling

Layer / File(s) Summary
Lint target, config, and dependency wiring
Makefile, .gitignore, CONTRIBUTING.md, examples/enitity/webapp/.oxlintrc.json, examples/enitity/webapp/package.json, examples/enitity/webapp/pnpm-workspace.yaml
Adds the lint-clicky-ui Makefile target, un-ignores the oxlint config file, documents the command, adds the oxlint configuration, and wires the webapp script and dependency entries.

Webapp Icon Component Migration

Layer / File(s) Summary
TypeScript paths and Vite alias for icons entrypoint
examples/enitity/webapp/tsconfig.json, examples/enitity/webapp/vite.config.ts
Adds vite/client types, reformats clicky-ui path mappings, and adds a Vite alias for @flanksource/clicky-ui/icons.
Icon component usage in App and ChatWidget
examples/enitity/webapp/src/App.tsx, examples/enitity/webapp/src/ChatWidget.tsx
Updates App.tsx and ChatWidget.tsx to import and render clicky-ui icon components in place of the previous string-based icon identifiers, with related JSX reformatting.

AI Chat Default Model and Tool Metadata Update

Layer / File(s) Summary
Agent request spec construction
aichat/agent.go
Imports commons-db/shell and updates the captain request spec to set Budget.MaxTurns and Setup.Cwd.
Default model ID and catalog contents
aichat/models.go, aichat/agent_test.go, aichat/models_test.go
Updates DefaultModelID, replaces the built-in model catalog entries and context windows, and adjusts model-related test expectations.
Tool mode and approval metadata
aichat/approval.go, aichat/approval_test.go
Renames tool modes, extends tool metadata with permission and strictness fields, and updates approval and tool-selection tests.
Tool definitions and catalog entries
aichat/tool_registry.go, aichat/tool_catalog.go, aichat/tool_registry_test.go
Extends tool definitions, catalog entries, and request filtering with parent/icon/default-permission/strict metadata, plus new helper logic and test coverage.
Model info response and catalog tests
aichat/models.go, aichat/models_test.go
Extends ModelInfo with temperature output, updates catalog construction, and adjusts model tests for the new provider/model defaults.
Genkit effort configuration
aichat/genkit.go, aichat/models_test.go
Delegates effort configuration to captain, adds backend/model normalization helpers, and updates Genkit tests for the new provider mapping.
Module graph refresh
aichat/go.mod
Updates the direct and indirect dependency requirements for the aichat module, including a new direct commons-db dependency and broad transitive version changes.

Hidden CLI Flag Support

Layer / File(s) Summary
Hidden field contract, parsing, and binding
flags/types.go, flags/parser.go, flags/binding.go, flags/parser_test.go
Extends FieldInfo with Hidden, sets it from the struct tag during parsing, and marks the Cobra flag hidden in BindFlag; tests cover parsing and binding visibility.

Schema-Ordered Markdown Formatting

Layer / File(s) Summary
Schema-ordered rendering and fallback
formatters/markdown_formatter.go, formatters/map_input_test.go
Adds nil handling, renders schema-defined fields before extra typed-map fields, and verifies the rendered label order in tests.

RPC Converter Flag Filtering

Layer / File(s) Summary
Local non-persistent flag filtering
rpc/converter.go, rpc/converter_flags_test.go
ConvertCommand now reads cmd.LocalNonPersistentFlags(), and the new test asserts inherited and hidden flags are excluded from generated parameters and schema.

HTTP Server-Timing Middleware

Layer / File(s) Summary
Timings accumulator and context wiring
rpc/http/timing.go, rpc/http/timing_test.go
Introduces the Timings accumulator, context wiring helpers, duration recording helpers, and header rendering for accumulated phases.
TimingMiddleware and timingRecorder
rpc/http/middleware.go, rpc/http/middleware_test.go
Wraps handlers with request timing context, stamps Server-Timing on write paths, and preserves flush and unwrap behavior; tests cover the header output and flush handling.

RPC Route Dedup for Wildcard Names

Layer / File(s) Summary
Wildcard-normalized dedupe key
rpc/serve.go, rpc/serve_test.go
Adds normalizeWildcardNames, switches duplicate detection to use the normalized path key, and changes route logging to clicky logging.

Entity Tool Hint Metadata

Layer / File(s) Summary
Tool hint types and aliases
entity/tool_hints.go, entity_aliases.go
Defines ToolPermission, MCPToolHints, and the exported re-exports for tool permissions and annotation constants.
Annotations and builder propagation
entity/annotations.go, entity/builder.go, entity/command.go
Computes tool hints from annotations, emits them through annotation helpers, and keeps entity and action builders synchronized with tool-group and hint data.
Entity, action, and operation metadata
entity/entity.go, entity/operation.go, entity/sub_command.go
Adds tool-hint fields to entity and action state, reconciles tool-group values during registration, and carries the hints through RPC operation metadata and command wiring.
Tool hint tests
entity/toolgroup_test.go
Extends entity tests to validate builder propagation, annotation emission, and action-level tool-hint wiring.
MCP tool annotations and meta
mcp/registry.go, mcp/registry_test.go
Adds MCP annotation and _meta fields to tool definitions and populates them from RPC and Clicky tool hints.

Task Manager Render and Output Lifecycle

Layer / File(s) Summary
Task timestamps and log-delta rendering
task/task.go, task/task_logs_render.go, task/task_log_display_test.go
Protects task timestamp reads with locking, adds EndTime, and replaces Pretty with the log-offset renderer plus incremental plain-delta tracking.
Pretty rendering with buffered logs
task/task_logs_render.go, task/task_log_display_test.go
Implements prettyWithLogOffset, including pretty-mixin delegation, display-name construction, status styling, buffered-log truncation, and per-level log rendering.
Group snapshots and duration calculations
task/group.go, task/group_race_test.go
Returns copied task snapshots from groups and updates status and duration calculations to use snapshot-based iteration and task end timestamps.
Manager render state and startup
task/manager.go, task/manager_color_test.go
Replaces one-time render guards with lifecycle state, adds stream-completion channels, updates color defaults, changes shutdown logging, and defers global group publication until initialization completes.
Render loop lifecycle
task/manager_lifecycle.go, task/render_restart_test.go, task/render_hook_test.go
Implements render start/stop state transitions, teardown coordination, and final render behavior for loop and non-loop paths.
Stdout/stderr capture
task/manager_output.go, task/manager_output_test.go, task/manager_wait.go
Streams stdout and stderr through drain goroutines, preserves blank lines, and waits for completion before restoring output during stop and wait paths.
Render restart behavior
task/render_restart_test.go
Adds restart-focused tests for render-loop re-entry after stop and after a no-progress state flip.
Plain render de-duplication
task/render.go, task/render_dedupe_test.go
Adds tests that validate plain render snapshot history, single emission of logs, and idempotent final output behavior.
Wait stops renderer and capture
task/manager_wait.go
Updates WaitSilent() and Wait() to stop output capture after stopping the renderer and documents that behavior.
Output capture tests
task/manager_output_test.go
Adds tests covering trailing-output flush, blank-line preservation, and stop-after-wait output restoration.
RunFunc cleanup and GC verification test
task/worker.go, task/worker_release_test.go
executeTask nils task.runFunc after completion, and the new test uses a finalizer to confirm the captured payload becomes collectible.

Possibly related PRs

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested labels: released

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is just the template with placeholders and no actual PR details or selections. Replace the template text with a real summary, change type, testing notes, checklist status, and any breaking-change context.
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and does not describe the main change in the pull request. Use a specific title that names the primary fix or feature instead of a generic label.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 pr/rpc-fixes

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.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Gavel crashed before producing results

Exit code: 1
Error: gavel exited 1 before writing results

Last lines of gavel.log

go: downloading github.com/cloudflare/circl v1.6.3 go: downloading github.com/cenkalti/backoff/v5 v5.0.3 go: downloading github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 go: downloading github.com...

Full gavel.log, JSON stub, and HTML stub are in the workflow artifact.

View full results

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
flags/binding.go (1)

125-134: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject hidden+required flags for body-less RPC calls.

ParseStructFields allows hidden:"true" and required:"true" together, but rpc/converter.go drops hidden fields from the generated parameters. That makes the value unreachable for GET/HEAD-style RPCs, so Cobra will still fail with a missing required flag. Add a parser check or a test for this combination.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flags/binding.go` around lines 125 - 134, ParseStructFields currently allows
hidden and required flags to be combined, but hidden fields are omitted by
rpc/converter.go so body-less RPC calls can never supply them. Add a validation
in ParseStructFields or the flag metadata path to reject hidden:"true" with
required:"true" together, and cover the behavior with a test around the
info.Hidden/info.Required handling and cmd.MarkFlagRequired/MarkHidden setup.
🧹 Nitpick comments (1)
rpc/serve.go (1)

498-501: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Route output should go through clicky logging, not fmt.Printf.

These warning lines write directly to os.Stdout. Per the repository guidelines, library code should route output through the clicky logging path (e.g. clicky.Warnf) rather than fmt.Printf/fmt.Println, so live task rendering isn't corrupted. The pre-existing fmt.Printf calls elsewhere in this function share the same issue.

As per coding guidelines: "Do not write directly to os.Stdout or os.Stderr in library code; route output through the clicky logging/writer path instead" and "emit logs through clicky.Infof, clicky.Errorf, clicky.Warnf, or clicky.Debugf rather than fmt.Println".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rpc/serve.go` around lines 498 - 501, The duplicate-endpoint warning in the
RPC registration flow is writing directly to standard output via fmt.Printf,
which should be routed through clicky logging instead. Update the warning path
in the endpoint registration logic in serve.go to use clicky.Warnf (or the
appropriate clicky logger) for the duplicate endpoint message and the related
Path/Already registered/Skipping details, and replace the other fmt.Printf calls
in the same function with clicky logging so library output does not bypass the
task rendering path.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/enitity/webapp/.oxlintrc.json`:
- Around line 3-8: The oxlint config in the webapp example references the
clicky-ui plugin via a specifier that resolves outside this repository, so
update the setup accordingly. Either change the plugin path in the
.oxlintrc.json entry for clicky-ui to point to a location inside this repo, or
add clear documentation in the relevant setup docs explaining that a sibling
clicky-ui checkout is required. Use the clicky-ui plugin entry in the jsPlugins
array as the locator.

In `@rpc/http/middleware.go`:
- Around line 12-18: TimingMiddleware currently relies on timingRecorder.stamp()
being triggered by WriteHeader/Write/Flush, so handlers that return without
writing never set Server-Timing. Update TimingMiddleware to call rec.stamp()
after next.ServeHTTP returns, and keep the existing write-path behavior in
timingRecorder unchanged. Add a regression test around TimingMiddleware to cover
a handler that returns without writing and assert the Server-Timing header is
still emitted.

---

Outside diff comments:
In `@flags/binding.go`:
- Around line 125-134: ParseStructFields currently allows hidden and required
flags to be combined, but hidden fields are omitted by rpc/converter.go so
body-less RPC calls can never supply them. Add a validation in ParseStructFields
or the flag metadata path to reject hidden:"true" with required:"true" together,
and cover the behavior with a test around the info.Hidden/info.Required handling
and cmd.MarkFlagRequired/MarkHidden setup.

---

Nitpick comments:
In `@rpc/serve.go`:
- Around line 498-501: The duplicate-endpoint warning in the RPC registration
flow is writing directly to standard output via fmt.Printf, which should be
routed through clicky logging instead. Update the warning path in the endpoint
registration logic in serve.go to use clicky.Warnf (or the appropriate clicky
logger) for the duplicate endpoint message and the related Path/Already
registered/Skipping details, and replace the other fmt.Printf calls in the same
function with clicky logging so library output does not bypass the task
rendering path.
🪄 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: CHILL

Plan: Pro

Run ID: 354ff0d0-8083-4437-8944-fae7e2198e29

📥 Commits

Reviewing files that changed from the base of the PR and between 0101296 and 31e982a.

⛔ Files ignored due to path filters (1)
  • examples/enitity/webapp/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • .gitignore
  • CONTRIBUTING.md
  • Makefile
  • aichat/agent.go
  • aichat/agent_test.go
  • aichat/models.go
  • aichat/models_test.go
  • examples/enitity/webapp/.oxlintrc.json
  • examples/enitity/webapp/package.json
  • examples/enitity/webapp/pnpm-workspace.yaml
  • examples/enitity/webapp/src/App.tsx
  • examples/enitity/webapp/src/ChatWidget.tsx
  • examples/enitity/webapp/tsconfig.json
  • examples/enitity/webapp/vite.config.ts
  • flags/binding.go
  • flags/parser.go
  • flags/parser_test.go
  • flags/types.go
  • formatters/map_input_test.go
  • formatters/markdown_formatter.go
  • rpc/converter.go
  • rpc/converter_flags_test.go
  • rpc/http/middleware.go
  • rpc/http/middleware_test.go
  • rpc/http/timing.go
  • rpc/http/timing_test.go
  • rpc/serve.go
  • rpc/serve_test.go
  • task/worker.go
  • task/worker_release_test.go

Comment thread examples/enitity/webapp/.oxlintrc.json
Comment thread rpc/http/middleware.go
moshloop added 2 commits July 7, 2026 10:32
…edge case

Gavel-Issue-Id: 6b2997e35e373dc3d3209e33280094ab
Claude-Session-Id: f7ecddc5-467d-4fd4-a7b8-f0778f653659
@moshloop moshloop enabled auto-merge (rebase) July 7, 2026 07:35
@socket-security

socket-security Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedgolang/​github.com/​flanksource/​commons-db@​v0.1.1372100100100100
Addednpm/​@​flanksource/​clicky-ui@​0.3.97710010096100
Addednpm/​oxlint@​1.72.0911009295100

View full report

@socket-security

socket-security Bot commented Jul 7, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: golang github.com/envoyproxy/go-control-plane/envoy is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?golang/github.com/firebase/genkit/go@v1.8.0golang/github.com/flanksource/commons-db@v0.1.13golang/github.com/flanksource/clicky@v1.21.36golang/github.com/envoyproxy/go-control-plane/envoy@v1.37.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/envoyproxy/go-control-plane/envoy@v1.37.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: golang github.com/envoyproxy/go-control-plane/envoy is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?golang/github.com/firebase/genkit/go@v1.8.0golang/github.com/flanksource/commons-db@v0.1.13golang/github.com/flanksource/clicky@v1.21.36golang/github.com/envoyproxy/go-control-plane/envoy@v1.37.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/envoyproxy/go-control-plane/envoy@v1.37.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: golang github.com/envoyproxy/go-control-plane/envoy is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?golang/github.com/firebase/genkit/go@v1.8.0golang/github.com/flanksource/commons-db@v0.1.13golang/github.com/flanksource/clicky@v1.21.36golang/github.com/envoyproxy/go-control-plane/envoy@v1.37.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/envoyproxy/go-control-plane/envoy@v1.37.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: golang github.com/envoyproxy/go-control-plane/envoy is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?golang/github.com/firebase/genkit/go@v1.8.0golang/github.com/flanksource/commons-db@v0.1.13golang/github.com/flanksource/clicky@v1.21.36golang/github.com/envoyproxy/go-control-plane/envoy@v1.37.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/envoyproxy/go-control-plane/envoy@v1.37.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: golang github.com/envoyproxy/go-control-plane/envoy is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?golang/github.com/firebase/genkit/go@v1.8.0golang/github.com/flanksource/commons-db@v0.1.13golang/github.com/flanksource/clicky@v1.21.36golang/github.com/envoyproxy/go-control-plane/envoy@v1.37.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/envoyproxy/go-control-plane/envoy@v1.37.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: golang github.com/envoyproxy/go-control-plane/envoy is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?golang/github.com/firebase/genkit/go@v1.8.0golang/github.com/flanksource/commons-db@v0.1.13golang/github.com/flanksource/clicky@v1.21.36golang/github.com/envoyproxy/go-control-plane/envoy@v1.37.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/envoyproxy/go-control-plane/envoy@v1.37.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: golang github.com/microsoft/go-mssqldb is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?golang/github.com/flanksource/commons-db@v0.1.13golang/github.com/microsoft/go-mssqldb@v1.10.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/microsoft/go-mssqldb@v1.10.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: golang github.com/planetscale/vtprotobuf is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?golang/github.com/firebase/genkit/go@v1.8.0golang/github.com/flanksource/commons-db@v0.1.13golang/github.com/flanksource/clicky@v1.21.36golang/github.com/planetscale/vtprotobuf@v0.6.1-0.20240319094008-0393e58bdf10

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/planetscale/vtprotobuf@v0.6.1-0.20240319094008-0393e58bdf10. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm jotai is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: examples/enitity/webapp/pnpm-lock.yamlnpm/@flanksource/clicky-ui@0.3.9npm/jotai@2.20.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/jotai@2.20.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

moshloop added 6 commits July 7, 2026 10:41
…ure drain race, blank-line loss, noColor default, StartGroup race
Introduce MCPToolHints struct to hold tool-level metadata including title, icon, group, parent, boolean hints (readOnly, destructive, idempotent, openWorld), default permission, and strict. Update entity building, action specs, and command annotation to use the new hints instead of a plain tool-group string. This enables richer MCP tool annotations and Clicky UI metadata inheritance.
…anonical mode names

Introduce canonical tool permission modes (on/off/ask/auto) with backward compatibility for legacy labels. Add DefaultPermission, Parent, Icon, and Strict fields to ToolInfo and ToolCatalogEntry to control tool behavior without user preferences. Refactor effortConfig to delegate to captain's shared EffortConfig, removing duplicate provider-specific logic. Update tests to verify mode normalization, default permission filtering, and temperature gating.
Introduce ToolAnnotations struct for well-known MCP tool properties. Infer readOnly, destructive, idempotent hints from HTTP method/verb. Propagate tool hints (icon, group, parent, permission, strict) into _meta field. Update RPCOperation to carry ToolHints from clicky annotations.
Add mutex protection to Group and Task methods to fix data races. Introduce render lifecycle state machine (renderIdle, renderRunning, renderStopping) to allow the render loop to be restarted after stopRender, enabling subsequent enqueues to trigger fresh rendering. Implement prettyPlainDelta to emit only new log entries per PlainRender tick, preserving full log history for snapshots and final tree. Add plainSummaryText for a concise one-line summary after plain loop. Ensure renderFinal is idempotent and final output includes summary without duplicating task lines. Update Wait/WaitSilent to flush captured output via StopCapturingOutput. Fix WaitTime and StartTime to be thread-safe.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
aichat/tool_catalog.go (1)

105-131: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

customCatalogEntry constructs a full ToolInfo but only Group and Name are read.

The info variable is used solely for preferenceKey(info), which only accesses info.Group and info.Name. Fields like OperationName, Method, Path, ClickyVerb, ClickyScope, Parent, Icon, DefaultPermission, and Strict are set but never read. Additionally, defaultPermissionMode(def.DefaultPermission) is called twice — once in info and once in the return value.

♻️ Simplify info construction in customCatalogEntry
 func customCatalogEntry(def ToolDefinition, name string, schema map[string]any) ToolCatalogEntry {
-	info := ToolInfo{
-		Name:              name,
-		OperationName:     def.Name,
-		Method:            def.Method,
-		Path:              def.Path,
-		ClickyVerb:        def.Verb,
-		ClickyScope:       def.Scope,
-		Group:             def.Group,
-		Parent:            def.Parent,
-		Icon:              def.Icon,
-		DefaultPermission: defaultPermissionMode(def.DefaultPermission),
-		Strict:            def.Strict,
-	}
+	info := ToolInfo{Name: name, Group: def.Group}
 	return ToolCatalogEntry{
 		Name:              name,
 		Title:             def.Name,
 		Description:       def.Description,
 		Source:            "custom",
 		Group:             def.Group,
 		Parent:            def.Parent,
 		Icon:              def.Icon,
 		PreferenceKey:     preferenceKey(info),
 		DefaultPermission: defaultPermissionMode(def.DefaultPermission),
 		Strict:            def.Strict,
 		Method:            def.Method,
 		Path:              def.Path,
 		OperationName:     def.Name,
 		InputSchema:       objectSchema(schema),
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aichat/tool_catalog.go` around lines 105 - 131, customCatalogEntry is
building a full ToolInfo even though preferenceKey(info) only needs Group and
Name. Simplify the local info construction to include only the fields actually
used by preferenceKey, and avoid setting unused fields like OperationName,
Method, Path, ClickyVerb, ClickyScope, Parent, Icon, Strict, and
DefaultPermission. Also compute defaultPermissionMode(def.DefaultPermission)
once and reuse it in the ToolCatalogEntry return value instead of calling it
twice.
task/task.go (1)

689-698: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

prettyPlainDelta returns api.Text but is not named Pretty, PrettyFull, or PrettyRow.

As per coding guidelines, a function that returns api.Text must be named Pretty, PrettyFull, or PrettyRow; otherwise it should return the api.Textable interface instead. prettyPlainDelta is an internal helper, so this is low-priority, but returning api.Textable would align with the guideline.

♻️ Proposed refactor: return api.Textable from prettyPlainDelta
-func (t *Task) prettyPlainDelta() api.Text {
+func (t *Task) prettyPlainDelta() api.Textable {
 	t.mu.Lock()
 	defer t.mu.Unlock()
 	text, total := t.prettyWithLogOffset(t.plainLogsRendered)
 	t.plainLogsRendered = total
 	return text
 }

Note: callers in render.go that use rendered.String() / rendered.ANSI() would need to resolve the api.Textable first (e.g., via .ToText() or equivalent), so verify the call sites before applying.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@task/task.go` around lines 689 - 698, prettyPlainDelta currently returns
api.Text, which conflicts with the naming guideline for non-Pretty helpers.
Update Task.prettyPlainDelta to return api.Textable instead, and adjust the
render.go callers that consume its result so they explicitly resolve it to text
before calling String() or ANSI(). Keep the existing behavior of
prettyWithLogOffset and plainLogsRendered unchanged while making the return type
consistent with the api.Textable contract.

Source: Coding guidelines

mcp/registry.go (1)

87-91: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

operationToolHints is computed twice per NewMcpTool call.

Both toolAnnotations (line 96) and clickyToolMeta (line 155) independently call operationToolHints(rpcOp), duplicating the merge work. Consider computing it once in NewMcpTool and passing the result to both helpers.

♻️ Proposed refactor: compute hints once in NewMcpTool
 func NewMcpTool(rpcOp *rpc.RPCOperation) *ToolDefinition {
+	hints := operationToolHints(rpcOp)
 	// Convert RPC schema to MCP schema
 	inputSchema := Schema{
 		Type:       rpcOp.Schema.Type,
 		Properties: make(map[string]Property),
 		Required:   rpcOp.Schema.Required,
 	}
 	// ... schema conversion unchanged ...
 	appName := "app"
 	if rpcOp.Command != nil {
 		appName = rpcOp.Command.RootName()
 	}
 	return &ToolDefinition{
 		Name:        rpcOp.Name,
 		Title:       fmt.Sprintf("%s %s", appName, rpcOp.Name),
 		Description: rpcOp.Description,
 		InputSchema: inputSchema,
-		Annotations: toolAnnotations(rpcOp),
-		Meta:        clickyToolMeta(rpcOp),
+		Annotations: toolAnnotationsFromHints(hints, rpcOp),
+		Meta:        clickyToolMetaFromHints(hints),
 		Command:     rpcOp.Command,
 	}
 }

Also applies to: 95-96, 154-155

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp/registry.go` around lines 87 - 91, `operationToolHints` is being
recomputed twice inside `NewMcpTool` via `toolAnnotations` and `clickyToolMeta`;
compute the hints once in `NewMcpTool` and pass them into both helpers instead
of calling `operationToolHints(rpcOp)` separately. Update the signatures of
`toolAnnotations` and `clickyToolMeta` as needed, keeping the behavior of
`Annotations`, `Meta`, and `Command` unchanged.
task/render.go (1)

185-222: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use api.Text{}.Append(...) instead of api.Text{...} struct literal.

Line 221 uses api.Text{Content: summary, Style: "text-gray-400"}. As per coding guidelines, when building api.Text, use clicky.Text(...) or chained api.Text{}.Append(...) calls; do not use api.Text{...} struct literals or Children: slice literals.

Additionally, verify that plainSummaryText is actually called — it does not appear in the provided code context or graph references. If it is dead code, consider removing it or wiring it into the final render path.

♻️ Proposed refactor
-	return api.Text{Content: summary, Style: "text-gray-400"}
+	return api.Text{}.Append(summary, "text-gray-400")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@task/render.go` around lines 185 - 222, plainSummaryText currently returns
api.Text via a struct literal, which violates the text-building convention.
Update plainSummaryText to build the summary using api.Text{}.Append(...) (or
clicky.Text(...)) instead of api.Text{Content: ..., Style: ...}. Also check
whether plainSummaryText is referenced by the render flow; if it is unused,
remove it or wire it into the final render path.

Source: Coding guidelines

task/task_logs_render.go (1)

20-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use api.Text{}.Append(...) instead of api.Text{...} struct literals.

Line 105–108 uses api.Text{Content: ..., Style: ...} struct literals. As per coding guidelines, when building api.Text, use clicky.Text(...) or chained api.Text{}.Append(...) calls; do not use api.Text{...} struct literals or Children: slice literals.

♻️ Proposed refactor
-		text.Children = append(text.Children, api.Text{
-			Content: fmt.Sprintf("\n%s", lo.Ellipsis(log.Message, 500)),
-			Style:   logStyle,
-		})
+		text.Children = append(text.Children, api.Text{}.
+			Append(fmt.Sprintf("\n%s", lo.Ellipsis(log.Message, 500)), logStyle))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@task/task_logs_render.go` around lines 20 - 112, The log rendering in
prettyWithLogOffset is building child entries with an api.Text struct literal,
which violates the api.Text construction guideline. Update the log append path
in Task.prettyWithLogOffset to build each child using api.Text{}.Append(...) or
clicky.Text(...) instead of api.Text{Content: ..., Style: ...}, and keep the
existing log-style selection and ellipsis handling intact.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@aichat/tool_catalog.go`:
- Around line 208-232: `applyToolMetadata` treats `DefaultPermission`
differently from `Group`, `Parent`, `Icon`, and `Strict`, so update the function
in `tool_catalog.go` to make the overwrite behavior consistent. Either change
`DefaultPermission` to only set when `entry.DefaultPermission` is still unset,
matching the fill-if-empty pattern used by the other fields, or intentionally
make the other fields always overwrite too if metadata should be authoritative.
Use the existing `stringMetadata`, `boolMetadata`, and `defaultPermissionMode`
flow in `applyToolMetadata` as the place to apply the consistency fix.

In `@task/manager_lifecycle.go`:
- Around line 172-201: `finishRenderTeardown` has a race where `renderFinal` can
run after a new render loop has already started, causing the next batch’s final
output to be skipped. Update the teardown flow in `Manager.finishRenderTeardown`
to check `renderState` while holding `tm.mu` and only call `renderFinal` when no
new loop is already `renderRunning`; keep the existing `cleanupTerminal`,
`uninstallLogSerializer`, and `releaseRenderTerminal` logic tied to the locked
teardown decision.

---

Nitpick comments:
In `@aichat/tool_catalog.go`:
- Around line 105-131: customCatalogEntry is building a full ToolInfo even
though preferenceKey(info) only needs Group and Name. Simplify the local info
construction to include only the fields actually used by preferenceKey, and
avoid setting unused fields like OperationName, Method, Path, ClickyVerb,
ClickyScope, Parent, Icon, Strict, and DefaultPermission. Also compute
defaultPermissionMode(def.DefaultPermission) once and reuse it in the
ToolCatalogEntry return value instead of calling it twice.

In `@mcp/registry.go`:
- Around line 87-91: `operationToolHints` is being recomputed twice inside
`NewMcpTool` via `toolAnnotations` and `clickyToolMeta`; compute the hints once
in `NewMcpTool` and pass them into both helpers instead of calling
`operationToolHints(rpcOp)` separately. Update the signatures of
`toolAnnotations` and `clickyToolMeta` as needed, keeping the behavior of
`Annotations`, `Meta`, and `Command` unchanged.

In `@task/render.go`:
- Around line 185-222: plainSummaryText currently returns api.Text via a struct
literal, which violates the text-building convention. Update plainSummaryText to
build the summary using api.Text{}.Append(...) (or clicky.Text(...)) instead of
api.Text{Content: ..., Style: ...}. Also check whether plainSummaryText is
referenced by the render flow; if it is unused, remove it or wire it into the
final render path.

In `@task/task_logs_render.go`:
- Around line 20-112: The log rendering in prettyWithLogOffset is building child
entries with an api.Text struct literal, which violates the api.Text
construction guideline. Update the log append path in Task.prettyWithLogOffset
to build each child using api.Text{}.Append(...) or clicky.Text(...) instead of
api.Text{Content: ..., Style: ...}, and keep the existing log-style selection
and ellipsis handling intact.

In `@task/task.go`:
- Around line 689-698: prettyPlainDelta currently returns api.Text, which
conflicts with the naming guideline for non-Pretty helpers. Update
Task.prettyPlainDelta to return api.Textable instead, and adjust the render.go
callers that consume its result so they explicitly resolve it to text before
calling String() or ANSI(). Keep the existing behavior of prettyWithLogOffset
and plainLogsRendered unchanged while making the return type consistent with the
api.Textable contract.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a9190406-4cd9-4c9c-ba2c-b19f71364a32

📥 Commits

Reviewing files that changed from the base of the PR and between dae8dfa and 68ac1b0.

📒 Files selected for processing (37)
  • aichat/approval.go
  • aichat/approval_test.go
  • aichat/genkit.go
  • aichat/models.go
  • aichat/models_test.go
  • aichat/tool_catalog.go
  • aichat/tool_registry.go
  • aichat/tool_registry_test.go
  • api/table_test.go
  • entity/annotations.go
  • entity/builder.go
  • entity/command.go
  • entity/entity.go
  • entity/operation.go
  • entity/sub_command.go
  • entity/tool_hints.go
  • entity/toolgroup_test.go
  • entity_aliases.go
  • mcp/registry.go
  • mcp/registry_test.go
  • rpc/converter.go
  • rpc/converter_toolgroup_test.go
  • task/group.go
  • task/group_race_test.go
  • task/manager.go
  • task/manager_color_test.go
  • task/manager_lifecycle.go
  • task/manager_output.go
  • task/manager_output_test.go
  • task/manager_wait.go
  • task/render.go
  • task/render_dedupe_test.go
  • task/render_hook_test.go
  • task/render_restart_test.go
  • task/task.go
  • task/task_log_display_test.go
  • task/task_logs_render.go
✅ Files skipped from review due to trivial changes (1)
  • api/table_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • rpc/converter.go
  • aichat/models.go

Comment thread aichat/tool_catalog.go
Comment on lines +208 to +232

func applyToolMetadata(entry *ToolCatalogEntry, meta map[string]any) {
if entry == nil || len(meta) == 0 {
return
}
if group, ok := stringMetadata(meta, "group", "toolGroup"); ok && entry.Group == "" {
entry.Group = group
entry.PreferenceKey = group
}
if parent, ok := stringMetadata(meta, "parent", "toolParent"); ok && entry.Parent == "" {
entry.Parent = parent
}
if icon, ok := stringMetadata(meta, "icon", "toolIcon"); ok && entry.Icon == "" {
entry.Icon = icon
}
if permission, ok := stringMetadata(meta, "defaultPermission", "permission", "defaultMode"); ok {
entry.DefaultPermission = defaultPermissionMode(ToolMode(permission))
}
if strict, ok := boolMetadata(meta, "strict"); ok && entry.Strict == nil {
entry.Strict = &strict
}
if nested, ok := meta["com.flanksource.clicky/tool"].(map[string]any); ok {
applyToolMetadata(entry, nested)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

applyToolMetadata inconsistently overrides DefaultPermission while other fields use fill-if-empty.

Group, Parent, Icon, and Strict only overwrite when the entry field is empty/nil, but DefaultPermission is always overwritten when the metadata key exists. This means metadata from a tool definition can override the info-derived DefaultPermission but cannot override other info-derived fields. If this is intentional (metadata is more authoritative for permissions), the other fields should follow the same pattern; if not, DefaultPermission should also check before overwriting.

🐛 Proposed fix for consistent fill-if-empty behavior
 	if permission, ok := stringMetadata(meta, "defaultPermission", "permission", "defaultMode"); ok && entry.DefaultPermission == "" {
 		entry.DefaultPermission = defaultPermissionMode(ToolMode(permission))
 	}

Or, if metadata should always take precedence, remove the empty checks from the other fields for consistency.

📝 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
func applyToolMetadata(entry *ToolCatalogEntry, meta map[string]any) {
if entry == nil || len(meta) == 0 {
return
}
if group, ok := stringMetadata(meta, "group", "toolGroup"); ok && entry.Group == "" {
entry.Group = group
entry.PreferenceKey = group
}
if parent, ok := stringMetadata(meta, "parent", "toolParent"); ok && entry.Parent == "" {
entry.Parent = parent
}
if icon, ok := stringMetadata(meta, "icon", "toolIcon"); ok && entry.Icon == "" {
entry.Icon = icon
}
if permission, ok := stringMetadata(meta, "defaultPermission", "permission", "defaultMode"); ok {
entry.DefaultPermission = defaultPermissionMode(ToolMode(permission))
}
if strict, ok := boolMetadata(meta, "strict"); ok && entry.Strict == nil {
entry.Strict = &strict
}
if nested, ok := meta["com.flanksource.clicky/tool"].(map[string]any); ok {
applyToolMetadata(entry, nested)
}
}
func applyToolMetadata(entry *ToolCatalogEntry, meta map[string]any) {
if entry == nil || len(meta) == 0 {
return
}
if group, ok := stringMetadata(meta, "group", "toolGroup"); ok && entry.Group == "" {
entry.Group = group
entry.PreferenceKey = group
}
if parent, ok := stringMetadata(meta, "parent", "toolParent"); ok && entry.Parent == "" {
entry.Parent = parent
}
if icon, ok := stringMetadata(meta, "icon", "toolIcon"); ok && entry.Icon == "" {
entry.Icon = icon
}
if permission, ok := stringMetadata(meta, "defaultPermission", "permission", "defaultMode"); ok && entry.DefaultPermission == "" {
entry.DefaultPermission = defaultPermissionMode(ToolMode(permission))
}
if strict, ok := boolMetadata(meta, "strict"); ok && entry.Strict == nil {
entry.Strict = &strict
}
if nested, ok := meta["com.flanksource.clicky/tool"].(map[string]any); ok {
applyToolMetadata(entry, nested)
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aichat/tool_catalog.go` around lines 208 - 232, `applyToolMetadata` treats
`DefaultPermission` differently from `Group`, `Parent`, `Icon`, and `Strict`, so
update the function in `tool_catalog.go` to make the overwrite behavior
consistent. Either change `DefaultPermission` to only set when
`entry.DefaultPermission` is still unset, matching the fill-if-empty pattern
used by the other fields, or intentionally make the other fields always
overwrite too if metadata should be authoritative. Use the existing
`stringMetadata`, `boolMetadata`, and `defaultPermissionMode` flow in
`applyToolMetadata` as the place to apply the consistency fix.

Comment thread task/manager_lifecycle.go
Comment on lines +172 to +201
// finishRenderTeardown emits the final frame where needed and restores
// terminal and logger state. Shared by both stopRender paths: after a running
// loop exits (loopWasRunning), and when no loop ever ran — noProgress/CI mode
// prints its final tree here.
func (tm *Manager) finishRenderTeardown(loopWasRunning bool) {
// In interactive mode the stop branch of renderLoop already emitted
// the authoritative final frame (via interactiveRender, which
// ClearLines(lastLines)+writes atomically). Calling renderFinal
// here would append a second copy of the summary below the live
// frame, doubling every summary line. PlainRender-based mode gets its
// closing output from renderFinal: the loop's stop branch already
// flushed every dirty task via PlainRender, so a running loop only
// needs the one-line summary; when no loop ran, nothing has been
// printed yet and the full tree is emitted.
if !tm.noRender.Load() && !tm.isInteractive.Load() {
tm.renderFinal(loopWasRunning)
}
tm.cleanupTerminal()
// On the idle path, only tear down while still idle: a concurrent enqueue
// may have restarted the loop, which still needs its freshly installed
// serializer and TTY ownership (its own stopRender releases them).
tm.mu.Lock()
teardown := loopWasRunning || tm.renderState == renderIdle
if teardown {
tm.uninstallLogSerializer()
}
tm.mu.Unlock()
if teardown {
tm.releaseRenderTerminal()
})
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Race condition: renderFinal can emit after a new loop starts, suppressing the new batch's final output.

In finishRenderTeardown, renderFinal (line 187) is called before the teardown check (line 194). When stopRender takes the renderIdle path, it unlocks tm.mu at line 167 before calling finishRenderTeardown(false). If a concurrent enqueue resets finalRendered and startRenderLoop transitions renderState to renderRunning in that window, renderFinal will emit the full tree and set finalRendered = true. The new loop's eventual stopRenderrenderFinal then sees finalRendered == true and skips, losing the new batch's final summary.

The fix is to guard renderFinal with a renderState == renderRunning check while already holding tm.mu:

🔒 Proposed fix: skip renderFinal when a new loop is already running
 func (tm *Manager) renderFinal(loopRan bool) {
 	if tm.noRender.Load() {
 		return
 	}
 	tm.mu.Lock()
-	if tm.finalRendered || len(tm.tasks) == 0 {
+	if tm.finalRendered || len(tm.tasks) == 0 || tm.renderState == renderRunning {
 		tm.mu.Unlock()
 		return
 	}

This is race-free: startRenderLoop sets renderState = renderRunning under tm.mu, and renderFinal checks it under tm.mu. In the normal stop path, renderState is renderStopping when renderFinal runs, so the check doesn't interfere.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@task/manager_lifecycle.go` around lines 172 - 201, `finishRenderTeardown` has
a race where `renderFinal` can run after a new render loop has already started,
causing the next batch’s final output to be skipped. Update the teardown flow in
`Manager.finishRenderTeardown` to check `renderState` while holding `tm.mu` and
only call `renderFinal` when no new loop is already `renderRunning`; keep the
existing `cleanupTerminal`, `uninstallLogSerializer`, and
`releaseRenderTerminal` logic tied to the locked teardown decision.

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.

1 participant