Skip to content

chore: promote staging to staging-promote/e2544ed4-24637363119 (2026-04-20 03:46 UTC) - #2705

Merged
henrypark133 merged 4 commits into
mainfrom
staging-promote/e88236ab-24647433078
Apr 21, 2026
Merged

henrypark133 merged 4 commits into
mainfrom
staging-promote/e88236ab-24647433078

Conversation

@ironclaw-ci

@ironclaw-ci ironclaw-ci Bot commented Apr 20, 2026 •

Copy link
Copy Markdown
Contributor

Auto-promotion from staging CI

Batch range: 7fb41555a9e55677d1aaea29ca567a5b369c2b05..e88236ab08c007343587efd3efd984ca0ee39ce8
Promotion branch: staging-promote/e88236ab-24647433078
Base: staging-promote/e2544ed4-24637363119
Triggered by: Staging CI batch at 2026-04-20 03:46 UTC

Commits in this batch (25):

Current commits in this promotion (0)

Current base: main
Current head: staging-promote/e88236ab-24647433078
Current range: origin/main..origin/staging-promote/e88236ab-24647433078

  • (no non-merge commits in range)

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

ilblackdragon and others added 4 commits April 20, 2026 12:23
…ironclaw#2599 stages 4d + 5 (#2687)

* refactor(gateway): extract extensions + jobs + settings + routines slices — ironclaw#2599 stages 4d + 5

Bundles the last feature-slice migrations in one PR. After this lands,
server.rs has zero feature handlers — it's a pure backward-compat
re-export shim that stage 6 will delete.

New feature slices:

- features/extensions/ (stage 4d) — nine routes: list / readiness /
  tools / install / activate / remove / registry / setup / setup-submit.
  Owns derive_activation_status, derive_onboarding, extension_phase_for_web,
  and apply_extension_readiness_to_response. Every handler that takes
  `{name}` from the URL validates via `ExtensionName::new` at the
  boundary (400 on path-traversal / invalid chars / oversized). The
  setup-submit path routes through `AuthManager::resolve_auth_flow_extension_name`
  (canonical resolver) and `platform::engine_dispatch`, preserving the
  identity invariants called out in CLAUDE.md.
- features/jobs/ (stage 5) — nine routes covering sandbox-job lifecycle
  (list / summary / detail / cancel / restart / prompt / events /
  files-list / files-read). Straight file move from handlers/jobs.rs.
- features/settings/ (stage 5) — eight routes (list / export / import /
  get / set / delete / tools-list / tools-set). `resolve_settings_store`
  promoted to `pub(crate)` for `handlers/tool_policy.rs`. Straight file
  move from handlers/settings.rs.
- features/routines/ (stage 5) — seven routes merged from two sources:
  `handlers/routines.rs` (list / summary / detail / trigger / toggle /
  delete) plus the previously-canonical `routines_runs_handler` from
  `server.rs` (the `handlers/routines.rs` copy of the same function was
  marked `#[allow(dead_code)]` and kept in sync manually — that
  redundancy is gone now). Uses the cleaner `routine.is_owned_by(...)`
  ownership predicate throughout instead of the direct `user_id` match
  the `server.rs` copy used.

server.rs changes:

- Deleted 549 lines of extension handlers + 47 lines of
  routines_runs_handler + the now-redundant axum/Json/types/Uuid imports
  the moved handlers pulled in.
- Reduced to 14 lines of `pub use` re-exports for `start_server` and
  `platform::state::*` so external callers (src/main.rs, src/app.rs,
  tests) keep resolving. Stage 6 follow-up deletes even those once the
  callers flip to `platform::*` directly.
- The `#[cfg(test)] mod tests` block stays in place (it's the only
  module still using `test_gateway_state*` helpers that construct state
  for chat + extensions + oauth + pairing together). Test imports
  updated to pull handlers/helpers from their new feature-slice homes.
  Promoting `test_gateway_state*` to `test_helpers.rs` is the last
  blocker before stage 6 and is tracked in the ironclaw#2599 follow-ups
  comment.

Cross-cutting updates:

- platform/router.rs: consolidated feature-slice imports into one
  section with an updated docstring noting the new completion state.
- handlers/mod.rs: dropped `pub mod extensions / jobs / routines /
  settings` declarations (the files physically moved via `git mv`).
  handlers/ now lists only the still-transitional modules.
- handlers/tool_policy.rs: switched `use super::settings::resolve_settings_store`
  to `use crate::channels::web::features::settings::resolve_settings_store`.
- src/extensions/manager.rs: two `handlers::extensions::derive_onboarding`
  call sites redirected to the new slice path.
- src/channels/web/tests/multi_tenant.rs: jobs + routines handler
  imports updated.
- features/{jobs,settings,routines,extensions}/mod.rs: all
  `crate::channels::web::server::{GatewayState, PerUserRateLimiter,
  RateLimiter, ActiveConfigSnapshot}` imports redirected to
  `platform::state::*` directly, so the new slices don't depend on the
  dying shim.

Quality gate:
- cargo fmt clean
- cargo clippy --all --tests --examples --all-features clean
- cargo test --lib channels::web — 424 passed
- scripts/check_gateway_boundaries.py — clean, empty allowlist preserved
- scripts/check_gateway_boundaries.py test — 16/16

Net shape: four slices added under features/, four handlers files
deleted (via `git mv`), server.rs −651 lines (to 14), plus 467 lines
added to features/extensions/ (the nine canonical extension handlers +
helpers). Migration is functionally complete — stage 6 is the final
cleanup.

Explicit non-scope:
- Stage 6 (server.rs shim deletion) is NOT in this PR. Needs the
  `test_gateway_state*` test-helper promotion to `test_helpers.rs` as
  the prerequisite, which is a coordinated cleanup touching every
  caller that still imports `crate::channels::web::server::*`.
- `handlers/` still holds 12 transitional modules (auth, engine,
  frontend, llm, memory, secrets, skills, system_prompt, tokens,
  tool_policy, users, webhooks). Per ironclaw#2599 stage 7, those
  migrate only if churn justifies — most are low-churn and pattern-
  free.
- No `Deps`-view narrowing in any slice; every handler still takes the
  full `GatewayState`. Separate hardening PR per the tracking issue.

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

* docs(gateway): fix CLAUDE.md stale references — PR #2687 review

Two Copilot doc fixes:

- `platform/engine_dispatch.rs` description said "server.rs (chat +
  extensions_setup_submit)" but chat migrated in 4c (#2680) and
  extensions in 4d (this PR). Updated to list the three current feature
  slices that compose the dispatch helpers.
- `features/chat/` description referenced
  `AuthManager::resolve_auth_flow_extension_name`, but the public
  `AuthManager` method is `resolve_extension_name_for_auth_flow`
  (src/bridge/auth_manager.rs:406). `resolve_auth_flow_extension_name`
  is the underlying `pub(crate)` free function the method delegates to.
  Corrected to the method name so the docs match the public API.

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

* fix(gateway): wire channel_relay kind_hint + refresh router docstring — PR #2687 review

Two PR #2687 review fixes:

- `extensions_install_handler` now maps `kind: "channel_relay"` to
  `ExtensionKind::ChannelRelay`. Frontend registry entries send
  `channel_relay` as the kind, and `ExtensionManager::install` uses
  `kind_hint` to disambiguate registry name collisions and URL-install
  inference. The dropped arm meant Slack-relay-style installs could
  land on the wrong disambiguation path. Pre-existing in the canonical
  `server.rs` parser; folding the fix here because two reviewers
  (Gemini + Copilot) flagged it and the fix is one line.

- `platform/router.rs` top-of-file docstring rewritten to match the
  post-stage-4d reality: feature handlers live in `features/<slice>/`
  or the transitional `handlers/*.rs` flat folder — none live in
  `server.rs`, which is now a pure re-export shim awaiting stage 6
  deletion. The old wording contradicted the updated inline comment
  at line 64.

Regression coverage: single-arm addition to a match on a small closed
enum; caller-level behavior is covered by existing
`ExtensionManager::install` paths that consume `kind_hint`. No new
test added — the change is a one-arm parity fix, and a test asserting
the match arm would duplicate the compiler's exhaustiveness check.
[skip-regression-check]

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…variants (#2677)

* refactor(ownership): collapse OwnerId+Identity into UserId with role variants

- Expand UserRole to {Owner, Admin, Regular}
- UserId carries role; methods is_owner()/is_admin()/is_regular()
- Remove From<String>/From<&str> impls (enforces types.md rule)
- Validated construction via new(); from_trusted() for DB-sourced values

Addresses bug pattern from #2561, #2620, #2349 where owner_id silently
round-tripped as String.

* refactor(ownership): address review feedback — id-only equality, persist owner role, doc fixes

- UserId PartialEq/Eq/Hash now compare only `id`, not `role`. Role is
  metadata that travels with the identity; two UserIds with the same id
  but different roles must be interchangeable as HashMap/HashSet keys
  and cache lookup targets. Added a regression test that builds a
  HashSet keyed on UserId and asserts cross-role `.contains()`
  membership, plus a hash-equality check.
- CLI pairing path now persists the "owner" role string (via
  UserRole::Owner.as_db_role()) instead of the hardcoded "admin", so
  a reload through UserRole::from_db_role stays Owner rather than
  being silently downgraded to Admin.
- Update the feature/pairing approve handler to mirror the refactor:
  build UserId via from_trusted + UserRole::from_db_role(&user.role)
  instead of the removed OwnerId::from.
- AdminScope doc comment now reflects that Owner also passes
  is_admin().
- AdminUser extractor error message now reads "Admin privileges
  required (admin or owner)" so the forbidden response matches the
  actual gate.

---------

Co-authored-by: Henry Park <henrypark133@gmail.com>
…2e stabilization (#2385)

* feat(gateway): add attachment flows and slash-skill coverage

* feat(v2): persist project attachments across channels

* feat(skills): install GitHub skill bundles

* feat(v2): cover live skill install and setup flow

* test(e2e): stabilize gateway and auth coverage

* test(e2e): stabilize post-merge warnings and browser flows

* fix(review): address follow-up PR feedback

* fix(review): address remaining attachment and skill install comments

* Address remaining attachment review comments

* fix(ci): allowlist ws.rs → server::inline_attachments_to_incoming

ws.rs was already allowlisted for the attachment shim symbols
(`images_to_attachments`, the rate limiter types, etc.) so the new
unified entrypoint added by this branch (combining images and
generic attachments before validation) follows the same pattern.
The entry will be removed together with the rest of the ws.rs
server:: block once the attachment helpers migrate into platform/.

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

* fix(e2e): attachment persistence path and Slack activate signature

Two e2e-surfacing regressions after merging staging:

1. `persist_project_attachments` was writing to
   `<base_dir>/projects/.ironclaw/attachments/...` because PR #2385's
   reviewer-requested switch from `std::env::current_dir()` to an
   explicit `project_root` kept the `.ironclaw/` prefix baked into
   `PROJECT_ATTACHMENT_DIR` while rooting at `ironclaw_base_dir()/projects`.
   Point `resolve_project_root()` at the parent of the base dir so
   `<parent>/.ironclaw/attachments/<owner>/<project>/...` matches the
   prompt's `project_path` and the user's expectation when base dir is
   `~/.ironclaw`. Updates the corresponding assertion in
   test_v2_engine_auth_flow.py to resolve paths against the fixture's
   home tempdir instead of the repo root.

2. `activate_slack()` grew a required `http_url` arg during the
   skill-install branch work but the `active_slack` fixture in
   test_slack_e2e.py still passed the old three-arg shape. That tripped
   every Slack scenario at setup (TypeError). Thread `http_url` from
   `slack_e2e_server` through the fixture.

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

* fix(engine-v2): auth-prompt surfacing, bundle_path injection, attachment-only inputs

- Orchestrator formatter now writes `Installed bundle path on disk:` into
  each skill block so the skill body sees the bundle location it needs to
  reference (e.g. running `pip install -r <bundle>/requirements.txt`).
  Previously the bundle_path metadata field was populated but never
  surfaced into the prompt, so skills that rely on filesystem paths
  silently no-op'd.
- The router no longer rejects messages whose text body is empty when
  the payload carries attachments. Safety validation's empty-input
  guard is a v1 input-sanity check; a pure-attachment follow-up (image
  upload with no caption) is a legitimate submission in the v2 gateway
  contract and previously tripped "Input cannot be empty".
- The engine auth-flow e2e tests now detect gate-paused state via
  `HistoryResponse.pending_gate` (and `resume_kind.Authentication`)
  rather than scanning the turn response text for "paste your token".
  Auth instructions live in the `onboarding_state` SSE event, not in
  the chat response (see `test_auth_no_duplicate_response.py`); the old
  string-matching assertion was checking the wrong surface.

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

* fix(e2e): switch approval/auth-prompt probes to pending_gate

Approval and auth prompts are surfaced through HistoryResponse.pending_gate
and the onboarding_state/gate_required SSE events, not as text in
turns[-1].response — the duplicate-response regression guard in
test_auth_no_duplicate_response.py explicitly forbids them from appearing
in the chat transcript.

Update the helpers in test_v2_engine_approval_flow.py,
test_v2_engine_auth_cancel.py, and test_v2_kernel_auth_preflight.py to
poll pending_gate instead of scanning turn text for "requires approval"
or "paste your token". Unblocks 5 approval, 1 auth-cancel, and 3
preflight tests.

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

* fix(e2e): google-oauth _wait_for_auth_prompt / _wait_for_response use pending_gate

Bring the Google Drive / skill-OAuth regression file in line with the rest
of the v2 e2e helpers: poll `HistoryResponse.pending_gate` for auth/approval
prompts, and accept a pending_gate as a valid terminal state for
`_wait_for_response` (an auth-retry chain that hits another gate is still
progress, not a hang).

Unblocks the oauth-cancel, invalid-token-paste, and api-key-then-api-call
scenarios; the lingering token-refresh scenario still exposes a real v2
auto-refresh regression (the engine prompts the user instead of issuing a
refresh against the stored refresh_token).

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

* fix(e2e): relax a few stale v2-surface assertions

- `test_skill_oauth_flow::test_auth_required_sse_event` was pinned to the
  old `onboarding_state/auth_required` SSE payload. The v2 gate pipeline
  delivers credential gates as `gate_required` (resume_kind
  `Authentication`) or, when preflight falls through to approval first,
  `approval_needed`. Accept any of those three, and treat a `thinking`
  "Running <tool>" status as evidence the tool call fired when no
  standalone `tool_started` event is emitted.
- `test_message_persistence` helpers asserted HTTP 200 on `/api/chat/send`,
  but the gateway now returns 202 ACCEPTED (fire-and-forget). Accept both.
- `test_project_detail` flipped the wrong global (`engineV2`) instead of
  `engineV2Enabled`, leaving the `data-v2-only` Projects tab hidden so the
  click timed out. Set the real flag.

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

* fix(review): address attachment index note correctness

Two Copilot review findings on the attachment persistence path:

- `attachment_index_note` in `src/bridge/router.rs` used the raw
  user-supplied filename in the markdown `# Uploaded attachment:` header
  and in the memory-doc `title` field. A filename with newlines /
  backticks / control characters would corrupt the agent-visible
  transcript and break searchable titles. Route the filename through a
  new `sanitize_filename_for_display` that strips control chars,
  collapses newlines/tabs to spaces, swaps backticks for apostrophes,
  truncates at 256 chars, and falls back to `"attachment"` when the
  sanitized result is empty.
- `persist_project_attachments` cleared `attachment.data` before
  calling `attachment_index_note`, so the `size_bytes.unwrap_or(
  data.len() as u64)` fallback reported `0` bytes whenever the channel
  hadn't pre-populated `size_bytes`. Swap the order — build the index
  note while the buffer is still populated, then drop the bytes.

Also adjust `src/agent/attachments.rs::format_attachment` for the
Image arm: when `data` has been cleared but `local_path` is set (the
engine-v2 persist-then-clear flow), the "visual content not available
in this conversation" message is misleading — the image is available,
just on disk. Surface a dedicated prompt that tells the agent to
reference the project file path instead of trying to load bytes from
memory.

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

* fix(e2e): cancel_during_auth asserts pending_gate clears, not chat text

The test polled \`turns[-1].response\` for "cancel" but the cancel flow
never writes an assistant row to the chat-history DB: resolve_gate
returns \`BridgeOutcome::Respond("Cancelled.")\` which broadcasts via
SSE and calls \`stop_thread\` on the engine thread, neither of which
goes through the DB persistence path that populates turn responses.

Switch the test to verify the user-visible signal the gateway actually
emits — \`history.pending_gate\` disappears after "cancel" resolves the
gate. Matches the approach used in \`test_v2_engine_approval_flow.py\`'s
deny-flow tests.

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

* fix(e2e): pairing approve test tolerates ExtensionName boundary reject

Staging's new `features/pairing/` slice (ironclaw#2599 stage 4b) validates
the `{channel}` URL segment through `ExtensionName::new` at the handler
boundary: a path-traversal / control-character / whitespace-containing
segment (like `evil.Ignore all`) now returns 400 instead of silently
routing to a pairing-store miss.

The regression test used to assert the older 200+JSON shape. Relax it
to accept either 200 (generic `Invalid or expired pairing code.`) or 400
(boundary validation); the real invariant the test exists to protect —
the raw injection-shaped channel string must not echo back into the
response — is still asserted.

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

* fix(review): preserve image bytes through LLM call + document drive mock pin

Two review findings:

- `src/bridge/router.rs::persist_project_attachments` was clearing
  `attachment.data` after writing the file to disk. The very next step
  in `handle_with_engine_inner` is `augment_with_attachments`, which
  only emits a multimodal `image_parts` entry when `att.data` is
  non-empty — so every engine-v2 image upload was silently dropped
  from the LLM request even though the file landed on disk. The
  `persisted_attachments` Vec is local to the dispatch and is dropped
  as soon as the engine call returns, so the "storage hygiene" comment
  the clear used to justify was a no-op. Stop clearing; let RAII free
  the bytes. Updates `src/agent/attachments.rs`'s Image-arm prompt to
  reflect the refined invariant (`data.is_empty()` now implies a
  downstream caller or channel stripped the buffer, not the normal
  persist path).

- `tests/e2e/scenarios/test_v2_engine_oauth_google.py::_pin_mock_drive_api_url`
  posts to `/__mock/set_github_api_url`. The wire name is historical
  — the Drive suite reused the knob — but the fixture name made the
  intent hard to follow. Adds a docstring that calls out the shared
  `_github_api_url` in `mock_llm.py` and explains why the endpoint
  rename would cascade into every other test that uses it.

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

* fix(review): address remaining Copilot feedback on PR 2385

- audio attachments: include `mime` (and size) attribute in `<attachment>`
  XML for parity with image/document so the frontend can render MIME and
  size in attachment cards
- /api/skills list/search: parallelize per-skill filesystem I/O
  (`read_install_metadata`, `try_exists`, `metadata`) via
  `futures::future::join_all` instead of awaiting serially — keeps the
  handler O(n) in wall time for large skill sets
- history parseUserMessageContent: only strip the trailing
  `<attachments>…</attachments>` block when at least one `<attachment>`
  tag is parsed from inside it, otherwise leave the raw text intact so
  user messages that legitimately end with that markup are preserved
- sync_v1_skill_to_store: look up existing shared skill doc via
  `list_skills_global()` instead of `list_shared_memory_docs(project_id)`
  so shared skills installed under one project are updated in place when
  re-synced from another project (prevents duplicate shared docs across
  per-user projects) and preserve the original `project_id` on in-place
  update

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s enum (#2678)

* refactor(events): replace JobResult.status String with JobResultStatus enum

Producers at 7 sites and consumers at 3 sites previously agreed by
convention only. Promotes the status field to a typed enum with
snake_case serde — wire format preserved.

Maps to bug pattern from #2570, #2531, #2517 where status transitions
drifted between producer and consumer.

Tests cover snake_case serialization, wire-format round-trip, and the
is_success() predicate used at consumer sites.

* refactor(events): add Stuck variant, accept "error" alias, case-insensitive parse

JobResultStatus now covers the full set of wire values producers emit:
- New `Stuck` variant for worker/job.rs `mark_stuck` path (was coerced
  to Failed + warn log, losing the distinction that job monitor and
  recovery logic care about).
- `FromStr` accepts `"error"` as a legacy alias for `Failed` so
  claude_bridge and acp_bridge wire payloads deserialize cleanly
  instead of hitting the default-on-unknown branch.
- `FromStr` trims whitespace and uses `eq_ignore_ascii_case`, so
  `"  COMPLETED  "` and `"Failed"` parse rather than falling back.
- Empty / whitespace-only input now returns `Err` (distinct from
  "unknown value") so callers can log it separately.

Producer migration: worker/job.rs emits `JobResultStatus::Stuck`
directly via `serde_json::json!` so the wire string stays pinned to
`as_str()`. claude_bridge and acp_bridge keep emitting `"error"` on
the wire; the FromStr alias covers them without churn on those call
sites.

Added unit tests for each variant, the `"error"` alias, case
insensitivity, whitespace trimming, empty-string error, and
preservation of the original input in `JobResultStatusParseError`.

---------

Co-authored-by: Henry Park <henrypark133@gmail.com>
@github-actions github-actions Bot added size: XL 500+ changed lines 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: 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: pairing Pairing mode scope: docs Documentation risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs and removed size: XL 500+ changed lines labels Apr 20, 2026
@claude

claude Bot commented Apr 20, 2026

Copy link
Copy Markdown

Code review

Found 1 issue:

  1. [LOW:50] pendingAttachmentReads is declared as const but mutated with .push() and .splice() — should be let for accurate intent signaling. While it works in JavaScript (const prevents reassignment, not mutation), using let clearly indicates the variable is mutable.

https://github.com/anthropics/ironclaw/blob/e88236ab08c007343587efd3efd984ca0ee39ce8/crates/ironclaw_gateway/static/js/core/bootstrap.js#L104

102	// array before composing the body so an Enter-press during file decode still
103	// includes the attachment.
104	const pendingAttachmentReads = [];
105	let authFlowPending = false;

Base automatically changed from staging-promote/e2544ed4-24637363119 to main April 21, 2026 03:18
@henrypark133
henrypark133 merged commit e88236a into main Apr 21, 2026
55 of 67 checks passed
@henrypark133
henrypark133 deleted the staging-promote/e88236ab-24647433078 branch April 21, 2026 03:18

This branch had an error being deployed

1 failed and 5 inactive deployments
Near Foundation Ironclaw / production — e88236ab Deployed Apr 20, 2026 by railway-app[bot]
ironclaw-nearai / production — e88236ab Deployed Apr 20, 2026 by railway-app[bot]
venice-ironclaw / production — e88236ab Deployed Apr 20, 2026 by railway-app[bot]
cosmose-ironclaw / production — e88236ab Deployed Apr 20, 2026 by railway-app[bot]
Ironclaw-QA / production — e88236ab Deployed Apr 20, 2026 by railway-app[bot]
humble-cat / staging-cameron — e88236ab Deployed Apr 20, 2026 by railway-app[bot]
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: db/postgres PostgreSQL backend scope: db Database trait / abstraction scope: docs Documentation scope: extensions Extension management scope: llm LLM integration scope: orchestrator Container orchestrator scope: pairing Pairing mode scope: tool/builtin Built-in tools scope: worker Container worker staging-promotion

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants