Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
146861d
release: prepare ClawFix 0.12.0
Jul 27, 2026
90502e6
ci: bind TUI release assets to exact tag
Jul 27, 2026
f5ff6b2
fix: refresh diagnostic script hash
Jul 27, 2026
2ed22ce
fix(cli): make repair transactions fail safely
Jul 27, 2026
1d9af7d
fix(cli): verify repair rollback state
Jul 27, 2026
9c878c9
feat: generate versioned capability contract
Jul 27, 2026
6e9de0f
docs: link versioned capability contract
Jul 27, 2026
86b87fc
Merge commit '1d9af7dcb7ac1ccc4d256f222ec9128231b72574' into cad/rele…
Aug 1, 2026
5c97378
Merge commit '6e9de0febd002e76c69e80649367a81a8fe13c1d' into cad/rele…
Aug 1, 2026
874997c
fix(release): smoke extracted TUI artifacts
Aug 1, 2026
cda2081
fix(cli): fail closed on ambiguous repair results
Aug 1, 2026
90c2fa3
feat(ops): add production continuity verifier
Aug 1, 2026
356ffcc
fix(cli): close repair result bypasses
Aug 1, 2026
8b315fb
docs: generate 0.12 capability candidate
Aug 1, 2026
9effc22
fix(ops): fail closed on continuity drift
Aug 1, 2026
85a7a6f
test(release): assert archive executable modes
Aug 1, 2026
575b38d
fix(ops): reject incomplete continuity evidence
Aug 1, 2026
e789fb8
fix(cli): preserve invalid terminal metadata
Aug 1, 2026
b81a51a
fix(cli): reject undefined terminal markers
Aug 1, 2026
ab9f3c2
fix(ops): require useful canary output
Aug 2, 2026
d38a4f4
test(cli): cover partial terminal markers
Aug 2, 2026
f0adfa4
merge: integrate production continuity
Aug 2, 2026
5df9448
docs: correct 0.12 release date
Aug 2, 2026
263274c
docs: mark 0.12 capability contract published
Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,30 @@ jobs:
npm pack ./cli --dry-run --json --cache /tmp/clawfix-npm-cache > "$manifest"
node scripts/verify-cli-package.mjs "$manifest"

tui:
name: TUI tests and typecheck
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.21

- name: Install TUI dependencies
working-directory: cli/tui
run: bun install --frozen-lockfile

- name: Test TUI
working-directory: cli/tui
run: bun test

- name: Typecheck TUI
working-directory: cli/tui
run: bunx tsc --noEmit

container:
name: Production container
runs-on: ubuntu-latest
Expand Down
77 changes: 77 additions & 0 deletions .github/workflows/production-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Production continuity verification

on:
workflow_dispatch:
inputs:
metered_mode:
description: Optional single paid canary for this run
required: true
default: none
type: choice
options:
- none
- agent
- diagnose
schedule:
- cron: "17 4 * * *"

permissions:
contents: read

concurrency:
group: production-continuity
cancel-in-progress: false

jobs:
verify-production:
name: clawfix.dev contract
runs-on: ubuntu-latest
timeout-minutes: 5
env:
CLAWFIX_API_TOKEN: ${{ secrets.CLAWFIX_API_TOKEN }}
CLAWFIX_CANARY_TOKEN: ${{ secrets.CLAWFIX_CANARY_TOKEN }}
CLAWFIX_SCHEDULED_CANARY: ${{ vars.CLAWFIX_SCHEDULED_CANARY }}
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Node 24
uses: actions/setup-node@v6
with:
node-version: "24"
package-manager-cache: false

- name: Verify public production contract
shell: bash
run: |
set -euo pipefail
mode="none"
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
mode="${{ inputs.metered_mode }}"
elif [ -n "${CLAWFIX_SCHEDULED_CANARY:-}" ]; then
# Scheduled paid traffic is opt-in. No repository variable means free checks only.
mode="$CLAWFIX_SCHEDULED_CANARY"
fi

case "$mode" in
none) canary_args=() ;;
agent) canary_args=(--agent-canary) ;;
diagnose) canary_args=(--diagnose-canary) ;;
*) echo "::error ::invalid canary mode: $mode"; exit 2 ;;
esac

version="$(node -p "require('./package.json').version")"
node scripts/verify-production.mjs \
--base-url https://clawfix.dev \
--expected-version "$version" \
"${canary_args[@]}" \
2>&1 | tee production-verification.json

- name: Retain verification evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: production-verification-${{ github.run_id }}
path: production-verification.json
if-no-files-found: warn
retention-days: 30
166 changes: 157 additions & 9 deletions .github/workflows/release-tui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ name: Release TUI binaries

on:
workflow_dispatch:
inputs:
targets:
description: Comma-separated TUI targets
required: false
default: linux-x64,linux-x64-baseline
push:
tags:
- "v*"
Expand All @@ -24,8 +19,48 @@ concurrency:
cancel-in-progress: false

jobs:
test-tui:
name: TUI tests and typecheck
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Verify release identity
shell: bash
run: |
set -euo pipefail
root_version="$(node -p "require('./package.json').version")"
cli_version="$(node -p "require('./cli/package.json').version")"
test "$root_version" = "$cli_version"
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
[[ "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
test "$GITHUB_REF_NAME" = "v$root_version"
test "$(git rev-list -n 1 "$GITHUB_REF_NAME")" = "$GITHUB_SHA"
fi

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.21

- name: Install TUI dependencies
working-directory: cli/tui
run: bun install --frozen-lockfile

- name: Test TUI
working-directory: cli/tui
run: bun test

- name: Typecheck TUI
working-directory: cli/tui
run: bunx tsc --noEmit

build-tui:
name: tui-${{ matrix.target }}
needs: test-tui
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
Expand Down Expand Up @@ -80,7 +115,7 @@ jobs:
windows-x64) pkg="@opentui/core-windows-x64" ;;
*) echo "unknown target"; exit 1 ;;
esac
bun add --optional "${pkg}@0.4.5" || true
bun add --optional --no-save "${pkg}@0.4.5"

- name: Build standalone binary
env:
Expand Down Expand Up @@ -117,6 +152,15 @@ jobs:
# a session nobody can type into.
CLAWFIX_TUI_REQUIRE_PTY=1 node scripts/smoke-tui-interactive.mjs "$launcher"

- name: Smoke musl binary on Alpine
if: matrix.target == 'linux-x64-musl'
run: |
docker run --rm \
-v "$PWD:/work" \
-w /work \
node:24-alpine \
sh -lc 'apk add --no-cache python3 >/dev/null && launcher=/work/dist/tui/clawfix-tui-linux-x64-musl && test -x "$launcher" && node scripts/smoke-tui-binary.mjs "$launcher" && CLAWFIX_TUI_REQUIRE_PTY=1 node scripts/smoke-tui-interactive.mjs "$launcher"'

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
Expand All @@ -132,17 +176,50 @@ jobs:
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- name: Checkout exact release source
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.sha }}

- name: Setup Node 24
uses: actions/setup-node@v6
with:
node-version: "24"
package-manager-cache: false

- name: Download all TUI artifacts
uses: actions/download-artifact@v4
with:
path: release-tui
pattern: clawfix-tui-*
merge-multiple: false

- name: Package target tarballs
- name: Revalidate remote tag before packaging
run: |
set -euo pipefail
remote_sha="$(git ls-remote --tags origin "refs/tags/$GITHUB_REF_NAME^{}" | cut -f1)"
if [ -z "$remote_sha" ]; then
remote_sha="$(git ls-remote --tags origin "refs/tags/$GITHUB_REF_NAME" | cut -f1)"
fi
test -n "$remote_sha"
test "$remote_sha" = "$GITHUB_SHA"

- name: Package and smoke final target tarballs
run: |
set -euo pipefail
mkdir -p release-dist
mkdir -p release-dist release-smoke
assert_archive_mode() {
local tarball="$1"
local member="$2"
local listing mode
listing="$(tar -tvzf "$tarball" "$member")"
read -r mode _ <<< "$listing"
if [ "$mode" != "-rwxr-xr-x" ]; then
echo "::error ::archive member $member in $tarball has mode $mode, expected -rwxr-xr-x"
exit 1
fi
}
# download-artifact layout with merge-multiple=false:
# release-tui/clawfix-tui-<target>/{launcher,.bin,assets-<target>/,...}
shopt -s nullglob
Expand All @@ -158,7 +235,49 @@ jobs:
ls -la "$dir" || true
exit 1
fi
tar -C "$dir" -czf "release-dist/clawfix-tui-${target}.tar.gz" .

# actions/upload-artifact intentionally normalizes regular files to 0644. Restore
# executable modes after download so the release archive contains runnable files.
chmod 0755 "$launcher" "$binary"

tarball="release-dist/clawfix-tui-${target}.tar.gz"
tar -C "$dir" -czf "$tarball" .
assert_archive_mode "$tarball" "./clawfix-tui-$target"
assert_archive_mode "$tarball" "./clawfix-tui-$target.bin"

# Release evidence starts after packaging. Extract into a fresh directory and smoke
# only those bytes; build-tree checks cannot prove the public download is runnable.
smoke_dir="release-smoke/$target"
rm -rf "$smoke_dir"
mkdir -p "$smoke_dir"
tar -xzf "$tarball" -C "$smoke_dir"
smoke_launcher="$smoke_dir/clawfix-tui-$target"
smoke_binary="$smoke_dir/clawfix-tui-$target.bin"
test -x "$smoke_launcher"
test -x "$smoke_binary"
test "$(stat -c '%a' "$smoke_launcher")" = "755"
test "$(stat -c '%a' "$smoke_binary")" = "755"
test -d "$smoke_dir/assets-$target"
node scripts/verify-tui-artifact.mjs "$smoke_binary" --target "$target"

if [ "$target" = "linux-x64-musl" ]; then
docker run --rm \
-v "$PWD:/work" \
-w /work \
node:24-alpine \
sh -lc 'set -eu
apk add --no-cache python3 >/dev/null
smoke_launcher=/work/release-smoke/linux-x64-musl/clawfix-tui-linux-x64-musl
smoke_binary=/work/release-smoke/linux-x64-musl/clawfix-tui-linux-x64-musl.bin
test -x "$smoke_launcher"
test -x "$smoke_binary"
node scripts/smoke-tui-binary.mjs "$smoke_launcher"
CLAWFIX_TUI_REQUIRE_PTY=1 node scripts/smoke-tui-interactive.mjs "$smoke_launcher"'
else
node scripts/smoke-tui-binary.mjs "$smoke_launcher"
CLAWFIX_TUI_REQUIRE_PTY=1 node scripts/smoke-tui-interactive.mjs "$smoke_launcher"
fi

echo "packed clawfix-tui-${target}.tar.gz"
packed=$((packed + 1))
done
Expand All @@ -174,9 +293,38 @@ jobs:
test -s TUI-SHA256SUMS
)

- name: Attest TUI release tarballs
uses: actions/attest-build-provenance@v3
with:
subject-path: release-dist/*.tar.gz

- name: Wait for CLI workflow to create the GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
for attempt in $(seq 1 60); do
if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
exit 0
fi
sleep 10
done
echo "::error ::CLI release was not created within 10 minutes"
exit 1

- name: Revalidate remote tag before upload
run: |
set -euo pipefail
remote_sha="$(git ls-remote --tags origin "refs/tags/$GITHUB_REF_NAME^{}" | cut -f1)"
if [ -z "$remote_sha" ]; then
remote_sha="$(git ls-remote --tags origin "refs/tags/$GITHUB_REF_NAME" | cut -f1)"
fi
test -n "$remote_sha"
test "$remote_sha" = "$GITHUB_SHA"

- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
files: |
release-dist/*.tar.gz
release-dist/TUI-SHA256SUMS
Expand Down
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,36 @@ ClawFix follows semantic versioning for the published npm CLI. GitHub releases a

## Unreleased

## 0.12.0 - 2026-08-02

- Expanded the guarded repair catalog from 1 to 5 entries: `gateway-not-running` (needs systemd/launchd), plus four config toggles applied and verified through OpenClaw's own CLI — `auto-update-enabled-warning`, `gateway-loopback-no-auth`, `no-hybrid-search`, and `no-memory-flush`. Every toggle shares one `configToggleRepair` contract with a read-back verification and a rollback path.
- Fixed `checkGatewayRunning()` reporting a repair as applied when nothing was actually listening on the gateway port; the listening port is now the sole verdict, never process-status prose or PIDs.
- Closed two unauthenticated webhook holes: the Resend/Svix inbound-email relay and the Lemon Squeezy payment webhook both previously skipped signature verification when unconfigured, or verified against the re-serialized body instead of the raw bytes. Both now fail closed on a missing secret, missing raw body, or bad signature, with constant-time comparison.
- Removed the payment surface entirely (`/api/checkout`, the payment page, the Lemon Squeezy webhook handler, and the unused verifier) after finding a configured store could charge a customer without recording the payment anywhere. `clawfix.dev` has no paid tier and no payment path.
- Added a Linux musl (Alpine) target to the standalone OpenTUI binary build and release matrix; ClawFix now runs on Alpine-based OpenClaw containers.
- Fixed the remote-repair flow silently dropping server-proposed repairs; the TUI now resolves `repair.proposed` events against a local finding before opening the approval dialog, and never renders a server-authored plan as if it were local.
- Added a TUI quit path (Ctrl+D, or Ctrl+C while idle); previously only `SIGTERM`/`SIGKILL` could end a session.
- Fixed the TUI sidebar renumbering findings by severity while `fix <#>`/`explain <#>` indexed the unsorted list, so a number shown in the sidebar could resolve to the wrong finding.
- Fixed the TUI status line printing the scan revision twice, which pushed the finding count off the end of the line.
- Findings are no longer titled with raw machine text (a timed-out probe surfaced as `[critical] timeout`; a parser exception surfaced its stack-trace fragment as the headline). Both now carry an actionable headline with the original text preserved as detail.
- Verified 5/5 real break-fix scenarios end-to-end against a live OpenClaw install: gateway killed, port conflict, corrupted config, loopback auth disabled, and auto-update left on all detect correctly; the repair pipeline reports `applied` or `verify_failed` truthfully against a rescan in every case.
- The port conflict ClawFix detects (a squatting process holding the gateway port) is deliberately not an automatic repair — every version of it ends in killing a process ClawFix does not own. Left as diagnosis and guidance only.
- Fixed `gateway-loopback-no-auth` reporting `applied` when `openclaw config set gateway.auth.mode token` succeeded but the follow-up `doctor --fix --generate-gateway-token` failed. The repair now restores the previous auth mode after token-generation failure, and the engine treats any nonzero or timed-out apply result as `verify_failed` before a later state check can create fake success.
- Reviewed open issue #8 (four detector candidates from superseded PR #2: persisted `__OPENCLAW_REDACTED__` placeholders, incomplete global npm installs, config written by a newer OpenClaw than the installed CLI, and structured update availability). The historical incident evidence is real, but the old patch predates the current diagnostics core and provides no current-main synthetic contracts. Per the issue's acceptance criteria, these remain separate focused follow-ups rather than being copied into 0.12.0 without fresh positive/negative fixtures.
- Added a second consent check at the TUI network boundary. A direct adapter caller can no longer upload a diagnostic or chat message with `consentGranted: false` even if a future UI refactor bypasses the privacy dialog.
- Fixed the standalone TUI ignoring the documented `CLAWFIX_API` custom-server variable and `CLAWFIX_API_TOKEN`; protected self-hosted servers now receive the bearer header consistently with the portable CLI.
- Expanded text redaction to cover generic bearer credentials, complete `Cookie:` headers, and Slack `xox*` tokens before diagnostics, errors, or chat content cross the network boundary.
- Bounded both agent-v2 and legacy `/api/chat` conversation stores by last activity and hard caps on every response path, including AI-disabled fallback and provider failures. Active long-lived chats no longer expire merely because their original creation time crossed the TTL.
- Connected client disconnects to upstream AI cancellation so abandoned requests stop provider work and release the shared concurrency slot instead of running to the provider timeout.
- Escaped results-page error text before assigning HTML, closing a DOM-injection sink.
- Fixed the portable interactive CLI omitting catalog-only repairs from its authorization set and replaced gateway-specific success/failure copy with repair-accurate outcomes.
- Added mandatory TUI tests and typechecking to CI and release builds, made native dependency installation fail closed, added real Alpine PTY smoke tests for the musl artifact, and attached GitHub build provenance attestations to TUI tarballs.
- Redacted free-text TUI messages in both the exact consent preview and final request, then bounded remote SSE time, bytes, incomplete-frame buffering, and assistant text so a hostile or wedged peer cannot keep the UI busy or grow memory indefinitely.
- Strengthened the release PTY smoke to require the literal typed probe in the composer and exit status zero; periodic redraws and crash-on-exit binaries now fail the gate.
- Disabled unsafe `fix-all` batch execution and changed remaining legacy repair prompts to default no. Every executable repair now needs its own current-state plan, explicit approval, verification, and rollback outcome.
- Added durable Resend `svix-id` idempotency when PostgreSQL is configured, a bounded single-process fallback when it is not, and retryable 503 behavior after failed forwarding.
- Corrected public repair, package-boundary, privacy, retention, and artifact-integrity language; the site no longer promises universal backups, temporary database retention, model-authored coverage, or reproducible binaries it cannot prove.

## 0.11.2 - 2026-07-24

- Shipped the full post-0.10.0 mainline as one end-to-end release: installer, hosted service, npm CLI, and OpenTUI standalone assets.
Expand Down
Loading