Skip to content

feat(pitch-deck): add prompt.fail domain landing page#28

Open
aegntic wants to merge 8 commits into
mainfrom
feat/prompt-fail-landing
Open

feat(pitch-deck): add prompt.fail domain landing page#28
aegntic wants to merge 8 commits into
mainfrom
feat/prompt-fail-landing

Conversation

@aegntic

@aegntic aegntic commented Mar 27, 2026

Copy link
Copy Markdown
Owner

Summary

Creative landing page for the prompt.fail domain — a playful "mistake museum" that documents AI prompt failures while promoting ClawPrompt and DevScribe products.

Features

  • Terminal-style intro with bash history of prompt failures
  • Hall of Shame gallery with 6 classic fails:
    • The Roleplay Overflow (identity crisis)
    • The Phantom Library (invented packages)
    • The Format Rebellion (cheese poetry instead of JSON)
    • The Apology Loop (sorry^∞)
    • The Mirror Hallucination (fake error quotes)
    • The Context Amnesia ("what variable?")
  • Product cards for ClawPrompt and DevScribe
  • Humorous stats (∞ failed prompts, 0 shame, 24/7 learning)
  • Design language matching clawREFORM's dark theme with amber/gold accents

Files Added

File Purpose
pitch-deck/prompt.fail.html Main landing page
pitch-deck/prompt.fail-redirect.html Simple redirect for domain routing
pitch-deck/prompt.fail-README.md Deployment documentation

Deployment Options

  • Cloudflare Pages / Vercel / Netlify as standalone
  • Cloudflare Worker redirect to clawreform.com/prompt-fail
  • DNS CNAME to main site

Test Plan

  • Preview HTML in browser locally
  • Verify all links work (ClawPrompt, DevScribe, aegntic.ai)
  • Test mobile responsiveness
  • Set up DNS for prompt.fail domain

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added local auto-import system for discovering and importing skills, MCP servers, and plugins from local directories.
    • Implemented OpenClaw orchestration API endpoints for multi-agent workflow management (registry, dispatch, scheduling, evaluation, repair).
    • Added interactive model selector with preset filtering, tier support, and availability indicators.
    • Introduced dedicated Import page in Settings for streamlined discovery and bulk import workflows.
  • UI/UX Enhancements

    • Redesigned Models catalog with card-based layout, availability badges, and cost display.
    • Added local skills discovery tab with import controls.
    • Added contextual API configuration fix button in chat for auth failures.

@aegntic and others added 8 commits March 13, 2026 04:26
…ation

- Added trace_id field to AgentEntry and Session structs
- Added trace_context.rs module for task-local TraceId storage
- Added openclaw.rs with canonical types for OpenClaw HTTP proxy routes
- Added docker-compose.openclaw.yml for service orchestration
- Added PostgreSQL schema with seed data

- All 279 tests pass
- Build compiles
- Clippy passes with zero warnings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…oof, gitignore updates

- Fix clippy lints: use #[default] derive for GoalStatus, IssueStatus, AgentKind
- Remove redundant Default impls in company.rs and openclaw.rs
- Fix unnecessary clone in trace_context.rs
- Allow clippy::items_after_test_module in tui/mod.rs
- Reformat re-exports in types/lib.rs and route formatting in routes.rs
- Add bun/bunx fallback to dashboard-proof.sh (no longer requires npx)
- Relax dashboard-proof version check to title+agents-panel assertion
- Add .worktrees/, .swarm/, output/demo/ to .gitignore
- Extract is_loopback_request(), is_public_dashboard_path() helpers
- Add loopback-only bypass for Obsidian bootstrap routes (POST /api/agents,
  GET/PUT /api/agents/{id}/files/*) so the local dashboard can seed and
  sync memory files without forcing an auth prompt
- Add tests for bootstrap bypass (loopback allowed, remote rejected,
  unrelated routes rejected)
Environments without local TCP listener support (sandboxed CI, containers)
now skip gracefully instead of failing. Adds the guard macro to all tests
in api_integration_test, daemon_lifecycle_test, load_test, and wire/peer.
OpenClaw integration:
- Add openclaw.rs module proxying to local OpenClaw Docker services
  (registry, dispatcher, scheduler, evaluator, repair)
- Register 13 new /api/openclaw/* routes in server.rs
- Add reqwest + lazy_static dependencies for HTTP proxying

Auto-import system:
- Add auto_import.rs module for scanning local directories to discover
  MCP servers, skills, and plugins
- Register /api/import/scan, /api/import, /api/import/suggestions routes
- Add import.js page (scan UI, item selection, import flow)
- Add model-selector.js page (rich model browsing with tier/provider filters)
- Wire both JS pages into webchat.rs SPA bundle
…PI error fix button

Skills page:
- Rework installed skills to compact list view with inline remove
- Add Local tab for scanning and importing skills from local directories
- Improve empty state with structured onboarding options (ClawHub, Local, Build)

Settings/Models:
- Add model tier filter presets (Frontier, Smart, Fast, Local)
- Add available-only toggle for model catalog
- Add Import tab for settings page

Chat:
- Add 'Fix API Configuration' button on system error messages (401/403/API errors)

Styles:
- Add skill-list, skill-row, model-preset-btn component styles to components.css
- Extend app.js with local skill scanning and import helpers
…sion restore archives

- Add 16 launch task specs (docs/launch-tasks/0-15) defining the parallel
  public alpha launch plan with dependencies, proof rules, and worktree conventions
- Add metallic design baseline (docs/design/launch-metallic-baseline.md)
- Add cli-swarm orchestration scripts (launch.py, launch_live_windows.py,
  verify.sh, proof-demo.sh, status.py) for multi-agent kitty/tmux coordination
- Add session restore snapshot (2026-03-13-launch-alpha) preserving the
  launch alpha planning session state, prompts, and conversation context
- Add cli-swarm overview doc (docs/cli-swarm.md)
Creative landing page for prompt.fail domain featuring:
- Terminal-style intro with bash history of prompt failures
- Hall of Shame gallery with 6 classic AI prompt fails
- Product cards for ClawPrompt and DevScribe
- Humorous stats and dark theme matching clawREFORM design

Includes redirect page and deployment documentation.

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

@sourcery-ai sourcery-ai 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.

Sorry @aegntic, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai

coderabbitai Bot commented Mar 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request introduces comprehensive launch preparation infrastructure for a public alpha release, including backend auto-import functionality for discovering and importing local skills/MCP servers/plugins, OpenClaw service integration endpoints, expanded frontend UI for model selection and import workflows, new type definitions for trace context and OpenClaw ecosystem structures, orchestration scripts for parallel launch task execution, and extensive launch task documentation and session restoration bundles.

Changes

Cohort / File(s) Summary
Auto-Import API
crates/clawreform-api/src/auto_import.rs, crates/clawreform-api/src/routes.rs, crates/clawreform-api/src/server.rs
Added HTTP endpoints (/api/import/scan, /api/import, /api/import/suggestions) for recursively scanning local directories to detect skills (via skill.toml/SKILL.md), MCP servers (via mcp_config.json), and plugins (via package.json), with import handlers that copy skills to the configured directory and stub handlers for MCP/plugins. Includes directory expansion, validation, error collection, and result aggregation.
OpenClaw Service Proxying
crates/clawreform-api/src/openclaw.rs, crates/clawreform-api/src/routes.rs, crates/clawreform-api/src/server.rs, crates/clawreform-api/Cargo.toml
Added HTTP proxy handlers for five OpenClaw upstream services (registry, dispatcher, scheduler, evaluator, repair) with endpoints for agent CRUD, task dispatch, schedule management, artifact evaluation, and repair workflows. Uses shared reqwest client with 30s timeout and forwards JSON bodies/query parameters while handling upstream failures with 502 responses.
OpenClaw Type System
crates/clawreform-types/src/openclaw.rs, crates/clawreform-types/src/lib.rs, crates/clawreform-types/src/agent.rs, crates/clawreform-types/src/company.rs
Introduced comprehensive OpenClaw ecosystem types including task/trace/artifact/event identifiers, agent state enums, lifecycle/dispatch/evaluation records, memory model (tier/visibility), and registry structures. Added trace_id: Option<TraceId> field to AgentEntry and Session. Refactored enum defaults to use derive macro in company.rs.
Trace Context Propagation
crates/clawreform-runtime/src/trace_context.rs, crates/clawreform-runtime/src/lib.rs, crates/clawreform-memory/src/session.rs
Added task-local trace context storage and accessor functions (current_trace_id, set_trace_id, clear_trace_id, new_trace_id) to propagate OpenClaw trace IDs through async task hierarchies. Updated session/agent initialization across kernel and memory to set trace_id: None.
Frontend Model Selector
crates/clawreform-api/static/js/pages/model-selector.js, crates/clawreform-api/static/js/pages/settings.js, crates/clawreform-api/static/css/components.css
New model selector page with multi-filter support (search, provider, tier, availability), preset actions (cheapest, smartest, local), and model cards grid UI. Updated settings page to toggle availability filtering and apply tier/provider/availability sorting. Added comprehensive CSS for model cards, provider groups, badges, and selection states.
Frontend Import UI
crates/clawreform-api/static/js/pages/import.js, crates/clawreform-api/static/index_body.html, crates/clawreform-api/static/css/components.css
New import page for scanning and bulk-importing skills/MCP servers/plugins from discovered or manual directories. Includes suggestion loading, directory scanning, item selection with categorization by type, and per-item/bulk import with result notification and app refresh. Added CSS for import card lists and skill rows.
Frontend Skills & Chat Updates
crates/clawreform-api/static/js/pages/skills.js, crates/clawreform-api/static/index_body.html, crates/clawreform-api/static/js/app.js
Added local skill discovery/import tab to skills page with suggestion-based and fallback directory scanning. Refactored installed skills UI from cards to rows. Updated chat to show "Fix API Configuration" button on auth failures. Enhanced Obsidian readiness flow with ensureObsidianReady() and silent seed option. Added showApiKeyFix() method to app store.
Middleware & Auth
crates/clawreform-api/src/middleware.rs, crates/clawreform-api/src/routes.rs
Extracted request classification helpers (is_loopback_request, is_public_dashboard_path, is_local_obsidian_bootstrap_request) and added loopback-gated Obsidian bootstrap route allowlist. Extended unit tests with request_with_addr helper for ConnectInfo testing.
Test Infrastructure
crates/clawreform-api/tests/api_integration_test.rs, crates/clawreform-api/tests/daemon_lifecycle_test.rs, crates/clawreform-api/tests/load_test.rs, crates/clawreform-wire/src/peer.rs, crates/clawreform-cli/src/tui/mod.rs
Added local_tcp_supported() runtime check and skip_if_no_local_tcp! macro to conditionally skip tests when local TCP binding is unavailable. Applied to integration tests and wire tests to handle restricted sandbox environments. Added clippy lint exception for test module ordering.
OpenClaw Database & Infrastructure
openclaw/openclaw_registry.sql, openclaw/docker-compose.openclaw.yml, openclaw/IMPLEMENTATION_PACK.md
Added complete PostgreSQL schema for agent registry with agent/lifecycle/artifact/dispatch/task/evaluation/memory/schedule/health tables, utility views, and trigger functions. Added Docker Compose stack for multi-service OpenClaw deployment (registry, dispatcher, scheduler, evaluator, repair) with PostgreSQL and Redis infrastructure. Included comprehensive implementation guide with architecture, service responsibilities, schemas, and delivery roadmap.
Launch Orchestration Scripts
scripts/cli-swarm/launch.py, scripts/cli-swarm/launch_live_windows.py, scripts/cli-swarm/status.py, scripts/cli-swarm/verify.sh, scripts/cli-swarm/proof-demo.sh, scripts/dashboard-proof.sh
Added orchestration for parallel launch task execution: launch.py starts detached screen sessions with per-task environment isolation; launch_live_windows.py creates linked git worktrees and visible tmux/kitty windows with real-time output; status.py and verify.sh provide monitoring/verification; proof-demo.sh runs dashboard proof with optional Remotion/TTS rendering. Updated dashboard-proof.sh with improved JSON/base-url handling and title validation.
Launch Task Documentation
docs/launch-tasks/*, docs/session-restore/2026-03-13-launch-alpha/*
Added 15 parallelizable launch task specifications (pre-deployment, audits, onboarding, content, social media, design, control room) with mission/scope/deliverables/prompt chains. Created session restore bundle documenting working state, conversation handoff, constraints, and task execution guidance with per-task prompt files and manifest metadata.
Design & Configuration
docs/design/launch-metallic-baseline.md, pitch-deck/prompt.fail.*, .gitignore
Added metallic design baseline establishing visual thesis and styling rules (matte foundations, two-shadow system, debossed tracks, metallic layers, amber glow). Created prompt.fail landing page with failure gallery and product cards. Updated .gitignore to exclude .worktrees/, output/demo/, .swarm/.
Minor Refactoring
crates/clawreform-api/src/routes.rs, crates/clawreform-api/src/webchat.rs, crates/clawreform-memory/src/structured.rs, crates/clawreform-kernel/src/kernel.rs, crates/clawreform-kernel/src/registry.rs, crates/clawreform-runtime/src/agent_loop.rs, crates/clawreform-runtime/src/compactor.rs
Initialization of new trace_id field across session/agent constructors, simplification of total_budget computation, reformatting of method calls, and test fixture updates to include trace context.

Sequence Diagram(s)

sequenceDiagram
    Client->>clawreform-api: POST /api/import/scan
    clawreform-api->>Filesystem: Scan directory<br/>(validate, expand ~, check exists)
    Filesystem-->>clawreform-api: Directory contents
    clawreform-api->>Filesystem: Search for marker files<br/>(mcp_config.json, skill.toml, package.json)
    Filesystem-->>clawreform-api: Item metadata
    clawreform-api-->>Client: ScanResult { items, errors }
    
    Client->>clawreform-api: POST /api/import (selected items)
    loop For each item by type
        alt Skill
            clawreform-api->>Filesystem: Copy to skills home dir
            clawreform-api->>Memory: Reload skill registry<br/>(write lock)
        else MCP Server / Plugin
            clawreform-api->>clawreform-api: Log import request<br/>(placeholder handler)
        end
    end
    clawreform-api-->>Client: ImportResult<br/>(successes, failures, HTTP 200/206)
Loading
sequenceDiagram
    Client->>clawreform-api: POST /api/openclaw/{service}/*
    clawreform-api->>Upstream Service: Forward request<br/>(POST/GET/PUT with JSON body,<br/>query/path params)
    Upstream Service-->>clawreform-api: HTTP response + body
    alt Success (2xx)
        clawreform-api-->>Client: Upstream status + body
    else Failure or read error
        clawreform-api-->>Client: 502 BAD_GATEWAY<br/>(error JSON)
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly Related PRs

  • PR #6: Overlaps in web UI surface modifications (index_body.html, static CSS, static JS/app.js) for theme/vault/Obsidian onboarding and related component updates.

Poem

🐰 Bushy-tailed with alpha dreams,
Launch tasks spawning in streams,
Traces flow through tasks so quick,
Models sorted, imports stick,
Swarm of windows, all aligned—
Launch is here, design refined! 🚀

✨ 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 feat/prompt-fail-landing

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (32)
crates/clawreform-types/src/openclaw.rs-525-541 (1)

525-541: ⚠️ Potential issue | 🟠 Major

Version DispatchRecord like the other canonical records.

TaskPacket, ArtifactRecord, RegistryRecord, and LifecycleEvent all carry schema_version, but DispatchRecord does not. That makes the dispatch audit payload the odd one out in the canonical schema set and removes the forward-compatibility hook the other records already have.

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

In `@crates/clawreform-types/src/openclaw.rs` around lines 525 - 541,
DispatchRecord is missing the canonical schema_version field used by TaskPacket,
ArtifactRecord, RegistryRecord, and LifecycleEvent; add a pub schema_version:
i32 (or the exact integer type used by the other records) to the DispatchRecord
struct with the same serde/rename conventions and a short doc comment, and
update any constructors, builders, or serialization usages that construct
DispatchRecord so they set the schema_version consistently with the other
canonical records.
crates/clawreform-api/src/webchat.rs-197-200 (1)

197-200: ⚠️ Potential issue | 🟠 Major

Add HTML tab and Alpine handlers for the model-selector page.

The import.js module is properly wired in index_body.html with corresponding tab element and Alpine handlers (lines 5016+), but model-selector.js is missing its HTML UI elements and Alpine modelSelectorPage() data function. Include both the tab element and Alpine binding in index_body.html to complete the integration.

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

In `@crates/clawreform-api/src/webchat.rs` around lines 197 - 200, The
index_body.html is missing the HTML tab element and Alpine binding for the
model-selector page referenced by include_str!(".../model-selector.js"); add a
tab entry (matching the pattern used for import.js) and the corresponding Alpine
data binding that initializes modelSelectorPage() so Alpine can mount the page;
ensure the tab's id/class matches the JS selector in model-selector.js and that
the Alpine data function name modelSelectorPage() is exported/available to the
page just like import.js's handlers.
openclaw/docker-compose.openclaw.yml-17-25 (1)

17-25: ⚠️ Potential issue | 🟠 Major

Don’t publish Postgres/Redis with checked-in credentials.

This stack binds both backing stores to host ports and uses a fixed database password. On any shared machine or loosely firewalled host, that makes the data plane reachable with known creds. Keep these services internal by default and inject the password from env/secrets instead.

🔒 Suggested hardening
   postgres:
     image: postgres:16
     container_name: openclaw-postgres
-    ports:
-      - "5432:5432"
     environment:
       POSTGRES_DB: openclaw
       POSTGRES_USER: openclaw
-      POSTGRES_PASSWORD: openclaw_secret
+      POSTGRES_PASSWORD: ${OPENCLAW_POSTGRES_PASSWORD?err}
+    expose:
+      - "5432"
@@
   redis:
     image: redis:7-alpine
     container_name: openclaw-redis
-    ports:
-      - "6379:6379"
+    expose:
+      - "6379"

Also applies to: 37-38

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

In `@openclaw/docker-compose.openclaw.yml` around lines 17 - 25, The compose
currently publishes Postgres to the host via the "5432:5432" port mapping and
hardcodes POSTGRES_PASSWORD=openclaw_secret in the environment; remove the
host-facing port mapping (replace with an internal-only network port or omit the
ports key) and stop committing the password by switching POSTGRES_PASSWORD to a
secret or environment-injected value (use docker secrets or an env var like
${POSTGRES_PASSWORD} and document setting it externally); apply the same changes
for the Redis service (remove host port mappings referenced around lines 37-38
and use secrets/env injection instead) so both backing stores remain
internal-only and credentials are not checked into source.
crates/clawreform-api/static/js/app.js-518-520 (1)

518-520: ⚠️ Potential issue | 🟠 Major

Don’t auto-create a new seed agent for an existing agent with an empty workspace.

After this regex change, ensureObsidianReady() treats “No memory markdown files” the same as “No agents found” and immediately calls createObsidianSeedAgent(). Opening the Obsidian page for a normal agent can now silently create a brand-new memory-seed agent and switch the graph to it.

🎯 Safer default
     get obsidianCanSeedAgent() {
-      return /No agents found|No memory markdown files/i.test(this.obsidianGraphError || '');
+      return /No agents found/i.test(this.obsidianGraphError || '');
     },

Also applies to: 553-555

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

In `@crates/clawreform-api/static/js/app.js` around lines 518 - 520, The getter
obsidianCanSeedAgent currently treats "No memory markdown files" the same as "No
agents found", causing ensureObsidianReady() to call createObsidianSeedAgent()
for existing agents with empty workspaces; change the logic so
obsidianCanSeedAgent only returns true for the explicit "No agents found" case
(e.g., test /No agents found/i against obsidianGraphError) or add an additional
distinction check in ensureObsidianReady() before calling
createObsidianSeedAgent() so an empty workspace ("No memory markdown files")
does not trigger seed creation; update all related uses (the getter and the call
sites referenced around ensureObsidianReady/lines 553-555) to follow the
stricter condition.
crates/clawreform-api/src/server.rs-305-317 (1)

305-317: 🛠️ Refactor suggestion | 🟠 Major

Route these handlers through routes.rs.

server.rs now binds crate::auto_import::* and crate::openclaw::* directly, which bypasses the API handler boundary this crate uses elsewhere. Please move or re-export these handlers through routes.rs so the router keeps depending on routes::* only.

As per coding guidelines, "New API routes must be registered in server.rs router AND implemented in routes.rs".

Also applies to: 648-703

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

In `@crates/clawreform-api/src/server.rs` around lines 305 - 317, The server.rs
router currently binds handlers directly from crate::auto_import
(scan_directory, import_items, get_suggestions) (and likewise other direct binds
to crate::openclaw::* per the comment), bypassing the intended API boundary;
move or re-export these handlers through routes.rs and update server.rs to
import only routes::*: add functions or pub re-exports in routes.rs (e.g., pub
use crate::auto_import::{scan_directory, import_items, get_suggestions} or
wrapper handlers implemented in routes.rs), then change the .route calls in
server.rs to reference routes::scan_directory, routes::import_items,
routes::get_suggestions (and the openclaw equivalents) so the router depends
only on routes::*. Ensure signatures and middleware expectations match the
original handlers when moving/re-exporting.
crates/clawreform-api/static/js/app.js-256-265 (1)

256-265: ⚠️ Potential issue | 🟠 Major

showApiKeyFix() can't navigate from the global store.

page and navigate() belong to the app() component (defined after Alpine.store), not Alpine.store('app'). This method will throw this.navigate is not a function at runtime when called.

🩹 Minimal fix
     showApiKeyFix() {
       // Reset the API key configuration and show the OpenRouter gate
       this.openRouterKeyInput = '';
       this.openRouterError = '';
       this.openRouterProviderStatus = 'unknown';
       this.showOpenRouterGate = true;
-      // Navigate to wizard if not already there
-      if (Alpine.store('app').page !== 'wizard') {
-        this.navigate('wizard');
-      }
+      if (window.location.hash !== '#wizard') {
+        window.location.hash = 'wizard';
+      }
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/clawreform-api/static/js/app.js` around lines 256 - 265,
showApiKeyFix() is calling this.navigate('wizard') but navigation lives on the
app() component, not the Alpine store, so this will throw; update showApiKeyFix
to invoke navigation on the app component instead (e.g. call
Alpine.store('app').navigate('wizard') or set Alpine.store('app').page =
'wizard') and keep the rest of showApiKeyFix (resetting openRouterKeyInput,
openRouterError, openRouterProviderStatus, showOpenRouterGate) unchanged so the
method uses the app() component's navigate/page API rather than this.navigate.
crates/clawreform-runtime/src/trace_context.rs-14-37 (1)

14-37: 🛠️ Refactor suggestion | 🟠 Major

Expose a scoped entrypoint instead of relying on silent no-op setters.

set_trace_id() and clear_trace_id() only work after someone has entered TRACE_CONTEXT.scope(...); no production code currently establishes this scope, so the setters silently do nothing in production. That makes a missing trace bootstrap indistinguishable from a valid "no trace" state. A small with_trace_context(...) helper would make the correct setup explicit and much harder to miss.

🧭 Example shape
+use std::future::Future;
+
+pub async fn with_trace_context<F, T>(trace_id: Option<TraceId>, future: F) -> T
+where
+    F: Future<Output = T>,
+{
+    TRACE_CONTEXT.scope(RefCell::new(trace_id), future).await
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/clawreform-runtime/src/trace_context.rs` around lines 14 - 37,
set_trace_id and clear_trace_id silently no-op unless TRACE_CONTEXT.scope(...)
has been entered, so production code can miss bootstrapping traces; add a
with_trace_context helper (e.g., with_trace_context<F, R>(trace_id: TraceId, f:
F) -> R where F: FnOnce() -> R) that creates the task-local scope using
TRACE_CONTEXT.scope(RefCell::new(Some(trace_id)), ...) and runs the provided
closure so the context is guaranteed for the duration of the closure; update
callers to use with_trace_context instead of calling set_trace_id/clear_trace_id
directly and ensure the helper returns the closure result and properly drops the
scope afterwards.
crates/clawreform-api/static/js/pages/import.js-58-60 (1)

58-60: ⚠️ Potential issue | 🟠 Major

Move the loading reset to finally.

The data.error early return and the exception path both skip this.scanning = false, which leaves the scan UI stuck in its busy state after a failed request.

⏱️ Suggested loading-state fix
       try {
         var data = await ClawReformAPI.post('/api/import/scan', { directory: dir });
         if (data.error) {
           this.scanError = data.error;
           return;
@@
-        this.scanning = false;
       } catch(e) {
         this.scanError = 'Scan failed: ' + e.message;
         this.showToast('error', 'Scan failed: ' + e.message);
+      } finally {
+        this.scanning = false;
       }

Also applies to: 73-76

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

In `@crates/clawreform-api/static/js/pages/import.js` around lines 58 - 60, The
handler currently returns early when data.error is truthy (setting
this.scanError then return) which leaves this.scanning true on that path and on
exceptions; wrap the async request/response logic in a try/catch/finally (or
move the this.scanning = false into a finally block) so that this.scanning is
always reset; specifically update the block that sets this.scanError and returns
(the code using this.scanError and this.scanning) so it does not return without
reaching the finally, and make the same change for the similar block around
lines 73-76 that also sets this.scanError.
crates/clawreform-api/static/js/pages/import.js-62-71 (1)

62-71: ⚠️ Potential issue | 🟠 Major

Keep the guarded flattening.

The second assignment overwrites the safe || [] normalization. If one collection is omitted in the scan response, discoveredItems can contain undefined entries or fail when mcp_servers is missing.

🧹 Keep the null-safe version
         this.scanResults = data;
         this.discoveredItems = []
           .concat(data.mcp_servers || [])
           .concat(data.skills || [])
           .concat(data.plugins || []);
-
-        // Flatten for UI display
-        this.discoveredItems = this.scanResults.mcp_servers
-          .concat(this.scanResults.skills)
-          .concat(this.scanResults.plugins);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/clawreform-api/static/js/pages/import.js` around lines 62 - 71, The
second assignment to discoveredItems overwrites the null-safe normalization and
can produce undefined entries if any of scanResults.mcp_servers,
scanResults.skills, or scanResults.plugins is missing; update the flattening to
use guarded accesses (e.g., (this.scanResults.mcp_servers || []),
(this.scanResults.skills || []), (this.scanResults.plugins || [])) when
concatenating, or remove the redundant assignment and rely on the initial
null-safe concat that builds this.discoveredItems from
data.mcp_servers/skills/plugins, ensuring discoveredItems is always an array
with no undefined elements.
crates/clawreform-api/static/js/pages/import.js-51-55 (1)

51-55: ⚠️ Potential issue | 🟠 Major

Reset the selection when the scan target changes.

selectedItems survives across scans, so items picked from one directory can still be imported after the user rescans a different path or the next scan errors. If you intentionally keep prior results visible while scanning, at least clear the selection and stale import result here.

♻️ Suggested reset at scan start
 async scanDirectory(dir) {
   this.scanPath = dir;
   this.scanning = true;
   this.scanError = '';
+  this.selectedItems = [];
+  this.importResults = null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/clawreform-api/static/js/pages/import.js` around lines 51 - 55, In
scanDirectory, reset any previous selection and stale import results when
starting a new scan: clear selectedItems (and any related selection state such
as selectedCount or selectedIds) and reset importResult/importError before
setting scanPath/scanning, so selections from a prior directory can’t be
imported after a rescAN or error; update the async scanDirectory method to
explicitly clear these fields at the top of the function.
crates/clawreform-api/static/js/pages/import.js-96-102 (1)

96-102: ⚠️ Potential issue | 🟠 Major

Make selectAll() idempotent.

Because it delegates to toggleSelect(), clicking “Select all” after manually selecting a few items removes those items instead of converging on the full eligible set.

✅ Suggested `selectAll()` behavior
     selectAll() {
-      var self = this;
-      for (var item of this.discoveredItems) {
-        if (item.can_import && !item.already_installed) {
-          self.toggleSelect(item);
-        }
-      }
+      this.selectedItems = this.discoveredItems.filter(function(item) {
+        return item.can_import && !item.already_installed;
+      });
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/clawreform-api/static/js/pages/import.js` around lines 96 - 102,
selectAll currently toggles every eligible item via toggleSelect, so re-running
it can unselect items; update selectAll (which iterates this.discoveredItems) to
only call toggleSelect(item) for items that are eligible (item.can_import &&
!item.already_installed) AND not already selected (e.g., check item.selected or
item.is_selected truthy), making selectAll idempotent and convergent on the full
eligible set while still using the existing toggleSelect helper.
crates/clawreform-api/static/index_body.html-5069-5080 (1)

5069-5080: ⚠️ Potential issue | 🟠 Major

Make these import selections keyboard-operable.

All three lists use clickable div cards with no focus target, role, or Enter/Space handling. Keyboard users cannot select Skills, MCP Servers, or Plugins to import from here.

Suggested pattern
- <div class="card-item" :class="{ selected: isSelected(item) }"
-   `@click`="item.can_import && toggleSelect(item)" :style="item.can_import ? 'cursor:pointer' : 'opacity:0.6'">
+ <button type="button" class="card-item" :class="{ selected: isSelected(item) }"
+   :disabled="!item.can_import" :aria-pressed="isSelected(item)"
+   `@click`="toggleSelect(item)">
    ...
- </div>
+ </button>

Apply the same change to the Skills, MCP Servers, and Plugins lists.

Also applies to: 5089-5100, 5109-5120

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

In `@crates/clawreform-api/static/index_body.html` around lines 5069 - 5080, The
skill-card divs generated by the template (x-for="item in scanResults?.skills")
are not keyboard-operable; update the card element used in the Skills, MCP
Servers, and Plugins lists (same pattern at the other ranges) to be focusable
and announceable by adding tabindex="0", role="button", and appropriate ARIA
state (e.g., aria-disabled when item.can_import is false and aria-pressed when
isSelected(item)), and wire a keydown handler that calls toggleSelect(item) when
Enter or Space is pressed (guarded by item.can_import), while preserving the
existing click handler and visual classes (isSelected, item.can_import,
item.already_installed) so keyboard users can select/unselect items the same as
mouse users.
crates/clawreform-api/static/index_body.html-4086-4087 (1)

4086-4087: ⚠️ Potential issue | 🟠 Major

Move the import suggestions lazy-load to importPage() scope.

The loadSuggestions() call at lines 4086–4087 references state that exists only in the nested importPage() created at line 5016. In Alpine.js, nested scopes cannot be accessed from siblings, so this handler will throw an error when the Import tab is first clicked.

Move the lazy-load logic into the importPage() initialization:

Suggested fix
- <div class="tab tab-secondary" role="tab" :class="{ active: tab === 'import' }"
-   `@click`="tab = 'import'; if (!suggestions.length) loadSuggestions()">Import</div>
+ <div class="tab tab-secondary" role="tab" :class="{ active: tab === 'import' }"
+   `@click`="tab = 'import'">Import</div>
- <div x-show="tab === 'import'" x-data="importPage()">
+ <div x-show="tab === 'import'" x-data="importPage()" x-init="if (!suggestions.length) loadSuggestions()">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/clawreform-api/static/index_body.html` around lines 4086 - 4087, The
click handler on the Import tab currently calls loadSuggestions() from a sibling
scope which will throw because suggestions and loadSuggestions() live inside the
nested importPage() Alpine scope; remove the loadSuggestions() call from the
div's `@click` and instead add the lazy-load check into the importPage()
initializer (inside the importPage() function/method), e.g., when importPage()
initializes or when its internal tab state becomes 'import' check if
suggestions.length is zero and then call loadSuggestions(); update references to
suggestions and loadSuggestions to use the importPage() scope and remove the
stale call from the tab click handler.
crates/clawreform-api/static/js/pages/model-selector.js-186-188 (1)

186-188: ⚠️ Potential issue | 🟠 Major

Propagate clears through onSelect too.

selectModel() notifies the embedding page, but clearSelection() does not. Any parent that mirrors the chosen model via the callback can keep submitting the stale selection after a clear.

🧩 Proposed fix
 clearSelection() {
   this.selectedModelId = '';
+  if (this.onSelect && typeof this.onSelect === 'function') {
+    this.onSelect(null);
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/clawreform-api/static/js/pages/model-selector.js` around lines 186 -
188, clearSelection currently only resets this.selectedModelId and doesn't
notify parents; update clearSelection() to also propagate the cleared state via
the same callback used by selectModel() (call the component's onSelect callback
or emitter with an empty string/null consistent with selectModel) so parents
receive the clear event; reference the clearSelection and selectModel methods
and the onSelect handler to implement the notification using the same payload
shape as selectModel.
crates/clawreform-api/src/openclaw.rs-60-75 (1)

60-75: ⚠️ Potential issue | 🟠 Major

Probe the service health checks concurrently.

With a 30s client timeout, the sequential loop can take up to 150s when the stack is down (5 services × 30s). Replace the for-loop with concurrent probes using tokio::join! or futures::join_all to reduce worst-case latency to ~30s.

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

In `@crates/clawreform-api/src/openclaw.rs` around lines 60 - 75, The for-loop
that probes each service URL sequentially (iterating over service_urls and using
HTTP_CLIENT.get(...).send().await) should be replaced with a concurrent probe
implementation so timeouts run in parallel; collect the probes for each (name,
url) into futures (preserving name and url), run them with
futures::future::join_all or a FuturesUnordered/stream approach, await the
combined future, then populate services.insert(...) from the concurrent results
into the ServiceStatus struct (referencing ServiceStatus, HTTP_CLIENT,
service_urls, and services.insert). Ensure each probe still maps errors to
healthy = false and keeps the same url string in the result.
crates/clawreform-api/src/auto_import.rs-95-170 (1)

95-170: ⚠️ Potential issue | 🟠 Major

Use tokio::task::spawn_blocking() to move filesystem operations off the async path.

Both scan_directory and import_items handlers invoke synchronous, blocking filesystem traversal directly in the async context. scan_recursive() uses std::fs::read_dir() (lines 253–299), and copy_dir_all() uses std::fs::read_dir() and std::fs::copy() (lines 513–527). A large directory scan or copy can pin a Tokio worker for seconds and starve unrelated requests.

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

In `@crates/clawreform-api/src/auto_import.rs` around lines 95 - 170, Both
scan_directory and import_items call blocking filesystem work (perform_scan ->
scan_recursive and import_single_item -> copy_dir_all) directly on the async
runtime; wrap those blocking calls in tokio::task::spawn_blocking and await the
JoinHandle to move IO off the async reactor. Concretely, update scan_directory
to call spawn_blocking(move || perform_scan(&dir, &state)).await and handle the
Result, and update import_items to run the per-item import_single_item calls (or
the whole loop) inside spawn_blocking where import_single_item invokes
copy_dir_all/scan_recursive; propagate and map any spawn errors into the
existing ImportResult/HTTP responses. Ensure you reference perform_scan,
import_single_item, scan_recursive and copy_dir_all when locating the code to
modify.
crates/clawreform-api/src/openclaw.rs-105-114 (1)

105-114: ⚠️ Potential issue | 🟠 Major

Use Json wrapper for error responses instead of plain strings.

Returning (StatusCode, String) causes Axum to respond with text/plain content-type, and the format string format!("{{\"error\": \"{}\"}}", e) generates invalid JSON if error messages contain quotes or newlines. This pattern is repeated across all proxy handlers in the file. Use (StatusCode, Json<ErrorResponse>) with proper JSON serialization instead, or use a JSON helper type to ensure valid, properly-typed responses.

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

In `@crates/clawreform-api/src/openclaw.rs` around lines 105 - 114, The handlers
in openclaw.rs currently return (StatusCode, String) (see the match on
req.send().await) which yields text/plain and unsafe manual JSON formatting;
change the response type to (StatusCode, axum::Json<ErrorResponse>) and convert
both error branches and the success branch to return Json-wrapped values (define
a small ErrorResponse { error: String } and serialize via axum::Json). Replace
all instances of format!("{{\"error\": \"{}\"}}", e) with Json(ErrorResponse {
error: e.to_string() }) and ensure successful responses return Json-deserialized
body or a Json wrapper to keep response types consistent across the proxy
handlers.
docs/session-restore/2026-03-13-launch-alpha/prompts/02-audit-dl-path.md-9-10 (1)

9-10: ⚠️ Potential issue | 🟠 Major

Keep the worktree location consistent with the task brief.

This prompt hardcodes /home/ae/clawreform/.worktrees/02-audit-dl-path, but the task brief it points the agent to declares ../clawreform-launch-02-audit-dl-path. That mismatch can send the restore workflow to the wrong checkout, and the absolute path still only works on one machine.

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

In `@docs/session-restore/2026-03-13-launch-alpha/prompts/02-audit-dl-path.md`
around lines 9 - 10, The prompt hardcodes an absolute worktree path
("/home/ae/clawreform/.worktrees/02-audit-dl-path") which conflicts with the
task brief that expects "../clawreform-launch-02-audit-dl-path" and will break
portability; update the Working directory line in 02-audit-dl-path.md to use the
relative path "../clawreform-launch-02-audit-dl-path" (or a variable placeholder
consistent with the task brief) and ensure the Artifact directory remains a
relative path (e.g., "output/launch/02-audit-dl-path/") so the restore workflow
checks out the correct worktree on any machine.
docs/session-restore/2026-03-13-launch-alpha/prompts/01-pre-site-deployment.md-9-10 (1)

9-10: ⚠️ Potential issue | 🟠 Major

Make the restore prompt path repo-portable.

/home/ae/clawreform/... only works on one machine and bakes a local home-directory layout into a committed restore artifact.

🧭 Suggested fix
-Working directory: /home/ae/clawreform/.worktrees/01-pre-site-deployment
-Artifact directory: output/launch/01-pre-site-deployment/
+Working directory: <repo-root>/.worktrees/01-pre-site-deployment
+Artifact directory: <repo-root>/output/launch/01-pre-site-deployment/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@docs/session-restore/2026-03-13-launch-alpha/prompts/01-pre-site-deployment.md`
around lines 9 - 10, The restore artifact hardcodes an absolute home-directory
path ("Working directory:
/home/ae/clawreform/.worktrees/01-pre-site-deployment") which makes the artifact
non-portable; change the file so the working directory and any paths are
recorded relative to the repository root or use environment/placeholders (e.g.,
${REPO_ROOT} or $HOME) instead of the absolute "/home/ae/..." string, and ensure
"Artifact directory: output/launch/01-pre-site-deployment/" remains relative;
update the lines that contain the literal "Working directory:" and "Artifact
directory:" entries so they emit repo-relative paths (or placeholders) rather
than the absolute path.
docs/design/launch-metallic-baseline.md-5-11 (1)

5-11: ⚠️ Potential issue | 🟠 Major

Replace the machine-local reference pack with repo-hosted assets or stable links.

These /home/ae/... paths and the unlinked “current planning thread” only resolve in one environment, so the baseline cannot actually be reviewed or reused by other contributors from the repo alone.

📎 Suggested fix
 Primary source references:
 
-- `/home/ae/Downloads/1_clawREFORM_design_z-axis.png`
-- `/home/ae/Downloads/2_clawREFORM_design_debossed.png`
-- `/home/ae/Downloads/4_clawREFORM_design_layeringmetals.png`
-- `/home/ae/Downloads/7_clawREFORM_design_lightspill.png`
-- attached concept boards in the current planning thread
+- `docs/design/references/1_clawREFORM_design_z-axis.png`
+- `docs/design/references/2_clawREFORM_design_debossed.png`
+- `docs/design/references/4_clawREFORM_design_layeringmetals.png`
+- `docs/design/references/7_clawREFORM_design_lightspill.png`
+- `docs/design/references/concept-boards.md`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/launch-metallic-baseline.md` around lines 5 - 11, The "Primary
source references" list contains machine-local paths and an unlinked note that
are not portable; replace each `/home/ae/...` entry and the "current planning
thread" mention with repo-hosted assets or stable external URLs and update the
markdown under the "Primary source references" section accordingly (e.g., add
the image files to the repository assets folder such as docs/assets/ and
reference them with relative paths, or point to permanent cloud/drive links),
and ensure filenames in the list match the committed asset names so reviewers
can open them directly from docs/design/launch-metallic-baseline.md.
scripts/dashboard-proof.sh-93-111 (1)

93-111: ⚠️ Potential issue | 🟠 Major

Wait for the agents root instead of asserting on body copy once.

Right after open, the script now reads the title and checks for 'Your Agents' exactly once. That makes the proof fragile on slower hydrations and on harmless copy edits, even though the same page would render correctly a moment later.

⏱️ Suggested fix
+wait_for_dashboard_shell() {
+  local attempts="${1:-20}"
+  local ready=""
+  for ((i = 0; i < attempts; i++)); do
+    ready="$(run_pwcli eval "!!document.querySelector('[x-data=\"agentsPage\"]')" | extract_result || true)"
+    if [[ "${ready}" == "true" ]]; then
+      return 0
+    fi
+    sleep 1
+  done
+  echo "Agents page did not render" >&2
+  exit 1
+}
+
 run_pwcli open "${URL}" >/dev/null
 run_pwcli snapshot >/dev/null
+
+wait_for_dashboard_shell
 
 PAGE_TITLE="$(
   run_pwcli eval "document.title" \
     | extract_result \
     | tr -d '"'
 )
 
 if [[ "${PAGE_TITLE}" != *"clawREFORM"* ]]; then
   echo "Unexpected page title: ${PAGE_TITLE}" >&2
   exit 1
 fi
-
-AGENTS_PANEL_VISIBLE="$(
-  run_pwcli eval "document.body.textContent.includes('Your Agents')" \
-    | extract_result
-)"
-
-if [[ "${AGENTS_PANEL_VISIBLE}" != "true" ]]; then
-  echo "Agents panel did not render" >&2
-  exit 1
-fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/dashboard-proof.sh` around lines 93 - 111, The script currently reads
PAGE_TITLE and then checks AGENTS_PANEL_VISIBLE once via run_pwcli eval, which
is brittle; replace the one-shot AGENTS_PANEL_VISIBLE check with a wait/poll for
the agents root element (e.g. wait for a specific selector or for
document.querySelector('#agents-root') to be non-null) using run_pwcli's waiting
mechanism or a short retry loop with a timeout, then assert presence; update the
block that sets AGENTS_PANEL_VISIBLE (and any immediate exit logic) to perform
the wait/poll before failing so slow hydrations or transient copy differences
don't cause false failures.
docs/session-restore/2026-03-13-launch-alpha/state/kitty-live.session-4-34 (1)

4-34: ⚠️ Potential issue | 🟠 Major

Session restore script still depends on screen, conflicting with restore direction.

Line 4 through Line 34 keep screen -r/-x as the execution path, while the restore checklist explicitly moves away from screen. Keeping this as-is risks restoring the same broken behavior. Please regenerate this session file after launcher patching, or clearly mark it as legacy/diagnostic only.

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

In `@docs/session-restore/2026-03-13-launch-alpha/state/kitty-live.session` around
lines 4 - 34, The session file still uses screen invocations (e.g., the launch
bash -lc 'screen -r ... || screen -x ...' lines used inside each
new_os_window/launch block), which conflicts with the new restore direction away
from screen; update the file by replacing those screen-based launch commands
with the new launcher invocation (regenerate this session after applying the
launcher patch so each launch/ new_os_window block uses the new launcher API),
or if you cannot regenerate now, explicitly mark the file and each launch block
as legacy/diagnostic-only with a clear comment header so restores won't use
these screen commands.
docs/session-restore/2026-03-13-launch-alpha/design/launch-metallic-baseline.md-7-11 (1)

7-11: ⚠️ Potential issue | 🟠 Major

Primary source references are not reproducible for collaborators.

Lines [7]-[11] depend on local files and thread attachments, so reviewers/agents cannot verify the baseline inputs.

💡 Suggested fix
-Primary source references:
-
-- `/home/ae/Downloads/1_clawREFORM_design_z-axis.png`
-- `/home/ae/Downloads/2_clawREFORM_design_debossed.png`
-- `/home/ae/Downloads/4_clawREFORM_design_layeringmetals.png`
-- `/home/ae/Downloads/7_clawREFORM_design_lightspill.png`
-- attached concept boards in the current planning thread
+Primary source references (repo-local):
+
+- `./references/1_clawREFORM_design_z-axis.png`
+- `./references/2_clawREFORM_design_debossed.png`
+- `./references/4_clawREFORM_design_layeringmetals.png`
+- `./references/7_clawREFORM_design_lightspill.png`
+- `./references/concept-boards.md` (exported links/images from planning thread)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@docs/session-restore/2026-03-13-launch-alpha/design/launch-metallic-baseline.md`
around lines 7 - 11, The listed primary source references are local paths and
thread attachments (e.g., files named 1_clawREFORM_design_z-axis.png,
2_clawREFORM_design_debossed.png, 4_clawREFORM_design_layeringmetals.png,
7_clawREFORM_design_lightspill.png) which are not reproducible; fix by adding
those image files to the repository or an accessible asset host, replace the
absolute local paths in launch-metallic-baseline.md with relative repo paths or
stable permalinks, and include brief captions and provenance (uploader, date,
original thread permalink) so reviewers/agents can access and verify the
baseline inputs.
scripts/cli-swarm/status.py-25-36 (1)

25-36: ⚠️ Potential issue | 🟠 Major

Resolve session log paths relative to the manifest location.

Lines [25]-[36] currently resolve session["log_file"] against the process CWD, which can miss logs when running the command outside the swarm directory.

💡 Suggested fix
 def main() -> int:
     args = parse_args()
-    manifest = json.loads(Path(args.manifest).read_text(encoding="utf-8"))
+    manifest_path = Path(args.manifest).resolve()
+    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
@@
-        print(f"log: {session['log_file']}")
-        for line in tail_text(Path(session["log_file"])):
+        raw_log_path = Path(session["log_file"])
+        log_path = raw_log_path if raw_log_path.is_absolute() else (manifest_path.parent / raw_log_path)
+        print(f"log: {log_path}")
+        for line in tail_text(log_path):
             print(f"  {line}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/cli-swarm/status.py` around lines 25 - 36, The session log paths are
currently resolved against the CWD, so change how session["log_file"] is built:
compute manifest_dir = Path(args.manifest).parent and for each session convert
log_path = Path(session["log_file"]); if not log_path.is_absolute(), set
log_path = manifest_dir / log_path; then use log_path when printing and when
calling tail_text (references: parse_args, manifest, session["log_file"],
tail_text). Ensure you still read manifest with
json.loads(Path(args.manifest).read_text(...)) and preserve existing prints and
iteration over manifest["sessions"].
docs/session-restore/2026-03-13-launch-alpha/state/launch_live_windows.py.snapshot-381-389 (1)

381-389: ⚠️ Potential issue | 🟠 Major

Make --open-windows-only load existing state instead of re-preparing tasks.

This path still runs ensure_worktree() and sync_launch_inputs() before opening Kitty. That means a reattach-only run can rewrite worktree state and even fail on missing CLI binaries, which defeats the purpose of --open-windows-only.

Suggested fix
-    selected_tasks = resolve_tasks(args)
     run_root = workspace / ".swarm" / args.name
     run_root.mkdir(parents=True, exist_ok=True)
-
-    selected: list[dict[str, str]] = []
-    for task in selected_tasks:
-        worktree = ensure_worktree(workspace, worktree_root, task)
-        data = sync_launch_inputs(workspace, worktree, task, args.name)
-        selected.append(data)
-
-    session_file = build_kitty_session(run_root, selected)
-    manifest_path = write_manifest(run_root, selected, session_file)
+    if args.open_windows_only:
+        manifest_path = run_root / "visible-launch-manifest.json"
+        manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
+        selected = manifest["tasks"]
+        session_file = pathlib.Path(manifest["session_file"])
+    else:
+        selected_tasks = resolve_tasks(args)
+        selected: list[dict[str, str]] = []
+        for task in selected_tasks:
+            worktree = ensure_worktree(workspace, worktree_root, task)
+            data = sync_launch_inputs(workspace, worktree, task, args.name)
+            selected.append(data)
+        session_file = build_kitty_session(run_root, selected)
+        manifest_path = write_manifest(run_root, selected, session_file)

Also applies to: 411-416

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

In
`@docs/session-restore/2026-03-13-launch-alpha/state/launch_live_windows.py.snapshot`
around lines 381 - 389, The loop currently always calls ensure_worktree() and
sync_launch_inputs() which re-prepares tasks; when args.open_windows_only is
set, skip those calls and instead load the previously saved run state from
run_root (e.g. via a load_run_state(run_root) helper that returns the selected
list or per-task data) so we only open existing windows without touching
worktrees or requiring CLIs; update the block that builds selected (the loop
over selected_tasks using ensure_worktree and sync_launch_inputs) to branch on
args.open_windows_only, and apply the same change to the other identical block
around the 411-416 region.
docs/session-restore/2026-03-13-launch-alpha/launch-tasks/15_clawREFORM_live-window-operator-sheet.md-69-70 (1)

69-70: ⚠️ Potential issue | 🟠 Major

The flag and auth guidance does not match the launcher.

launch_live_windows.py.snapshot does not define --isolate-cli-state, and it always rewrites HOME/XDG into .swarm/... state roots. This note currently tells operators to rely on a flag that does not exist and on local logins that are not reused by default.

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

In
`@docs/session-restore/2026-03-13-launch-alpha/launch-tasks/15_clawREFORM_live-window-operator-sheet.md`
around lines 69 - 70, The docs text incorrectly says task windows reuse local
CLI logins and references a non-existent flag (--isolate-cli-state); update the
note to match the launcher (launch_live_windows.py.snapshot) behavior by
removing the flag mention and stating explicitly that the launcher always
rewrites HOME/XDG into .swarm state roots so local CLI auth/config is not reused
by default, and if there is an alternative way to opt into reusing local state
call it out (or state there is no such option). Ensure you reference
launch_live_windows.py.snapshot and the --isolate-cli-state flag name in the
updated copy so operators see the mismatch is resolved.
scripts/cli-swarm/verify.sh-52-57 (1)

52-57: ⚠️ Potential issue | 🟠 Major

Don't suppress a requested proof-stage failure.

Once DASHBOARD_PROOF_URL is set, a failing dashboard-proof.sh still leaves this script green. Any wrapper or CI job using the exit code will treat verification as passed even though the report says proof: fail.

Suggested fix
 if [[ -n "${DASHBOARD_PROOF_URL:-}" ]]; then
-  if run_stage proof bash scripts/dashboard-proof.sh "${DASHBOARD_PROOF_URL}"; then
-    :
-  else
-    true
-  fi
+  run_stage proof bash scripts/dashboard-proof.sh "${DASHBOARD_PROOF_URL}"
 else
   printf -- "- proof: skipped (set DASHBOARD_PROOF_URL)\n" >> "${REPORT}"
 fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/cli-swarm/verify.sh` around lines 52 - 57, The current conditional
around DASHBOARD_PROOF_URL runs run_stage proof bash scripts/dashboard-proof.sh
and suppresses its failure (the else branch does nothing/use true), which makes
the script exit success even when the proof fails; update the conditional so
that if run_stage (invoked with run_stage proof bash scripts/dashboard-proof.sh)
returns non-zero the script also exits non-zero (propagate or explicitly exit
with the run_stage status) instead of swallowing the error, preserving the
original DASHBOARD_PROOF_URL guard and invocation.
scripts/cli-swarm/launch.py-298-308 (1)

298-308: ⚠️ Potential issue | 🟠 Major

--coordinator is currently ignored.

For coordinator specs, tool_name is hard-coded to pi, so non-default values still build a pi command and can even fail on tool_binary("pi"). That also leaves the manifest claiming one coordinator while another actually launches. Either honor the flag end-to-end or remove it.

Also applies to: 410-425

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

In `@scripts/cli-swarm/launch.py` around lines 298 - 308, The coordinator branch
currently forces tool_name = "pi" which ignores any provided --coordinator/tool
flag and can call the wrong binary; change the logic so tool_name uses the
provided tool value for coordinators (e.g., tool_name = tool if tool is set else
"pi"), keep slug = f"{name}-{role}-{index:02d}-{tool_name}", use the same
tool_name when looking up DEFAULT_MODELS and when constructing the command via
base_command, and ensure tool_binary (or wherever the binary is resolved) is
invoked for that tool_name; apply the same fix to the other coordinator-handling
block (lines ~410-425) so manifest, prompt_file creation, model selection, and
command construction all honor the requested tool rather than hard-coding "pi".
scripts/cli-swarm/launch.py-165-200 (1)

165-200: ⚠️ Potential issue | 🟠 Major

Isolate CLI homes per session, not per tool.

home_dir = root / "homes" / tool gives every worker using the same CLI the same HOME, XDG_*, and tool-specific state dirs. Since distribute() intentionally reuses tools, same-tool workers can overwrite one another's local state. Derive the home root from the unique session slug/state dir instead.

One simple way to scope it per session
 def local_env_exports(root: pathlib.Path, tool: str, session_dir: pathlib.Path) -> dict[str, str]:
-    home_dir = root / "homes" / tool
+    home_dir = root / "homes" / f"{tool}-{session_dir.parent.name}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/cli-swarm/launch.py` around lines 165 - 200, In local_env_exports,
the HOME/XDG dirs are currently shared per tool (home_dir = root / "homes" /
tool) causing same-tool workers to clobber each other; change the home root to
be derived from the unique session slug (e.g. home_dir = root / "homes" /
session_dir.name) so each session gets its own HOME/XDG directories (optionally
you can nest tool under that session: root / "homes" / session_dir.name / tool
if you still want per-tool subfolders inside the session), then leave the rest
of the function (directory creation and env keys) unchanged.
scripts/cli-swarm/launch_live_windows.py-197-200 (1)

197-200: ⚠️ Potential issue | 🟠 Major

Add graceful fallbacks for missing or malformed dconf data.

The gnome_terminal_profile() function at line 214 calls dconf unconditionally without checking if the tool exists, and parse_dconf_string() at line 197 can raise SyntaxError or ValueError on unexpected dump values. These are called unconditionally via write_kitty_profile_config() at line 640, turning a cosmetic feature into a hard launcher failure on systems without dconf or with malformed profile data.

Wrap ast.literal_eval() in a try-except block to return None on parse errors, and check shutil.which("dconf") at the start of gnome_terminal_profile() to return None gracefully when the tool is unavailable.

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

In `@scripts/cli-swarm/launch_live_windows.py` around lines 197 - 200, Update
parse_dconf_string to handle malformed input by wrapping the ast.literal_eval
call in a try/except that catches SyntaxError and ValueError and returns None on
failure; also update gnome_terminal_profile to check for the presence of the
dconf binary using shutil.which("dconf") at the start and return None
immediately if not found so callers like write_kitty_profile_config don't treat
missing dconf as a hard failure. Ensure the changes reference the existing
functions parse_dconf_string and gnome_terminal_profile and preserve existing
return types (str | None).
scripts/cli-swarm/launch.py-313-313 (1)

313-313: ⚠️ Potential issue | 🟠 Major

Defer screen requirement to launch-only path.

require_screen() is called unconditionally during spec creation (line 313), which happens at lines 410–412 in main() before the args.launch guard at line 443. This causes preparation-only runs (--launch=false) to fail on hosts without screen even though the binary is only needed for actual session launch. Either defer the binary check to the args.launch path, or store the string "screen" symbolically in the SessionSpec and resolve it only when launching.

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

In `@scripts/cli-swarm/launch.py` at line 313, The code calls require_screen()
while building the launch command (screen_command) during spec creation, causing
preparation-only runs to fail; change the flow so require_screen() is invoked
only in the launch path (guarded by args.launch) or store a symbolic "screen"
token in SessionSpec and resolve it inside the launch_session logic: move the
require_screen() call out of the spec construction that runs in main() and into
the branch that handles args.launch (or update SessionSpec to carry a
screen_marker and replace it with require_screen() when actually launching) so
hosts without screen can run preparation-only without error.
scripts/cli-swarm/launch_live_windows.py-457-457 (1)

457-457: ⚠️ Potential issue | 🟠 Major

Create the log directory in Python before starting the tmux session.

A race condition exists between when tmux pipe-pane attaches at line 535 and when the bash script creates the parent directory for the log file at line 457. Since tmux new-session -d returns immediately without waiting for the spawned bash script to execute, pipe-pane can attempt to write to the log file before the parent directory is created, causing the command to fail and the entire session creation to abort.

The fix is to create log_file.parent in Python at the start of ensure_tmux_session before starting tmux:

Minimal fix
def ensure_tmux_session(item: dict[str, str]) -> bool:
    tmux_binary = require_binary("tmux")
+    pathlib.Path(item["log_file"]).parent.mkdir(parents=True, exist_ok=True)
    tmux_session = item["tmux_session"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/cli-swarm/launch_live_windows.py` at line 457, A race condition can
occur because the parent directory for log_file is created in the shell script
instead of before tmux is launched in ensure_tmux_session, so pipe-pane may try
to write to a nonexistent path; fix it by ensuring log_file.parent (and any
other required dirs like status_file.parent and artifact_dir) are created in
Python at the start of ensure_tmux_session (use Path(...).mkdir(parents=True,
exist_ok=True)) before constructing the tmux new-session and pipe-pane commands
so tmux won't fail when attaching output.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5efef533-f8e4-4ffd-8eed-474a4cc1219e

📥 Commits

Reviewing files that changed from the base of the PR and between 85697b9 and 407997e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (96)
  • .gitignore
  • crates/clawreform-api/Cargo.toml
  • crates/clawreform-api/src/auto_import.rs
  • crates/clawreform-api/src/lib.rs
  • crates/clawreform-api/src/middleware.rs
  • crates/clawreform-api/src/openclaw.rs
  • crates/clawreform-api/src/routes.rs
  • crates/clawreform-api/src/server.rs
  • crates/clawreform-api/src/webchat.rs
  • crates/clawreform-api/static/css/components.css
  • crates/clawreform-api/static/index_body.html
  • crates/clawreform-api/static/js/app.js
  • crates/clawreform-api/static/js/pages/import.js
  • crates/clawreform-api/static/js/pages/model-selector.js
  • crates/clawreform-api/static/js/pages/settings.js
  • crates/clawreform-api/static/js/pages/skills.js
  • crates/clawreform-api/tests/api_integration_test.rs
  • crates/clawreform-api/tests/daemon_lifecycle_test.rs
  • crates/clawreform-api/tests/load_test.rs
  • crates/clawreform-cli/src/tui/mod.rs
  • crates/clawreform-kernel/src/kernel.rs
  • crates/clawreform-kernel/src/registry.rs
  • crates/clawreform-memory/src/session.rs
  • crates/clawreform-memory/src/structured.rs
  • crates/clawreform-runtime/src/agent_loop.rs
  • crates/clawreform-runtime/src/compactor.rs
  • crates/clawreform-runtime/src/lib.rs
  • crates/clawreform-runtime/src/trace_context.rs
  • crates/clawreform-types/src/agent.rs
  • crates/clawreform-types/src/company.rs
  • crates/clawreform-types/src/lib.rs
  • crates/clawreform-types/src/openclaw.rs
  • crates/clawreform-wire/src/peer.rs
  • docs/cli-swarm.md
  • docs/design/launch-metallic-baseline.md
  • docs/launch-tasks/0_clawREFORM_launch-commander.md
  • docs/launch-tasks/10_clawREFORM_content-creator.md
  • docs/launch-tasks/11_clawREFORM_content-distributor.md
  • docs/launch-tasks/12_clawREFORM_email-list-super-sherlock.md
  • docs/launch-tasks/13_clawREFORM_launch-control-room.md
  • docs/launch-tasks/14_clawREFORM_design-system-translation.md
  • docs/launch-tasks/15_clawREFORM_live-window-operator-sheet.md
  • docs/launch-tasks/1_clawREFORM_pre-site-deployment.md
  • docs/launch-tasks/2_clawREFORM_audit_dl-path.md
  • docs/launch-tasks/3_clawREFORM_release-packaging-proof.md
  • docs/launch-tasks/4_clawREFORM_onboarding-first-run-smoke.md
  • docs/launch-tasks/5_clawREFORM_parallel-proof-screenshots.md
  • docs/launch-tasks/6_clawREFORM_public-alpha-go-live.md
  • docs/launch-tasks/7_clawREFORM_twitter-x-launch-chain.md
  • docs/launch-tasks/8_clawREFORM_youtube-launch-automation.md
  • docs/launch-tasks/9_clawREFORM_reddit-medium-quora-chain.md
  • docs/session-restore/2026-03-13-launch-alpha/README.md
  • docs/session-restore/2026-03-13-launch-alpha/constraints-summary.md
  • docs/session-restore/2026-03-13-launch-alpha/conversation.md
  • docs/session-restore/2026-03-13-launch-alpha/design/launch-metallic-baseline.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/0_clawREFORM_launch-commander.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/10_clawREFORM_content-creator.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/11_clawREFORM_content-distributor.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/12_clawREFORM_email-list-super-sherlock.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/13_clawREFORM_launch-control-room.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/14_clawREFORM_design-system-translation.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/15_clawREFORM_live-window-operator-sheet.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/1_clawREFORM_pre-site-deployment.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/2_clawREFORM_audit_dl-path.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/3_clawREFORM_release-packaging-proof.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/4_clawREFORM_onboarding-first-run-smoke.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/5_clawREFORM_parallel-proof-screenshots.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/6_clawREFORM_public-alpha-go-live.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/7_clawREFORM_twitter-x-launch-chain.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/8_clawREFORM_youtube-launch-automation.md
  • docs/session-restore/2026-03-13-launch-alpha/launch-tasks/9_clawREFORM_reddit-medium-quora-chain.md
  • docs/session-restore/2026-03-13-launch-alpha/non-sandbox-delta.md
  • docs/session-restore/2026-03-13-launch-alpha/prompts/01-pre-site-deployment.md
  • docs/session-restore/2026-03-13-launch-alpha/prompts/02-audit-dl-path.md
  • docs/session-restore/2026-03-13-launch-alpha/prompts/04-onboarding-first-run-smoke.md
  • docs/session-restore/2026-03-13-launch-alpha/prompts/05-parallel-proof-screenshots.md
  • docs/session-restore/2026-03-13-launch-alpha/prompts/10-content-creator.md
  • docs/session-restore/2026-03-13-launch-alpha/prompts/13-launch-control-room.md
  • docs/session-restore/2026-03-13-launch-alpha/prompts/14-design-system-translation.md
  • docs/session-restore/2026-03-13-launch-alpha/restore-checklist.md
  • docs/session-restore/2026-03-13-launch-alpha/state/kitty-live.session
  • docs/session-restore/2026-03-13-launch-alpha/state/launch_live_windows.py.snapshot
  • docs/session-restore/2026-03-13-launch-alpha/state/visible-launch-manifest.json
  • docs/session-restore/2026-03-13-launch-alpha/working-state.md
  • openclaw/IMPLEMENTATION_PACK.md
  • openclaw/docker-compose.openclaw.yml
  • openclaw/openclaw_registry.sql
  • pitch-deck/prompt.fail-README.md
  • pitch-deck/prompt.fail-redirect.html
  • pitch-deck/prompt.fail.html
  • scripts/cli-swarm/launch.py
  • scripts/cli-swarm/launch_live_windows.py
  • scripts/cli-swarm/proof-demo.sh
  • scripts/cli-swarm/status.py
  • scripts/cli-swarm/verify.sh
  • scripts/dashboard-proof.sh

Comment on lines +263 to +295
if path.is_dir() {
let mcp_config = path.join("mcp_config.json");
let skill_toml = path.join("skill.toml");
let skill_md = path.join("SKILL.md");
let package_json = path.join("package.json");

if mcp_config.exists() {
if let Some(item) = parse_mcp_server(&path, &mcp_config, configured_mcp) {
result.mcp_servers.push(item);
}
continue;
}

if skill_toml.exists() || skill_md.exists() {
let manifest_path = if skill_toml.exists() {
skill_toml
} else {
skill_md
};
if let Some(item) = parse_skill(&path, &manifest_path, installed_skills) {
result.skills.push(item);
}
continue;
}

if package_json.exists() {
if let Some(item) = parse_plugin(&path, &package_json) {
result.plugins.push(item);
}
continue;
}

scan_recursive(&path, result, installed_skills, configured_mcp)?;

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let me locate and read the file
fd -t f "auto_import.rs" | head -5

Repository: aegntic/clawreform

Length of output: 102


🏁 Script executed:

# Read the specific lines mentioned in the review
sed -n '260,300p' crates/clawreform-api/src/auto_import.rs

Repository: aegntic/clawreform

Length of output: 1355


🏁 Script executed:

# Get a broader view of the scan_recursive function to understand the full context
sed -n '240,320p' crates/clawreform-api/src/auto_import.rs

Repository: aegntic/clawreform

Length of output: 2567


🏁 Script executed:

# Search for any symlink-related handling or visited set tracking in the file
rg -i "symlink|visited|seen" crates/clawreform-api/src/auto_import.rs -B 2 -A 2

Repository: aegntic/clawreform

Length of output: 44


🏁 Script executed:

# Search for other uses of read_dir or directory traversal
rg "read_dir|walk|WalkDir" crates/clawreform-api/ -l

Repository: aegntic/clawreform

Length of output: 291


🏁 Script executed:

# Check if there are any uses of symlink checking methods
rg "is_symlink|read_link|canonicalize" crates/clawreform-api/

Repository: aegntic/clawreform

Length of output: 801


🏁 Script executed:

# Get the full scan function to understand entry source
sed -n '240,265p' crates/clawreform-api/src/auto_import.rs

Repository: aegntic/clawreform

Length of output: 826


🏁 Script executed:

# Check if there's an entry.file_type() call that could prevent following symlinks
rg "file_type|DirEntry" crates/clawreform-api/src/auto_import.rs -A 2 -B 2

Repository: aegntic/clawreform

Length of output: 258


Guard the recursive walk against symlink loops.

path.is_dir() follows symlinks, so a directory structure with symlink cycles (e.g., a/b -> ..) will recurse infinitely, causing stack overflow or hanging the scan. Use entry.file_type()?.is_dir() instead, which doesn't follow symlinks, or track visited inodes to prevent cycles.

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

In `@crates/clawreform-api/src/auto_import.rs` around lines 263 - 295, The
directory check in the recursive scanner uses path.is_dir(), which follows
symlinks and can cause infinite recursion on symlink cycles; change the check to
use the DirEntry's file_type (e.g., entry.file_type()?.is_dir()) where entries
are iterated, or alternatively track visited inodes by storing (dev, ino) from
metadata() in a HashSet and skip already-seen directories before calling
scan_recursive(&path, ...); update the logic around parse_mcp_server,
parse_skill, parse_plugin and the recursive call so the recursion only occurs
for non-symlink directories or unvisited inodes (use the scan_recursive
signature to accept the visited set if you choose the inode-tracking approach).

Comment on lines +430 to +452
fn import_single_item(item: &ImportItem, state: &AppState) -> Result<ImportedItem, String> {
match item.item_type.as_str() {
"skill" => import_skill(item, state),
"mcp_server" => import_mcp_server(item, state),
"plugin" => import_plugin(item, state),
_ => Err(format!("Unknown item type: {}", item.item_type)),
}
}

fn import_skill(item: &ImportItem, state: &AppState) -> Result<ImportedItem, String> {
let source_path = PathBuf::from(&item.path);
let name = item.name.clone().unwrap_or_else(|| {
source_path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "imported-skill".to_string())
});

let skills_dir = state.kernel.config.home_dir.join("skills");
std::fs::create_dir_all(&skills_dir)
.map_err(|e| format!("Failed to create skills directory: {}", e))?;

let dest_path = skills_dir.join(&name);

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 | 🔴 Critical

Treat ImportItem.path and ImportItem.name as untrusted input.

Right now a client can skip the scan flow, post any server-side path as a "skill", and even use name with .., /, \, or an absolute path to escape skills_dir. Re-validate that the source still looks like a real skill, reject path separators/absolute names, and canonicalize the destination before copying.

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

In `@crates/clawreform-api/src/auto_import.rs` around lines 430 - 452, Validate
and sanitize untrusted ImportItem.path and ImportItem.name in import_skill:
reject absolute paths and any names containing path separators or parent
components ("..", "/", "\\") by inspecting Path components of source_path and
name, ensure source_path exists and contains expected skill markers (e.g.,
manifest file) before importing, canonicalize both source_path and the computed
dest_path and verify the canonicalized dest_path is a descendant of skills_dir,
and return an Err when checks fail; also normalize/sanitize name (allow only
safe chars like alphanumerics, '-', '_') before joining to skills_dir to prevent
directory traversal and enforce that import_skill, skills_dir, dest_path, and
ImportItem.path/name are used in these checks.

Comment on lines +54 to +80
fn is_public_dashboard_path(path: &str) -> bool {
path == "/"
|| path == "/api/health"
|| path == "/api/health/detail"
|| path == "/api/status"
|| path == "/api/version"
|| path == "/api/agents"
|| path == "/api/profiles"
|| path == "/api/config"
|| path.starts_with("/api/uploads/")
|| path == "/api/models"
|| path == "/api/models/aliases"
|| path == "/api/providers"
|| path == "/api/budget"
|| path == "/api/budget/agents"
|| path.starts_with("/api/budget/agents/")
|| path == "/api/network/status"
|| path == "/api/a2a/agents"
|| path == "/api/approvals"
|| path == "/api/channels"
|| path == "/api/skills"
|| path == "/api/sessions"
|| path == "/api/integrations"
|| path == "/api/integrations/available"
|| path == "/api/integrations/health"
|| path.starts_with("/api/cron/")
}

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 | 🔴 Critical

Make the public allowlist method-aware.

POST /api/agents still bypasses auth for remote callers because /api/agents matches is_public_dashboard_path() before the loopback-only bootstrap rule is checked. That leaves agent creation unauthenticated even when an API key is configured. Restrict the dashboard allowlist to safe methods, or remove /api/agents from it and let is_local_obsidian_bootstrap_request() own the local POST case.

Also applies to: 135-140

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

In `@crates/clawreform-api/src/middleware.rs` around lines 54 - 80, The public
allowlist currently ignores HTTP method which permits unsafe POSTs (e.g. POST
/api/agents) to bypass auth; update is_public_dashboard_path to be method-aware
(change signature to accept method: &str or &Method) and only return true for
safe methods (e.g. "GET" and "HEAD") for the listed endpoints, or remove
"/api/agents" and any POST-sensitive routes (like "/api/budget/agents" and
similar entries around the other occurrence at lines 135-140) from the allowlist
and let is_local_obsidian_bootstrap_request() handle loopback-only POST
bootstrap requests; ensure you update all call sites to pass the method and
adjust tests accordingly.

Comment on lines +219 to +227
CREATE INDEX idx_lifecycle_agent ON lifecycle_events(agent_id);
CREATE INDEX idx_lifecycle_created ON lifecycle_events(created_at DESCENDING);

-- Artifact lookups
CREATE INDEX idx_artifacts_trace ON artifacts(trace_id);
CREATE INDEX idx_artifacts_owner ON artifacts(owner_agent);
CREATE INDEX idx_artifacts_project ON artifacts(project);
CREATE INDEX idx_artifacts_type ON artifacts(artifact_type);
CREATE INDEX idx_artifacts_created ON artifacts(created_at DESCENDING);

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 | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

What is the PostgreSQL CREATE INDEX syntax for descending index columns? Is DESCENDINGvalid, or must it beDESC?

💡 Result:

The PostgreSQL CREATE INDEX syntax for descending index columns uses DESC. DESCENDING is not valid. Syntax example: CREATE INDEX index_name ON table_name (column_name DESC); Official PostgreSQL documentation (version 18, current as of 2026) specifies [ASC | DESC] [NULLS {FIRST | LAST}] for column sort order, with DESC for descending. ASC is the default. No mention of DESCENDING in syntax or examples across docs or guides.

Citations:


🏁 Script executed:

find . -name "openclaw_registry.sql" -type f

Repository: aegntic/clawreform

Length of output: 94


🏁 Script executed:

sed -n '219,233p' ./openclaw/openclaw_registry.sql

Repository: aegntic/clawreform

Length of output: 825


Replace DESCENDING with DESC in these index definitions.

PostgreSQL does not accept DESCENDING inside CREATE INDEX, so this schema load fails before the later indexes, views, and seed data are created.

🛠️ Minimal syntax fix
-CREATE INDEX idx_lifecycle_created ON lifecycle_events(created_at DESCENDING);
+CREATE INDEX idx_lifecycle_created ON lifecycle_events(created_at DESC);

-CREATE INDEX idx_artifacts_created ON artifacts(created_at DESCENDING);
+CREATE INDEX idx_artifacts_created ON artifacts(created_at DESC);

-CREATE INDEX idx_dispatch_created ON dispatch_records(created_at DESCENDING);
+CREATE INDEX idx_dispatch_created ON dispatch_records(created_at DESC);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openclaw/openclaw_registry.sql` around lines 219 - 227, The CREATE INDEX
statements using DESCENDING are invalid for PostgreSQL; locate the index
definitions for idx_lifecycle_created (lifecycle_events(created_at DESCENDING))
and idx_artifacts_created (artifacts(created_at DESCENDING)) and change the sort
specifier from DESCENDING to DESC so the indexes become valid (e.g., use
created_at DESC); leave other index names (idx_lifecycle_agent,
idx_artifacts_trace, idx_artifacts_owner, idx_artifacts_project,
idx_artifacts_type) unchanged.

Comment on lines +52 to +56
```
prompt.fail. A 76.76.21.21 (Vercel)
# or
prompt.fail. CNAME clawreform.com
```

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 | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

For apex/root domains, is a standard DNS CNAME record allowed, and how do Cloudflare, Vercel, and Netlify recommend configuring apex redirects/hosting?

💡 Result:

No, standard DNS CNAME records are not allowed at apex/root domains. DNS standards (RFC 1034, RFC 2181) prohibit CNAME at the zone apex because it must coexist with mandatory SOA and NS records, and CNAME cannot coexist with other record types. Cloudflare recommends CNAME records at apex using their proprietary CNAME flattening (returns A record to clients). For hosting, use proxied A/AAAA/CNAME (@) to origin or Cloudflare services. For redirects, use Redirect Rules or Bulk Redirects (proxied A record to 192.0.2.1, then redirect). Vercel recommends A record at apex to their anycast IP 76.76.21.21 (external DNS). CNAME for subdomains (e.g., www to cname.vercel-dns.com). With Vercel nameservers, automatic handling. Vercel handles www redirects. Netlify recommends A record at apex to their load balancer IP (75.2.60.5 or 104.198.14.52). CNAME for www to [site].netlify.app. Use Netlify DNS for easier management including apex. Redirects via _redirects file or netlify.toml.

Citations:


Fix apex DNS configuration: CNAME records cannot be used at domain root.

Line 55 shows prompt.fail. CNAME clawreform.com, which is invalid DNS. RFC 1034/RFC 2181 prohibit CNAME records at the apex because they cannot coexist with mandatory SOA and NS records. The correct configuration for all three platforms is:

  • Vercel: A record 76.76.21.21 at apex (CNAME only for subdomains like www)
  • Cloudflare: A record at apex (optionally uses proprietary CNAME flattening if via Cloudflare DNS)
  • Netlify: A record at apex (CNAME only for www subdomain)

Update the example to show A record guidance instead; the current CNAME example will not resolve.

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

In `@pitch-deck/prompt.fail-README.md` around lines 52 - 56, Replace the invalid
apex CNAME example (the line `prompt.fail.    CNAME   clawreform.com`) with an
A-record instruction and platform-specific guidance: keep the A example
`prompt.fail.    A       76.76.21.21` for Vercel, and note that Cloudflare and
Netlify require an A record at the apex (Cloudflare may use CNAME flattening
internally) while CNAMEs are only valid for subdomains like `www`; update the
README text around those examples to explicitly state "use A record at apex;
CNAME only for www/subdomains" for Vercel, Cloudflare, and Netlify.

Comment on lines +315 to +325
forwarded = [
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"GEMINI_API_KEY",
"GOOGLE_API_KEY",
"OPENROUTER_API_KEY",
"OPENCODE_API_KEY",
]
for key in forwarded:
if key in os.environ:
env[key] = os.environ[key]

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 | 🔴 Critical

Do not write forwarded credentials into *-start.sh or the manifest.

local_env_exports() forwards provider keys, render_agent_shell_command() inlines them, and Line 473 drops that string straight into the generated starter script. Lines 594-620 then persist the same string as agent_shell_command, so live credentials end up under .swarm/ inside the repo. Pass the env to tmux/subprocess at session creation time and keep only the non-secret agent_command in persisted artifacts.

Also applies to: 423-426, 473-473, 594-620

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

In `@scripts/cli-swarm/launch_live_windows.py` around lines 315 - 325, The code is
currently inlining forwarded secret keys (the forwarded list) into the rendered
shell command and persisting them via
render_agent_shell_command()/agent_shell_command; instead, stop embedding
secrets in the string: have local_env_exports() collect secrets into an env dict
and pass that env directly to tmux/subprocess when creating the session (use the
env parameter of subprocess or tmux session env API), remove any logic in
render_agent_shell_command() that injects these keys into the returned command
string, and ensure only a non-secret agent_command (without exported
credentials) is written to the generated starter script and manifest; keep the
forwarded list and the env population logic but only use that env at
process/session launch time, not in persisted files.

Comment on lines +150 to +155
def ensure_clean_dir(path: pathlib.Path, force: bool) -> None:
if path.exists():
if not force:
raise SystemExit(f"{path} already exists; rerun with --force to replace it.")
shutil.rmtree(path)
path.mkdir(parents=True, exist_ok=True)

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 | 🔴 Critical

Constrain --name before --force can delete outside .swarm/.

args.name is joined verbatim into run_root, then ensure_clean_dir() may shutil.rmtree() it. A value like ../.. escapes the swarm root and can wipe arbitrary directories. Reject path components and verify the resolved path stays under workspace / ".swarm" before removing anything.

Suggested guard
 def main() -> int:
     args = parse_args()
+    if pathlib.Path(args.name).name != args.name or args.name in {"", ".", ".."}:
+        raise SystemExit("--name must be a simple directory name")
     worker_tools = [tool.strip() for tool in args.tools.split(",") if tool.strip()]
@@
     workspace = repo_root(args.workspace)
-    run_root = workspace / ".swarm" / args.name
+    swarm_root = (workspace / ".swarm").resolve()
+    run_root = (swarm_root / args.name).resolve()
+    if run_root.parent != swarm_root:
+        raise SystemExit("--name must stay within .swarm/")

Also applies to: 397-398

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

In `@scripts/cli-swarm/launch.py` around lines 150 - 155, The code currently joins
args.name into run_root and may call ensure_clean_dir(run_root) which allows
path-traversal (e.g., "../..") to escape the swarm root and be deleted; fix by
validating args.name before joining and by checking resolved paths before
deleting: reject names that contain path separators, backslashes, leading
slashes, or any ".." segments (i.e., only allow a simple basename), and after
constructing run_root ensure run_root.resolve() is a descendant of (workspace /
".swarm"). Also update any other usage that constructs a path from args.name
(the other call around the 397-398 area) to perform the same basename validation
and descendant check prior to calling ensure_clean_dir or shutil.rmtree.

Comment on lines +197 to +206
for key in ("OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GEMINI_API_KEY", "OPENCODE_API_KEY", "OPENROUTER_API_KEY"):
if key in os.environ:
env[key] = os.environ[key]
return env


def render_shell_command(command: list[str], env: dict[str, str], cwd: pathlib.Path, log_file: pathlib.Path) -> str:
exports = " ".join(f"{key}={shlex.quote(value)}" for key, value in env.items())
quoted = " ".join(shlex.quote(part) for part in command)
return f"cd {shlex.quote(str(cwd))} && mkdir -p {shlex.quote(str(log_file.parent))} && env {exports} {quoted} >> {shlex.quote(str(log_file))} 2>&1"

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 | 🔴 Critical

Do not serialize live API keys into the manifest.

local_env_exports() forwards provider secrets, and Lines 203-206 bake them into the shell command. Lines 415-440 then write and print those commands, so credentials end up on disk and in terminal logs. Keep the environment separate from the rendered command and inject it only at process launch.

Also applies to: 309-325, 415-440

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