Skip to content

chore: promote staging to staging-promote/bc672520-23062088162 (2026-03-13 18:08 UTC) - #1137

Merged
henrypark133 merged 25 commits into
staging-promote/bc672520-23062088162from
staging-promote/f53c1bb1-23064256940
Mar 16, 2026
Merged

henrypark133 merged 25 commits into
staging-promote/bc672520-23062088162from
staging-promote/f53c1bb1-23064256940

Conversation

@ironclaw-ci

@ironclaw-ci ironclaw-ci Bot commented Mar 13, 2026 •

Copy link
Copy Markdown
Contributor

Auto-promotion from staging CI

Batch range: bc6725205ada24f26ed30fd042dc4aa6b546cb93..f53c1bb10beba3f6bb1f127c34371a6c0bf6f510
Promotion branch: staging-promote/f53c1bb1-23064256940
Base: staging-promote/bc672520-23062088162
Triggered by: Staging CI batch at 2026-03-13 18:08 UTC

Commits in this batch (1):

Current commits in this promotion (19)

Current base: staging-promote/bc672520-23062088162
Current head: staging-promote/f53c1bb1-23064256940
Current range: origin/staging-promote/bc672520-23062088162..origin/staging-promote/f53c1bb1-23064256940

Auto-updated by staging promotion metadata workflow

Waiting for gates:

  • Tests: pending
  • E2E: pending
  • Claude Code review: pending (will post comments on this PR)

Auto-created by staging-ci workflow

* fix(mcp): address 14 audit findings across MCP module

- Replace panicking assert! in new_with_config with Result return (Critical)
- Fix initialize() race condition using tokio::sync::OnceCell (High)
- Fix localhost check bypass via proper URL parsing (High)
- Extract shared stream_transport_send() to deduplicate stdio/unix send logic
- Use atomic write (tmp+rename) for config file persistence
- Filter SSE responses by request_id to prevent wrong-response dispatch
- Share a single reqwest::Client for OAuth via fallible OnceLock
- Log notification send errors instead of silently discarding
- Fix unwrap_or(0) that could steal id=0 responses
- Store InitializeResult in OnceCell so callers can access server capabilities
- Add redirect logging in OAuth discovery
- Reuse is_localhost_url() in auth.rs
- Add McpToolWrapper unit tests and regression tests
- URL-encode PKCE challenge for consistency

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

* chore: retrigger CI with skip-regression-check label

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added size: XL 500+ changed lines scope: tool/mcp MCP client risk: medium Business logic, config, or moderate-risk modules contributor: experienced 6-19 merged PRs and removed size: XL 500+ changed lines labels Mar 13, 2026
@claude

claude Bot commented Mar 13, 2026

Copy link
Copy Markdown

Code review

Found 5 issues:

  1. [HIGH:75] oauth_http_client() stores errors as String in static, not AuthError: In auth.rs, the static stores Result<Client, String> but returns Result<&'static Client, AuthError>. This violates CLAUDE.md's error type guidance—errors should be stored as proper types, not strings.

https://github.com/anthropics/ironclaw/blob/9f6c62c/src/tools/mcp/auth.rs#L15-L23

fn oauth_http_client() -> Result<&'static reqwest::Client, AuthError> {
    static CLIENT: std::sync::OnceLock<Result<reqwest::Client, String>> =
        std::sync::OnceLock::new();
    CLIENT
        .get_or_init(|| {
            reqwest::Client::builder()
                .timeout(Duration::from_secs(30))
  1. [HIGH:75] O(n²) string allocations in hot SSE parsing path: In http_transport.rs, the buffer accumulates with buffer.push_str(&String::from_utf8_lossy(&chunk)) and truncates with buffer[remaining_start..].to_string(). For large SSE responses, this creates quadratic behavior. Should pre-allocate capacity and use more efficient string operations.

https://github.com/anthropics/ironclaw/blob/9f6c62c/src/tools/mcp/http_transport.rs#L188-L220

buffer.push_str(&String::from_utf8_lossy(&chunk));
...
buffer = buffer[remaining_start..].to_string();
  1. [MEDIUM:70] OnceCell clone resets initialization state, breaking singleton semantics: In client.rs, cloning an McpClient creates a fresh OnceCell::new() instead of sharing the initialization guard. This breaks the design intent—a cloned client will re-initialize even if the source already did, potentially re-sending handshakes or missing cached state.

https://github.com/anthropics/ironclaw/blob/9f6c62c/src/tools/mcp/client.rs#L505-L510

impl Clone for McpClient {
    fn clone(&self) -> Self {
        Self {
            ...
            initialized: tokio::sync::OnceCell::new(),  // Should share with source?
  1. [MEDIUM:65] Multiple sequential mutex lock acquisitions for single timeout operation: In transport.rs, stream_transport_send() acquires the writer/pending mutex 3-4 times sequentially (register, write, cleanup). Under contention, this creates lock-wait cycles. Should consolidate into a single critical section where possible.

https://github.com/anthropics/ironclaw/blob/9f6c62c/src/tools/mcp/transport.rs#L870-L933

let mut w = writer.lock().await;  // Lock 1
...
let mut map = pending.lock().await;  // Lock 2
...
let mut w = writer.lock().await;  // Lock 3
...
let mut map = pending.lock().await;  // Lock 4
  1. [MEDIUM:60] DRY violation: identical error mapping duplicated in factory.rs: Lines 678-682 and 686-690 duplicate the same error conversion. Extract into a helper function or consolidate branches.

https://github.com/anthropics/ironclaw/blob/9f6c62c/src/tools/mcp/factory.rs#L678-L691

Ok(McpClient::new_with_config(server)
    .map_err(|e| McpFactoryError::InvalidConfig { ... })?
    ...
)
...
Ok(McpClient::new_with_config(server)
    .map_err(|e| McpFactoryError::InvalidConfig { ... })?
    ...
)

🤖 Generated with Claude Code

henrypark133 and others added 20 commits March 13, 2026 11:24
…nsions (#1106)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…2433

chore: promote staging to staging-promote/7d745d54-23066609095 (2026-03-13 20:06 UTC)
* feat(web): add follow-up suggestion chips and ghost text to chat UI

The LLM now always generates 1-3 follow-up command suggestions via
<suggestions> tags in its response. These are extracted server-side,
broadcast as SSE events, and rendered as clickable chips above the
chat input. The first suggestion also appears as ghost text in the
input field (Tab to accept). Includes debug logging for LLM responses
in the agentic loop and removes noisy NEAR AI status logging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve deferred review items from PR #1156 [skip-regression-check]

- Remove literal backslashes from raw string prompt (reasoning.rs)
- Make WASM channels skip Suggestions status (no-op instead of empty callback)
- Add !e.shiftKey guard to Tab-to-accept ghost text handler
- Cap extracted suggestions at 3 and trim whitespace-only entries
- Extract suggestions in approval-resume path (prevents tag leaking)
- Remove stale .has-ghost class during showSuggestionChips reset

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(registry): move MCP server entries from code to JSON manifests

Move 8 hardcoded MCP server RegistryEntry structs from
builtin_entries() into data-driven JSON files under
registry/mcp-servers/, matching the existing pattern used by
tools and channels. Exclude the GitHub MCP entry which conflicts
with the WASM GitHub tool's OAuth flow.

Extend ManifestKind with McpServer, make version/source optional
on ExtensionManifest (MCP servers don't need them), and add
url/auth fields for MCP-specific config. Update build.rs,
embedded catalog, catalog loader, installer, and CLI display
to handle the new kind and optional fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(registry): address PR review — add missing slack-mcp, remove .expect(), fix fmt

- Add missing slack-mcp.json (was dropped during migration)
- Remove production .expect() in get_strict(), replace with .ok_or_else()
- Clean up unwrap_or_default() in key_for() to use .next() directly
- Log warning for MCP manifests missing url field instead of silent empty
- Run cargo fmt to fix formatting diffs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: re-trigger CI with correct base branch (staging)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): improve no-panics check to properly exclude test modules

The grep-based filter only excluded lines literally containing
#[cfg(test)], #[test], or 'mod tests' — not lines *inside* test
modules. Use awk to track hunk context from diff @@ headers and
skip all added lines within test module hunks.

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(registry): remove slack-mcp MCP entry (conflicts with WASM slack tool)

Remove slack-mcp.json alongside the already-excluded github MCP
entry — both conflict with existing WASM tools of the same name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(registry): address re-review — skip invalid MCP entries, fix install order

- to_registry_entry() now returns Option<RegistryEntry>; MCP manifests
  missing a url field are skipped with a warning instead of creating
  broken entries with empty URLs
- Move McpServer early-return before require_source() in install paths
  so the error message is clear ("cannot install MCP servers") rather
  than the misleading "missing source spec"
- Add test for MCP manifest with missing URL returning None

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…header, docs inconsistency and security concern (#1162)

Implement industry-standard HMAC-SHA256 header-based webhook authentication
to resolve issue #722. The X-Hub-Signature-256 header follows GitHub's
webhook security model, replacing the non-standard X-IronClaw-Signature header.

**Changes:**
- Rename HTTP webhook signature header from X-IronClaw-Signature to X-Hub-Signature-256
- X-Hub-Signature-256 is the standard used by GitHub, Stripe, and other webhook providers
- HMAC-SHA256 signatures continue to use sha256=<hex> format
- Body 'secret' field remains supported as deprecated fallback for backward compatibility
- All error messages and documentation updated to reflect new header name

**Security impact:**
- Signatures verified via HTTP header instead of request body
- Signature visible in Authorization header only, not logged in request body
- Follows industry best practices for webhook authentication
- Fail-closed policy: rejects requests without authentication

**Backward compatibility:**
- Requests without X-Hub-Signature-256 header fall back to 'secret' field in body (with deprecation warning)
- Deprecation path: migrate to header-based auth, body field support will be removed in a future release

**Test coverage:**

Unit tests (20 tests in src/channels/http.rs):
- 6 header-based auth tests (valid/invalid/malformed signatures, header encoding)
- 2 backward compatibility tests (deprecated body secret fallback)
- 3 error handling tests (missing auth, invalid JSON, content-type validation)
- 4 signature verification unit tests (valid digest, invalid digest, missing prefix, invalid hex)
- 5 advanced tests (concurrency, dynamic updates, header precedence, no deadlocks, runtime clearing)

E2E tests (12 tests in tests/e2e/scenarios/test_webhook.py):
- Valid HMAC-SHA256 signature acceptance
- Invalid/wrong/malformed signature rejection
- Header precedence over body secret
- Deprecated body secret backward compatibility
- Missing auth rejection (fail-closed)
- Content-Type validation
- Invalid JSON handling
- Case-insensitive header lookup
- Message queuing and processing
- Fixture for running server with HTTP_WEBHOOK_SECRET configured

All 3,033 lib tests pass with zero clippy warnings.

**Example usage after fix:**

BODY='{"content": "hello"}'
SECRET="your-webhook-secret"
SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')

curl -X POST http://127.0.0.1:9090/webhook \
  -H "Content-Type: application/json" \
  -H "X-Hub-Signature-256: sha256=$SIG" \
  -d "$BODY"

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
…th (#1164)

* fix: Google Sheets returns 403 PERMISSION_DENIED after completing OAuth

* fix: linter

* fix: linter

* fix: ci

* fix

* fix

* fix

* fix
Python bytecode cache files were accidentally committed. Remove them
from tracking and prevent future occurrences via .gitignore.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(webhook): avoid holding mutex across async shutdown

* test(webhook): add regression coverage for begin_shutdown split path

* test(webhook): satisfy no-panics rule in begin_shutdown regression
#1161)

* fix: Non-transactional multi-step context updates between metadata/token setup and DB

* fix: code style
…1172)

* perf(routines): bound tool-loop history snapshot clone cost

* test(ci): annotate snapshot assertions for no-panics matcher

* test(ci): keep no-panics suppression on single-line assertion

* test(ci): keep snapshot tail assert single-line for no-panics

* Update src/agent/routine_engine.rs

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* chore(deps): bump yanked uds_windows in lockfile for cargo-deny

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* fix(channels): use live owner binding during wasm hot activation

* test(channels): cover owner-id store fallback without panic macros
* fix(llm): add stop_sequences parity for tool completions

* refactor(web-openai): dedupe request builders and satisfy no-panics gate

* test(llm): mark multiline assert with safety comment for CI gate

* test(llm): make safety-marked assert formatting-stable
* fix: N+1 query pattern in event trigger loop (routine_engine)

* fix: linter
* feat(routines): render cron triggers as human-readable summaries

* test(routines): annotate multiline cron assertions for no-panics CI

* test(routines): avoid multiline assert lint false positives
Fixes cargo-deny CI failure due to yanked crate.
[skip-regression-check]

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…5848

chore: promote staging to staging-promote/579c4fdb-23095333790 (2026-03-14 21:05 UTC)
…3790

chore: promote staging to staging-promote/17706632-23094430993 (2026-03-14 20:03 UTC)
…0993

chore: promote staging to staging-promote/f9b880c2-23080458788 (2026-03-14 19:08 UTC)
…8788

chore: promote staging to staging-promote/7d745d54-23066609095 (2026-03-14 04:31 UTC)
…9095

chore: promote staging to staging-promote/f53c1bb1-23064256940 (2026-03-13 19:12 UTC)
@github-actions github-actions Bot added scope: agent Agent core (agent loop, router, scheduler) scope: channel Channel infrastructure scope: channel/cli TUI / CLI channel scope: channel/web Web gateway channel scope: channel/wasm WASM channel runtime scope: tool/builtin Built-in tools scope: tool/wasm WASM tool sandbox scope: db Database trait / abstraction scope: db/postgres PostgreSQL backend scope: llm LLM integration scope: orchestrator Container orchestrator scope: worker Container worker scope: extensions Extension management scope: ci CI/CD workflows scope: dependencies Dependency updates labels Mar 16, 2026
@henrypark133
henrypark133 merged commit a580c1d into staging-promote/bc672520-23062088162 Mar 16, 2026
13 of 14 checks passed
@github-actions github-actions Bot added the size: XL 500+ changed lines label Mar 16, 2026
@henrypark133
henrypark133 deleted the staging-promote/f53c1bb1-23064256940 branch March 16, 2026 14:51
@github-actions github-actions Bot added contributor: core 20+ merged PRs and removed contributor: experienced 6-19 merged PRs labels Mar 16, 2026
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
…3064256940

chore: promote staging to staging-promote/bc672520-23062088162 (2026-03-13 18:08 UTC)
drchirag1991 pushed a commit to drchirag1991/ironclaw that referenced this pull request Apr 8, 2026
…3064256940

chore: promote staging to staging-promote/544e41c9-23062088162 (2026-03-13 18:08 UTC)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: agent Agent core (agent loop, router, scheduler) scope: channel/cli TUI / CLI channel scope: channel/wasm WASM channel runtime scope: channel/web Web gateway channel scope: channel Channel infrastructure scope: ci CI/CD workflows scope: db/postgres PostgreSQL backend scope: db Database trait / abstraction scope: dependencies Dependency updates scope: extensions Extension management scope: llm LLM integration scope: orchestrator Container orchestrator scope: tool/builtin Built-in tools scope: tool/mcp MCP client scope: tool/wasm WASM tool sandbox scope: worker Container worker size: XL 500+ changed lines staging-promotion

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants