feat(pitch-deck): add prompt.fail domain landing page#28
Conversation
…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>
There was a problem hiding this comment.
Sorry @aegntic, your pull request is larger than the review limit of 150000 diff characters
📝 WalkthroughWalkthroughThis 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
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)
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
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly Related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
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 | 🟠 MajorVersion
DispatchRecordlike the other canonical records.
TaskPacket,ArtifactRecord,RegistryRecord, andLifecycleEventall carryschema_version, butDispatchRecorddoes 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 | 🟠 MajorAdd HTML tab and Alpine handlers for the model-selector page.
The
import.jsmodule is properly wired inindex_body.htmlwith corresponding tab element and Alpine handlers (lines 5016+), butmodel-selector.jsis missing its HTML UI elements and AlpinemodelSelectorPage()data function. Include both the tab element and Alpine binding inindex_body.htmlto 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 | 🟠 MajorDon’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 | 🟠 MajorDon’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 callscreateObsidianSeedAgent(). Opening the Obsidian page for a normal agent can now silently create a brand-newmemory-seedagent 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 | 🟠 MajorRoute these handlers through
routes.rs.
server.rsnow bindscrate::auto_import::*andcrate::openclaw::*directly, which bypasses the API handler boundary this crate uses elsewhere. Please move or re-export these handlers throughroutes.rsso the router keeps depending onroutes::*only.As per coding guidelines, "New API routes must be registered in
server.rsrouter AND implemented inroutes.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.
pageandnavigate()belong to theapp()component (defined afterAlpine.store), notAlpine.store('app'). This method will throwthis.navigate is not a functionat 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 | 🟠 MajorExpose a scoped entrypoint instead of relying on silent no-op setters.
set_trace_id()andclear_trace_id()only work after someone has enteredTRACE_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 smallwith_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 | 🟠 MajorMove the loading reset to
finally.The
data.errorearly return and the exception path both skipthis.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 | 🟠 MajorKeep the guarded flattening.
The second assignment overwrites the safe
|| []normalization. If one collection is omitted in the scan response,discoveredItemscan containundefinedentries or fail whenmcp_serversis 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 | 🟠 MajorReset the selection when the scan target changes.
selectedItemssurvives 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 | 🟠 MajorMake
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 | 🟠 MajorMake these import selections keyboard-operable.
All three lists use clickable
divcards 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 | 🟠 MajorMove the import suggestions lazy-load to
importPage()scope.The
loadSuggestions()call at lines 4086–4087 references state that exists only in the nestedimportPage()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 | 🟠 MajorPropagate clears through
onSelecttoo.
selectModel()notifies the embedding page, butclearSelection()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 | 🟠 MajorProbe 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!orfutures::join_allto 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 | 🟠 MajorUse
tokio::task::spawn_blocking()to move filesystem operations off the async path.Both
scan_directoryandimport_itemshandlers invoke synchronous, blocking filesystem traversal directly in the async context.scan_recursive()usesstd::fs::read_dir()(lines 253–299), andcopy_dir_all()usesstd::fs::read_dir()andstd::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 | 🟠 MajorUse
Jsonwrapper for error responses instead of plain strings.Returning
(StatusCode, String)causes Axum to respond withtext/plaincontent-type, and the format stringformat!("{{\"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 | 🟠 MajorKeep 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 | 🟠 MajorMake 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 | 🟠 MajorReplace 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 | 🟠 MajorWait 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 | 🟠 MajorSession restore script still depends on
screen, conflicting with restore direction.Line 4 through Line 34 keep
screen -r/-xas the execution path, while the restore checklist explicitly moves away fromscreen. 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 | 🟠 MajorPrimary 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 | 🟠 MajorResolve 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 | 🟠 MajorMake
--open-windows-onlyload existing state instead of re-preparing tasks.This path still runs
ensure_worktree()andsync_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 | 🟠 MajorThe flag and auth guidance does not match the launcher.
launch_live_windows.py.snapshotdoes not define--isolate-cli-state, and it always rewritesHOME/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 | 🟠 MajorDon't suppress a requested proof-stage failure.
Once
DASHBOARD_PROOF_URLis set, a failingdashboard-proof.shstill leaves this script green. Any wrapper or CI job using the exit code will treat verification as passed even though the report saysproof: 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
--coordinatoris currently ignored.For coordinator specs,
tool_nameis hard-coded topi, so non-default values still build a pi command and can even fail ontool_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 | 🟠 MajorIsolate CLI homes per session, not per tool.
home_dir = root / "homes" / toolgives every worker using the same CLI the sameHOME,XDG_*, and tool-specific state dirs. Sincedistribute()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 | 🟠 MajorAdd graceful fallbacks for missing or malformed dconf data.
The
gnome_terminal_profile()function at line 214 callsdconfunconditionally without checking if the tool exists, andparse_dconf_string()at line 197 can raiseSyntaxErrororValueErroron unexpected dump values. These are called unconditionally viawrite_kitty_profile_config()at line 640, turning a cosmetic feature into a hard launcher failure on systems withoutdconfor with malformed profile data.Wrap
ast.literal_eval()in a try-except block to returnNoneon parse errors, and checkshutil.which("dconf")at the start ofgnome_terminal_profile()to returnNonegracefully 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 | 🟠 MajorDefer
screenrequirement to launch-only path.
require_screen()is called unconditionally during spec creation (line 313), which happens at lines 410–412 inmain()before theargs.launchguard at line 443. This causes preparation-only runs (--launch=false) to fail on hosts withoutscreeneven though the binary is only needed for actual session launch. Either defer the binary check to theargs.launchpath, or store the string"screen"symbolically in theSessionSpecand 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 | 🟠 MajorCreate the log directory in Python before starting the tmux session.
A race condition exists between when
tmux pipe-paneattaches at line 535 and when the bash script creates the parent directory for the log file at line 457. Sincetmux new-session -dreturns immediately without waiting for the spawned bash script to execute,pipe-panecan 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.parentin Python at the start ofensure_tmux_sessionbefore 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
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (96)
.gitignorecrates/clawreform-api/Cargo.tomlcrates/clawreform-api/src/auto_import.rscrates/clawreform-api/src/lib.rscrates/clawreform-api/src/middleware.rscrates/clawreform-api/src/openclaw.rscrates/clawreform-api/src/routes.rscrates/clawreform-api/src/server.rscrates/clawreform-api/src/webchat.rscrates/clawreform-api/static/css/components.csscrates/clawreform-api/static/index_body.htmlcrates/clawreform-api/static/js/app.jscrates/clawreform-api/static/js/pages/import.jscrates/clawreform-api/static/js/pages/model-selector.jscrates/clawreform-api/static/js/pages/settings.jscrates/clawreform-api/static/js/pages/skills.jscrates/clawreform-api/tests/api_integration_test.rscrates/clawreform-api/tests/daemon_lifecycle_test.rscrates/clawreform-api/tests/load_test.rscrates/clawreform-cli/src/tui/mod.rscrates/clawreform-kernel/src/kernel.rscrates/clawreform-kernel/src/registry.rscrates/clawreform-memory/src/session.rscrates/clawreform-memory/src/structured.rscrates/clawreform-runtime/src/agent_loop.rscrates/clawreform-runtime/src/compactor.rscrates/clawreform-runtime/src/lib.rscrates/clawreform-runtime/src/trace_context.rscrates/clawreform-types/src/agent.rscrates/clawreform-types/src/company.rscrates/clawreform-types/src/lib.rscrates/clawreform-types/src/openclaw.rscrates/clawreform-wire/src/peer.rsdocs/cli-swarm.mddocs/design/launch-metallic-baseline.mddocs/launch-tasks/0_clawREFORM_launch-commander.mddocs/launch-tasks/10_clawREFORM_content-creator.mddocs/launch-tasks/11_clawREFORM_content-distributor.mddocs/launch-tasks/12_clawREFORM_email-list-super-sherlock.mddocs/launch-tasks/13_clawREFORM_launch-control-room.mddocs/launch-tasks/14_clawREFORM_design-system-translation.mddocs/launch-tasks/15_clawREFORM_live-window-operator-sheet.mddocs/launch-tasks/1_clawREFORM_pre-site-deployment.mddocs/launch-tasks/2_clawREFORM_audit_dl-path.mddocs/launch-tasks/3_clawREFORM_release-packaging-proof.mddocs/launch-tasks/4_clawREFORM_onboarding-first-run-smoke.mddocs/launch-tasks/5_clawREFORM_parallel-proof-screenshots.mddocs/launch-tasks/6_clawREFORM_public-alpha-go-live.mddocs/launch-tasks/7_clawREFORM_twitter-x-launch-chain.mddocs/launch-tasks/8_clawREFORM_youtube-launch-automation.mddocs/launch-tasks/9_clawREFORM_reddit-medium-quora-chain.mddocs/session-restore/2026-03-13-launch-alpha/README.mddocs/session-restore/2026-03-13-launch-alpha/constraints-summary.mddocs/session-restore/2026-03-13-launch-alpha/conversation.mddocs/session-restore/2026-03-13-launch-alpha/design/launch-metallic-baseline.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/0_clawREFORM_launch-commander.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/10_clawREFORM_content-creator.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/11_clawREFORM_content-distributor.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/12_clawREFORM_email-list-super-sherlock.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/13_clawREFORM_launch-control-room.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/14_clawREFORM_design-system-translation.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/15_clawREFORM_live-window-operator-sheet.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/1_clawREFORM_pre-site-deployment.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/2_clawREFORM_audit_dl-path.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/3_clawREFORM_release-packaging-proof.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/4_clawREFORM_onboarding-first-run-smoke.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/5_clawREFORM_parallel-proof-screenshots.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/6_clawREFORM_public-alpha-go-live.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/7_clawREFORM_twitter-x-launch-chain.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/8_clawREFORM_youtube-launch-automation.mddocs/session-restore/2026-03-13-launch-alpha/launch-tasks/9_clawREFORM_reddit-medium-quora-chain.mddocs/session-restore/2026-03-13-launch-alpha/non-sandbox-delta.mddocs/session-restore/2026-03-13-launch-alpha/prompts/01-pre-site-deployment.mddocs/session-restore/2026-03-13-launch-alpha/prompts/02-audit-dl-path.mddocs/session-restore/2026-03-13-launch-alpha/prompts/04-onboarding-first-run-smoke.mddocs/session-restore/2026-03-13-launch-alpha/prompts/05-parallel-proof-screenshots.mddocs/session-restore/2026-03-13-launch-alpha/prompts/10-content-creator.mddocs/session-restore/2026-03-13-launch-alpha/prompts/13-launch-control-room.mddocs/session-restore/2026-03-13-launch-alpha/prompts/14-design-system-translation.mddocs/session-restore/2026-03-13-launch-alpha/restore-checklist.mddocs/session-restore/2026-03-13-launch-alpha/state/kitty-live.sessiondocs/session-restore/2026-03-13-launch-alpha/state/launch_live_windows.py.snapshotdocs/session-restore/2026-03-13-launch-alpha/state/visible-launch-manifest.jsondocs/session-restore/2026-03-13-launch-alpha/working-state.mdopenclaw/IMPLEMENTATION_PACK.mdopenclaw/docker-compose.openclaw.ymlopenclaw/openclaw_registry.sqlpitch-deck/prompt.fail-README.mdpitch-deck/prompt.fail-redirect.htmlpitch-deck/prompt.fail.htmlscripts/cli-swarm/launch.pyscripts/cli-swarm/launch_live_windows.pyscripts/cli-swarm/proof-demo.shscripts/cli-swarm/status.pyscripts/cli-swarm/verify.shscripts/dashboard-proof.sh
| 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)?; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let me locate and read the file
fd -t f "auto_import.rs" | head -5Repository: 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.rsRepository: 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.rsRepository: 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 2Repository: 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/ -lRepository: 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.rsRepository: 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 2Repository: 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).
| 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); |
There was a problem hiding this comment.
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.
| 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/") | ||
| } |
There was a problem hiding this comment.
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.
| 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); |
There was a problem hiding this comment.
🧩 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:
- 1: https://www.postgresql.org/docs/current/sql-createindex.html
- 2: http://www.postgres.com/docs/18/sql-createindex.html
- 3: http://www.postgresql.org/docs/9.5/sql-createindex.html
- 4: https://www.postgresql.org/docs/current/indexes-ordering.html
- 5: https://atlasgo.io/guides/postgres/descending-indexes
- 6: https://stackoverflow.com/questions/78941374/create-primary-index-for-primary-key-in-postgres-with-desc-ordering
🏁 Script executed:
find . -name "openclaw_registry.sql" -type fRepository: aegntic/clawreform
Length of output: 94
🏁 Script executed:
sed -n '219,233p' ./openclaw/openclaw_registry.sqlRepository: 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.
| ``` | ||
| prompt.fail. A 76.76.21.21 (Vercel) | ||
| # or | ||
| prompt.fail. CNAME clawreform.com | ||
| ``` |
There was a problem hiding this comment.
🧩 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:
- 1: https://serverfault.com/questions/613829/why-cant-a-cname-record-be-used-at-the-apex-aka-root-of-a-domain
- 2: https://www.urllo.com/resources/learn/what-is-an-apex-domain
- 3: https://kb.isc.org/docs/aa-01640
- 4: https://isc.org/blogs/cname-at-the-apex-of-a-zone
- 5: https://en.wikipedia.org/wiki/CNAME_record
- 6: https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-root-domain/
- 7: https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-zone-apex/
- 8: https://vercel.com/docs/domains/working-with-domains/add-a-domain
- 9: https://vercel.com/docs/domains/working-with-domains
- 10: https://www.vercel.com/docs/domains/working-with-dns
- 11: https://vercel.com/guides/how-can-i-debug-a-dns-record
- 12: https://blog.achulslander.com/configure-external-dns-for-netlify
- 13: https://netlify-pilot.netlify.app/dns/index.html
- 14: https://netlify.com/blog/2021/04/06/migrating-dns-for-a-production-site-we-made-you-a-site-migration-checklist
- 15: https://developers.cloudflare.com/fundamentals/manage-domains/redirect-domain/
- 16: https://developers.cloudflare.com/pages/how-to/www-redirect/
- 17: https://developers.cloudflare.com/rules/url-forwarding/examples/redirect-root-to-www/
- 18: https://docs.netlify.com/routing/redirects/
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.21at 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.
| 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] |
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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.
| 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" |
There was a problem hiding this comment.
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
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
Files Added
pitch-deck/prompt.fail.htmlpitch-deck/prompt.fail-redirect.htmlpitch-deck/prompt.fail-README.mdDeployment Options
Test Plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
UI/UX Enhancements