* fix(ui): show toast error when package install/uninstall fails
HttpClient throws ApiError on HTTP 500 responses, but installPackage
and uninstallPackage hooks did not catch the exception. This caused
the error to propagate past the toast logic, so users saw no feedback
when a system package failed to install.
Wrap http.post calls in try/catch to always show toast with the server
error message. Update PackageSection handlers to check res.ok instead
of relying on try/catch for flow control.
* fix(ui): truncate verbose package error in toast notification
Extract last meaningful error line (e.g. "ERROR: ...") from package
manager output instead of dumping the full multi-line response into
the toast, which caused text overflow.
* fix(ui): wrap long text in toast instead of truncating
Add break-words and min-w-0 to toast content so long error messages
(URLs, package manager output) wrap properly within the toast element.
Remove shortError helper — show full error with proper wrapping.
* fix(ui): constrain toast container width to prevent overflow
Move max-w-sm from toast item to the fixed container so long text
(URLs, multi-line errors) is properly bounded. Add overflow-hidden
on toast items as a safety net.
* fix(pkg-helper): re-add packages cascade-removed by apk del
apk del removes orphaned dependencies, which may include other
user-installed packages if they were pulled in as transitive deps.
After uninstalling, check which persisted packages are still present
and re-install any that were accidentally removed.
* revert: remove cascade re-add logic from pkg-helper
apk del does not cascade-remove other world packages (verified via
Docker test). The re-add logic was based on incorrect assumption.
Root cause of the all-packages-deleted issue needs further investigation.
The emitRun wrapper in runLoop (loop.go) is missing TenantID, causing
all streaming events (chunk, thinking, tool.call) to be emitted with
TenantID=Nil. The event filter (event_filter.go:43-44) blocks unscoped
events for non-admin clients, so tenant-bound API key users receive no
streaming — only run.started and run.completed (emitted from Run() in
loop_run.go which already sets TenantID).
Fix: add event.TenantID = l.tenantID, matching the pattern in
loop_run.go's emitRun wrapper.
* fix: add panic recovery to tool, cron, and summarization goroutines
A panicking tool, cron job, or auto-summarization goroutine crashes
the entire server process — disconnecting all clients and aborting
all running agents. Add defer/recover at three levels:
- safeExecute() in tools/registry.go: catches panics from any tool's
Execute method and returns an error result to the LLM
- Agent loop goroutine (loop.go): defense-in-depth for non-tool code
in the parallel tool execution goroutine
- Cron execution goroutine (service_execution.go): prevents a single
panicking cron job from taking down the server
- Summarization goroutine (loop_history.go): prevents background
auto-summarization panics from crashing the process
All recovery points log the panic value and stack trace at ERROR level
for debugging. Tool execution panics return actionable error messages
to the LLM so the agent can recover gracefully.
* refactor: extract panic recovery into safego.Recover helper
DRY the duplicated recover+stack+log boilerplate across 4 goroutine
sites into a shared safego.Recover(onPanic, attrs...) function.
Stack buffer bumped from 4096 to 8192 bytes.
---------
Co-authored-by: viettranx <viettranx@gmail.com>
* fix: clear cron session in DB when not cached (cold-cache after restart)
Fixes#365
PGSessionStore.Reset() only cleared the in-memory cache. After a server
restart the cache is empty, so Reset was a no-op — the next GetOrCreate
loaded the full accumulated history from DB, causing LLM tool loops from
contradictory context.
Add a DB fallback: when the session isn't in cache, issue a direct
UPDATE to clear messages and summary in PostgreSQL. This ensures cron
sessions always start clean regardless of server restarts.
The #294 fix (Reset+Save before each cron run) already had the right
intent but only worked within the same server lifetime.
* fix: move DB call outside mutex and log errors in Reset cold-cache path
ExecContext was running under s.mu.Lock(), blocking all session cache
operations during DB round-trip. Release the lock before the DB call.
Also log ExecContext errors instead of silently discarding them —
silent failure defeats the purpose of the cold-cache fix.
---------
Co-authored-by: viettranx <viettranx@gmail.com>
- Add RoleOwner ("owner") to permissions hierarchy (above admin)
- Replace all IsCrossTenant/client.IsCrossTenant() permission guards with
IsOwnerRole/client.IsOwner() role-based checks (20+ locations)
- Remove cross-tenant bypass from tenantClauseN SQL helper — always adds
AND tenant_id = $N (fail-closed)
- Add explicit unscoped store methods for startup: ListAllProviders,
ListAllInstances, ListAllEnabled, GetByIDUnscoped, GetTeamUnscoped
- Remove CrossTenant from auth layer: owner users get role "owner" with
concrete tenant ID (MasterTenantID fallback), no bypass flag
- Remove crossTenant field from WS Client; connect response sends
is_owner instead of cross_tenant
- Simplify event filter from 3-mode to 2-mode (tenant-scoped + owner sees system events)
- Fix channel instance loader to use inst.TenantID for agent lookup
- Fix agent embedding goroutine to capture tenant from caller context
- Fix provider cache invalidation to use event.TenantID
- Separate skills store: ListAllSystemSkills (system only) vs
ListAllSkills (system + tenant-scoped), StoreMissingDeps restricted
to system skills only
- Frontend: isCrossTenant → isOwner, add "owner" to UserRole type,
RequireAdmin uses hasMinRole for owner > admin hierarchy
The requireAuth and requireAuthBearer middlewares correctly detect
cross-tenant admin status (gateway token + owner user ID) but never
call store.WithCrossTenant(ctx). This causes all HTTP tenant management
endpoints (GET/POST /v1/tenants, etc.) to return 403 "insufficient role"
because handlers check store.IsCrossTenant(ctx) which was always false.
The WebSocket path works correctly because the gateway Client object
tracks cross-tenant status independently via client.IsCrossTenant().
Fix: Add store.WithCrossTenant(ctx) in both auth functions when
auth.CrossTenant is true, matching the design of the WS path.
Affected endpoints:
- GET/POST /v1/tenants (list, create)
- GET/PATCH /v1/tenants/{id} (get, update)
- GET/POST/DELETE /v1/tenants/{id}/users (membership)
- Any future endpoint checking store.IsCrossTenant(ctx)
* fix: check errors in cron store Scan and Unmarshal to prevent data corruption
Three unchecked errors in the cron store could silently corrupt data:
1. cron_update.go:44 — QueryRowContext().Scan() error ignored when
fetching current schedule during partial updates. If DB fails,
all schedule fields default to zero values, corrupting schedule_kind
to empty string and bypassing all validation.
2. cron_update.go:167 — json.Unmarshal error ignored when parsing
existing payload for merge. Corrupted JSON leaves payload zero-valued,
wiping all existing fields (Message, Deliver, Channel, To) when
only one field was being updated.
3. cron_scan.go:63 — json.Unmarshal error ignored when scanning cron
job rows. Malformed payload JSON silently returns zero-valued payload,
masking data corruption.
All three now return descriptive errors instead of proceeding with
corrupted data. NULL payload (valid case) is handled separately.
* fix: add NULL payload guard and check Marshal error in cron update
- Add len(payloadJSON) > 0 guard before Unmarshal in UpdateJob payload
merge path, consistent with the same guard in scanCronRow. Without
this, NULL payload (valid for new jobs) would return a false error
instead of proceeding with the merge.
- Check json.Marshal error instead of discarding it, consistent with
the error-checking theme of the parent commit.
---------
Co-authored-by: viettranx <viettranx@gmail.com>
- Introduced StripMessageDirectives in the agent loop to remove internal
[[name:value]] routing tags from user-facing content while preserving
them in session store for model context
- Narrowed regex from wildcard `(?s)\[\[.*?\]\]` to structured
`\[\[\w+(?::[^\]\n]+)?\]\]` to avoid false positives on legitimate content
- Preserved [[tts...]] tags for TTS AutoTagged pipeline compatibility
- Added 15 unit tests covering strip, preserve, and edge cases
- Fixed step numbering in agent loop (steps 5-9)
- Update README Docker section to use `make up` instead of raw
docker compose commands. `make up` handles network creation,
version embedding from git tags, and database migrations.
- Add WITH_* flags to Makefile for optional compose services:
WITH_BROWSER, WITH_OTEL, WITH_SANDBOX, WITH_TAILSCALE, WITH_REDIS.
Uses WITH_ prefix to avoid conflicts with common env vars (e.g.
BROWSER is set by many desktop environments).
- Document common make commands (up/down/logs/reset) and optional
service flags with descriptions in a table format.
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
MCP bridge now returns 403 when no gateway token is configured instead
of serving tools without authentication. Startup warnings added for
default Postgres password and open CORS configuration.
When using draft_transport in Telegram DMs, the preview draft sometimes
stays in the input area after the real message is delivered, showing a
brief duplicate reply.
Clear the stale draft by sending an empty sendMessageDraft as soon as
the final message is sent in Send(). The cleanup is asynchronous and
best-effort so it won't block message delivery.
Co-authored-by: Badgerbees <rapabelias@gmail.com>
- Fix hardcoded MIT license → CC BY-NC 4.0 with link to CC website
- Move About button from user menu dropdown to topbar header
- Shorten menu label to "About" / "Giới thiệu" / "关于"
* feat(ui): add About GoClaw dialog to user menu
- Add AboutDialog showing version, source code, license, docs, and bug report links
- Add "About GoClaw" item to UserMenu (with Info icon)
- Add i18n for en, vi, zh
* fix(docker): pass VERSION build arg to Dockerfile
Without this, Docker builds always produce `dev` as the version string.
Users can set GOCLAW_VERSION in .env or environment to embed the correct
version (e.g. from `git describe --tags --always`).
* feat(ui): show update status in About dialog
- Clean version display (strip git commit hash suffix)
- Show "Up to date" badge when on latest version
- Show update banner with link to release notes when new version available
- Fetch health data on dialog open to get latest update info
- Add i18n keys for update status (en, vi, zh)
* refactor(ui): clean version display across all UI locations
Extract cleanVersion() to shared utility. Apply to:
- About dialog (already had it inline)
- Sidebar connection status footer
- Overview page header
"v2.5.1-3-g4fd653c1" now displays as "v2.5.1" everywhere.
* feat(ui): show version in System Health card instead of header
- Add version cell with Tag icon to SystemHealthCard grid
- Green check when up to date, red when update available
- Revert cleanVersion from overview header (keep upstream behavior)
- Add i18n for systemHealth.version (en, vi, zh)
* fix(ui): move version to System Health card header to preserve grid layout
Version now shows in the card header row (right side) instead of as a
grid cell, keeping the original 6-cell (2x3) layout intact.
---------
Co-authored-by: huyluseo <thanhhuylu@example.com>
1. Stale user_context_files after agent file update (#294.1)
- Added PropagateContextFile() to AgentStore — single CTE query copies
agent-level file to all existing user instances
- Added `propagate` param to agents.files.set WS handler
- Response includes `propagated` count
2. Cron session reuse — tool errors block future runs (#294.2)
- Reset + Save cron session before each run
- Each run starts clean, persisted to DB to survive restarts
3. Write-behind cache risk — crash loses messages (#294.3)
- Added periodic checkpoint flush every 5 tool iterations
- Messages persisted to session incrementally during long runs
- Token calibration tracks checkpointed messages correctly
4. Silent cron delivery failures (#294.4)
- Added slog.Warn when deliver=true but channel/chatID missing
Closes#294
All topbar items (Documents, Language, Timezone, User menu) now use
text-sm consistently. Previously, non-select items used text-xs (12px)
while Select triggers inherited md:text-sm (14px) from the base
SelectTrigger component, causing a visible mismatch.
Two issues fixed:
1. effectiveRestrict() now always returns true (multi-tenant security),
so the "unrestricted" test case expected wrong results — paths outside
workspace are now blocked regardless of the restrict parameter.
2. Tests used bare Unix paths (/etc/passwd) which are not absolute on
Windows (no drive letter), causing them to resolve inside the workspace
and pass incorrectly. Added outsidePath() helper that prepends the
volume name for cross-platform absolute paths.
* fix(vision): fix Lark inline image parsing and MCP bridge read_image
Two fixes for image reading through Lark channel with claude-cli provider:
1. Lark post messages use flat format {"content":[[...]]} but resolvePostElements
only handled language-wrapped format {"zh_cn":{"content":[...]}}. Now checks
flat format first, and skips non-map values in fallback iteration.
2. read_image tool failed via MCP bridge because bridge creates fresh HTTP context
losing WithMediaImages. Fix: enrich <media:image> tags with path attribute
(like documents), pass workspace through X-Workspace bridge header with HMAC,
so read_image can load images from disk via path parameter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): add HMAC backward compat and guard workspace injection
- VerifyBridgeContext falls back to old signature format (without workspace)
for sessions with pre-existing MCP configs, preventing 403 on server restart
- Workspace context injection now requires agent context (agentID/userID)
to prevent unauthenticated path injection bypassing HMAC
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: viettranx <viettranx@gmail.com>
Add ?user_id query param to MCP credential endpoints so system admins
and tenant admin/owner can manage credentials for other users.
Backend:
- Add resolveTargetUserID() with permission checks (system admin,
tenant admin/owner) and cross-tenant defense-in-depth
- Add TenantStore dependency to MCPUserCredentialsHandler
Frontend:
- Add searchable Combobox user selector for admin users in the
credentials dialog (visible only when canManageUsers)
- Update credential API calls to pass optional userId param
- Add i18n keys for admin mode (en/vi/zh)
Replace per-handler `token string` field with package-level
`pkgGatewayToken` via `InitGatewayToken()`, matching the existing
pattern for `pkgAPIKeyCache`, `pkgOwnerIDs`, etc.
- Remove `token` from ~30 HTTP handler structs and constructors
- Simplify `requireAuth(minRole, next)` signature (was 3 params)
- Simplify `resolveAuth(r)` signature (was 2 params)
- Rename `resolveAuthBearer` → `resolveAuthWithBearer` (clearer)
- Add `store.WithRole()`/`RoleFromContext()` to propagate caller
role through context
- Inject role into context in `requireAuth` and `requireAuthBearer`
Block team lead from being assigned as task owner across all dispatch
paths. When a leader assigns a task to itself, it gets dispatched to a
team session while still running on the main session — causing race
conditions, duplicate work, and potential infinite announce loops.
Guards added:
- executeCreate: reject assigneeID == team.LeadAgentID
- dispatchTaskToAgent: safety net auto-fail (retry/ticker edge cases)
- DispatchUnblockedTasks: auto-fail leader-owned pending tasks
- ProcessPendingTasks: skip before AssignTask
- executeRetry: explicit error before dispatch
- TeamsMethods.dispatchTaskToAgent: guard for WS/dashboard path
Also removed redundant GetTeamForAgent DB call in dispatchTaskToAgent
by passing *store.TeamData directly from callers.
- Remove "API Docs" from sidebar menu
- Add external "Documents" link (docs.goclaw.sh) to topbar header
- Remove unused notification bell icon from topbar
* feat(gateway): add version update checker
Check GitHub releases periodically (1h) and surface update availability
in the health endpoint + dashboard header. Version is auto-detected from
git tags via Makefile, falling back to VERSION file or build arg.
Backend: new UpdateChecker goroutine, health response includes
latestVersion/updateAvailable/updateUrl fields.
Frontend: version badge in overview header with clickable "available"
link when a newer release exists.
* fix(gateway): fix update checker compile errors and harden GitHub API call
- Export UpdateChecker/NewUpdateChecker to match server.go references
- Move StartUpdateChecker(ctx) after ctx declaration in gateway startup
- Add User-Agent header for GitHub API compliance
- Limit response body to 1MB via io.LimitReader
- Use strconv.Atoi instead of manual int parsing in parseSemver
---------
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
Co-authored-by: viettranx <viettranx@gmail.com>
Channel factories create PendingHistory before InstanceLoader calls
SetTenantID on the channel, so tenantID was always uuid.Nil causing
"tenant_id required" errors on compaction sweep and auto-compact.
Fix: add SetTenantID to PendingHistory and propagate it from
InstanceLoader after channel creation. Also fix HTTP compact endpoint
using context.Background() instead of preserving request tenant context.
- use-chat-sessions, skill-upload-dialog: replace crypto.randomUUID()
with uniqueId() which has a UUID v4 fallback for non-secure contexts
(HTTP without localhost) where crypto.randomUUID is unavailable
- use-chat-send: show localized error message when WebSocket is not
connected instead of silently dropping the send
- Add error.notConnected i18n key to en/vi/zh chat locale files
Closes#352
Post messages with embedded img tags were only producing [image]
placeholder text — the actual image_key was discarded and never
downloaded. Now extractPostImageKeys() collects image keys from
post JSON, and resolvePostImages() downloads them via the existing
Lark resource API. Also handles reply-to-post with images.
Refactored: shared resolvePostElements() helper for language variant
lookup, mediaMaxBytes() helper, and dedup via seen map.
Auto-compaction stopped working after server restarts because provider
resolution failure was silent and there was no retry mechanism. Add a
10-minute periodic sweep in the flusher loop that checks DB for groups
exceeding the compaction threshold, and warn when compaction provider
cannot be resolved at startup.
Normalize message timestamps to UTC in backend, extract shared
messageToTimestamp helper (DRY), and render chat times using the
user's selected timezone from the topbar setting.
- Add key props to cron routes (matching Agents/Sessions pattern)
- Fix React Rules of Hooks violation in CronPage (hooks after early return)
- Redesign overview tab: markdown preview/edit toggle for message,
info grid with icons, agent select dropdown instead of raw UUID input
- Redesign run history: expandable entries with markdown for summary/error
- Show agent display name instead of UUID in list row, header, and overview
- Add i18n keys (edit, preview, noMessage) for en/vi/zh
1. providers/anthropic_stream.go — check scanner.Err() after SSE loop;
truncated responses returned silently on network errors
2. config/config_load.go — trim whitespace in GOCLAW_OWNER_IDS;
trailing spaces caused owner exact-match failures
3. cron/service.go — handle saveUnsafe() errors at 5 call sites;
disk I/O failures lost cron state silently
4. tasks/task_ticker.go — separate timeouts for followups (15s) and
recovery (30s); shared context risked followups starving recovery
5. hooks/use-ws-event.ts — useRef pattern for handler to prevent
subscribe/unsubscribe churn with inline functions
6. markdown-renderer.tsx — AbortController for file fetch with abort
on new fetch and on unmount
7. mcp-form-dialog.tsx — auto-split Command field when user pastes
full command; fix splitShellTokens to preserve commas inside quotes
Replace hardcoded "0.2.0" in WS connect response with the actual
build-injected version. Wire server version through WS client → auth
store → sidebar footer so it displays next to connection status.
- Add comment_type column (note/blocker) with migration 000028
- Blocker comments auto-fail task, cancel member session via
EventTeamTaskFailed broadcast, and escalate to leader via
InboundMessage (system:escalation bypasses debounce)
- Enrich audit events: fix tenant scope bug (context.Background →
WithTenantID), add 4 missing types (commented/progress/updated/stale),
populate data field (reason, comment_text, progress_percent)
- Add blocker escalation config toggle in team settings UI
- Add audit logs modal with paginated team events (Clock icon)
- Update features modal: all features now available (no coming soon)
- Update code comments: reviewer role not yet active, all flows via leader
- Update docs: remove v1/v2 versioning, add blocker escalation and
review workflow sections, document enriched audit events
- Add lead hint in search/list results with member+model info and task
creation guidelines (description quality, complexity, model matching)
- Add member instructions in dispatch message (progress, comment, complete)
- Include member comments and attachments in announce content to leader
- Cap announce content at 50k runes to prevent context blowup
- Remove duplicate comment block in announce path
- Force all teams to v2: remove IsTeamV2 gate, v2Actions map,
isConsumerTeamV2, and v1 TEAM.md branch
- Replace version comparison modal with features showcase modal
- Remove version selector from team settings UI
Agent run goroutines can outlive the WebSocket client (e.g. after
reconnect). When the goroutine finishes and calls SendResponse/SendEvent,
the closed send channel causes a panic that crashes the server.
Add defer/recover to both methods to gracefully drop messages for
disconnected clients instead of panicking.
HTTP client timeout (30s) equaled getUpdates API timeout (30s), causing
context deadline exceeded errors (~210/hr) when Telegram held the
long-poll for the full duration. Increase HTTP client to 60s, reduce
getUpdates to 25s for 35s headroom.
Closes#361
Add missing deny patterns discovered during security audit:
- destructive_ops: halt, init/telinit 0|6, systemctl suspend/hibernate
- privilege_escalation: broaden su to block all forms (bare su, su root),
add doas, pkexec (CVE-2021-4034), runuser
- Provider list: change outer <button> to <div role="button"> to fix
invalid nested <button> HTML (delete button inside row button)
- Chat page: derive agentId via useMemo from URL session key instead of
useState initializer that only runs once (stale on session switch)
- Agent change no longer creates ghost sessions — navigates to /chat
and lets handleSend create session on first message
- Clear messages when navigating away from session (empty key only);
session-to-session switch still replaces atomically via loadHistory
- Split ROUTES.CHAT into CHAT (nav) + CHAT_PATTERN (route definition)
Closes#349