40 KiB
status, created, owner
| status | created | owner |
|---|---|---|
| building | 2026-07-30 | tbd |
Diagnostic logging
Decision: ADR-2026-07-30-file-backed-diagnostic-bundles
Implementation plan: diagnostic-logging
Why
Users can see frontend failures that leave no backend evidence, and support cannot rely on one known artifact containing recent backend and browser diagnostics. The product needs a clearly disclosed bundle that users can download and agents can inspect without continuously uploading browser console history or returning an unbounded log export.
What
Backend files and terminal output
- Every backend process targets
<Kandev home>/logs/backend-logs.log, where<Kandev home>is the resolvedKANDEV_HOME_DIRor its~/.kandevdefault, and writes there whenever the path is available. - Backend startup prints the absolute log-file path to stdout before the backend becomes ready.
- A normal run writes
infoand higher entries to the file andwarnand higher entries to stdout. - A debug run writes
debugand higher entries to the file while stdout remainswarnand higher. - An explicitly verbose run writes
infoand higher entries to both the file and stdout. logging.level/KANDEV_LOG_LEVELremains the supported override for the file threshold, andlogging.formatremains supported.logging.outputPath,logging.maxSizeMb,logging.maxBackups,logging.maxAgeDays, andlogging.compressare removed; the diagnostic path and daily retention policy are not configurable.backend-logs.logrepresents the current UTC calendar day. At UTC midnight, the backend atomically rolls it tobackend-logs-YYYY-MM-DD.logand creates a new owner-readable, owner-writable active file.- A restart during the same UTC day appends to
backend-logs.log. On the first startup after a day boundary, the previous active file is rolled to the date it represents before new entries are written. - Cleanup retains a rolling three-day UTC window: the current active day and at most the two preceding dated daily files. Older recognized daily files are removed; unrelated files in the log directory are preserved.
- Each daily backend file accepts at most 256 MiB. Once the active file reaches that bound, entries that would exceed it are dropped without blocking application work and are reflected in file-sink loss statistics. UTC rollover resumes normal writes into the next day's file.
- Rollover is crash-recoverable. A small owner-only rollover journal records the source day, source identity/size, destination, and copied offset before an existing dated destination is extended. Recovery resumes that transaction exactly once and never replaces an existing dated file.
- The structured backend in-memory ring buffer is removed. The System Logs page, Improve Kandev, and agent diagnostics use the files and bundles described below.
Error-toast reporting
- Every error toast displayed through either supported frontend toast system
is reported immediately to the backend and written as an
errorentry namedfrontend error toast. - Toast reporting includes the visible toast text plus rich browser context: the browser URL origin and pathname with query and fragment removed, browser identity fields, viewport dimensions, a client timestamp, the toast implementation source, the current task ID when it can be derived from a recognized task route or query parameter, a toast-emission call stack when available, and underlying error metadata when the caller still provides it.
- Toast reporting is best effort. A reporting failure does not suppress, replace, duplicate, or otherwise alter the original toast and does not create a retry toast.
- Frontend report bodies and individual fields are bounded before they reach the logger. The backend never treats a client-supplied value as a log level or log message template.
- The endpoint has an in-memory token bucket per authenticated identity
allowing 60 reports per minute with a burst of 20, plus a process-wide
bucket allowing 300 reports per minute with a burst of 100. Exhausted
buckets return
429 Too Many RequestswithRetry-After; the frontend silently discards that response. Independent byte buckets admit at most 64 KiB per minute per identity with a 64 KiB burst and 256 KiB per minute process-wide with a 256 KiB burst, measured from the bounded request body.
Browser console history
- The frontend intercepts
console.debug,console.info,console.warn, andconsole.error, pluswindow.errorandunhandledrejection, without changing their normal browser behavior. - Entries remain local to the browser until a diagnostic bundle explicitly requests them. Kandev does not continuously stream console logs to the backend.
- Browser history is stored in IndexedDB for a rolling three-day UTC window. Each browser profile has an opaque random installation ID shared by its tabs.
- Entries are partitioned by the current authenticated Kandev identity. A capture returns only that identity's partition; signing into another account in the same browser profile cannot expose the previous account's entries. Auth-disabled mode uses the existing synthetic single-user scope.
- The browser store keeps at most 10,000 entries and 20 MiB after serialization across all identity partitions. Each entry is capped at 64 KiB. Age is pruned first, then the oldest entries are evicted until both bounds hold.
- Entries contain a client timestamp, level, source, message, bounded JSON-safe arguments, available stack, full URL, and a task ID derived from a recognized task route when available.
- Console-call staging never retains an arbitrary caller object. It preserves
primitive values and bounded
Errorfields. Other objects, functions, DOM values, and proxies become reference-free type descriptors without walking prototypes or invoking getters. At most 20 arguments are inspected; strings are capped at 4 KiB, error messages at 8 KiB, and stacks at 16 KiB before the overall 64 KiB entry cap. - If IndexedDB is unavailable or fails, the frontend falls back to the existing 500-entry in-memory buffer. A resulting bundle identifies the degraded capture in its manifest.
Performance and resource contract
- Diagnostic capture is best effort and is never on an application-critical path. Emitting a backend log, displaying a toast, or invoking a browser console method must not wait for filesystem I/O, IndexedDB I/O, a network request, ZIP creation, or another browser tab.
- File and stdout use independent bounded asynchronous queues so a blocked
terminal cannot stall the authoritative file. The file queue accepts at most
8,192 entries or 8 MiB and reserves 2,048 entries or 2 MiB for
warn+. The stdout queue accepts at most 2,048 entries or 2 MiB and reserves 512 entries or 512 KiB forwarn+. An encoded entry over 256 KiB is dropped. Producers never wait for sink I/O;debug/infois shed before reserved capacity is used, and evenwarn+is dropped rather than blocking when its queue is full. Each sink records atomic loss counters by level and reason. - Graceful shutdown drains both queues for at most two seconds, then records
any remaining entries as lost.
DPanic,Panic, andFatalare terminal exceptions: the logger attempts the same bounded drain and writes one capped final entry directly to stderr before the process terminates. - Toast reporting begins after the original toast has been handed to its UI library and is never awaited. It performs bounded context collection, uses a two-second request deadline, and has no retry queue. "Immediately" means the report is scheduled in the same toast-emission turn, not that rendering waits for an HTTP round trip.
- Browser interception does only constant-time staging work on the console
call path, where constant-time means bounded by the fixed argument and field
limits above. Its reference-free staging queue holds at most 500 entries or
2 MiB. It persists at most 50 entries or 256 KiB per transaction, scheduled
after 250 ms or during browser idle time with a one-second timeout fallback.
IndexedDB opening, serialization, pruning, and quota handling never run
synchronously in the intercepted console call. A full staging queue drops
lower-priority
debug/infoentries first and records loss metadata; it never delays the original console call. - Bundle collection, JSONL writing, cleanup, and download preparation run
outside the route handler. An identity owns at most one active job, at most
eight jobs may collect or wait process-wide, and one archive build runs
process-wide. An equivalent source request reuses the existing job. A
different request from an identity with an active job returns
429; global saturation returns503; both includeRetry-After: 5. - A job accepts at most four browser profiles, 20 MiB per profile and 80 MiB total frontend data. Without ACP, backend payload remains capped at 160 MiB and frontend payload at 80 MiB. When ACP is selected, the per-source budgets are 96 MiB backend, 48 MiB frontend, 96 MiB ACP, and 2 MiB runtime index. Omitted sources do not transfer their budget to another source. Backend and ACP files are selected newest-first; when a selected file exceeds its remaining source budget, only its newest bytes are included and the manifest records the source byte range. Total uncompressed archive payload remains capped at 256 MiB and a job may use at most 384 MiB of temporary disk. Creation fails safely when that temporary-space budget is unavailable.
- ACP collection is request-driven, permits at most ten selected sessions,
fetches from at most two reachable executor-side
agentctlinstances at a time, and has a 30-second total collection deadline. It never continuously copies protocol frames to the backend. ACP collection, like frontend capture, runs outside product-critical paths and may produce a partial bundle. - Log payloads use ZIP
Storerather than CPU-heavy compression. Copy and ZIP writing use 1 MiB chunks and yield between chunks. Collection/build jobs have a five-minute hard lifetime; ready/partial archives expire 15 minutes after becoming downloadable. - The bundle manifest records capture, persistence, queue, and archive truncation/loss counters. Diagnostic failures and pressure must be visible in the artifact without creating recursive log traffic.
System Logs page and bundle contents
/settings/system/logsno longer renders a log tail, file table, individual file downloads, copy action, or refresh action.- The page states that a standard bundle does not read stored chat history, session transcripts, agent responses, prompts, tool messages, database rows, or workspace files. This is a source boundary, not a redaction guarantee: incidental content already emitted into a selected log may still appear.
- The frontend disclosure names
console.debug,console.info,console.warn,console.error, uncaught JavaScript errors, unhandled promise rejections, bounded browser/runtime context, and user-visible error toasts that are reported into backend logs. - The backend disclosure names structured application/runtime, HTTP/service, executor, integration, startup/shutdown, warning/error, identifier, path, performance, and diagnostic-loss events. Backend inclusion copies emitted file lines; it does not query product tables or construct transcripts.
- The page exposes one always-visible Customize bundle action. It opens source selection with backend and frontend selected by default; the optional runtime index and ACP evidence start unselected. Collection, preparing, ready/partial, busy-with-retry, and failure states remain on the page without navigating away.
- When the backend reports that raw ACP capture is enabled, ACP is an optional source in that same customizer. Selecting it requires the user to choose one or more eligible sessions before collection starts. ACP is never silently included by a one-click action. The source remains available when no eligible session exists; the picker then shows an explanatory empty state and keeps submission disabled.
- The ACP selection surface explicitly warns that raw and normalized protocol frames can contain full prompts, agent responses, tool arguments/results, workspace file content, MCP payloads, environment-derived values, and secrets. The standard no-transcript disclosure never appears to cover an ACP-inclusive bundle.
- ACP candidate rows show their authorized Kandev task title as a link that opens that task in a new tab. The title is available only to identify a picker row; it is not written to the runtime index, manifest, or archive. The picker provides explicit select-all and clear-selection controls. Select all includes the first eligible sessions within the backend's maximum rather than exceeding the collection limit.
manifest.jsonis always included and is not a selectable source. The optional runtime index contains only task/session IDs, agent/provider/model, status, timestamps, executor type, and ACP availability. It excludes task titles, descriptions, prompts, responses, tool payloads, file content, and stored message bodies.- Desktop renders source/session customization in a modal dialog. Phone uses an inset bottom drawer patterned after the shipped mobile picker/menu surfaces, with one internally scrolling body, a fixed disclosure/header, safe-area-aware actions, no horizontal overflow, and at least 44px touch targets. Both viewports share selection, validation, job, and download logic.
- The combined ZIP uses this layout:
manifest.json
backend/backend-logs.log
backend/backend-logs-YYYY-MM-DD.log
frontend/browser-01.jsonl
frontend/browser-02.jsonl
runtime/sessions.json
acp/session-01/raw-acp.jsonl
acp/session-01/normalized-acp.jsonl
backend/contains the recognized active and dated files still inside the three-day retention window. File bytes are copied without reformatting.frontend/contains one JSON Lines file per distinct responding browser profile. Multiple tabs from one browser profile are deduplicated. Client values never determine archive paths or filenames.runtime/sessions.jsonis present only when the runtime source is requested. It contains at most 500 authorized sessions updated within the backend three-day diagnostic window, newest first. When ACP sessions are selected, their rows are included even if they fall outside that window.acp/is present only for explicitly selected sessions while ACP debug capture is enabled. It contains raw and normalized retained JSONL frames collected on demand from host-resident files or reachable executor-sideagentctlinstances. Archive directories are server-numbered; client-supplied session IDs never become ZIP paths.manifest.jsonmaps each directory to its authorized task/session and records unavailable or truncated files.manifest.jsonidentifies requested and included sources, capture time, Kandev version/commit, OS/architecture, uptime and bounded runtime metrics, backend filenames, frontend browser/connection counts, storage mode, omitted or truncated data, selected ACP session availability and byte ranges, runtime index counts, capture timeouts, and archive expiry.- Bundle archives and working files are owner-only and live in a Kandev-owned temporary directory. User/API bundle jobs expire 15 minutes after creation.
- If frontend or ACP capture is unavailable or incomplete, a multi-source
bundle still becomes downloadable with backend files. Its state is
partial, and the manifest plus UI state explains each unavailable source/session. - Any backend/ACP byte-range truncation, frontend profile omission, runtime
index truncation, queue loss, or archive-cap truncation also makes the bundle
partial; the manifest records exact source byte ranges and aggregate loss counters.
Agent diagnostics
- Agents use the same bundle API and request only
backend,frontend, orall. A backend-only request never waits for a browser. - The task-mode MCP tool does not expose raw ACP or runtime-index selection. ACP capture is an explicit human download flow because its stronger content and permission contract must be reviewed before collection.
- Task-session agents normally use the task-mode
get_diagnostic_bundle_kandevMCP tool. It accepts only a source selection; it derives the task, session, and authenticated owner from the MCP dispatch context, creates the identity-owned job, waits for ready/partial, and materializes the ZIP as an owner-only file under the execution workspace's ignored.kandev/diagnostics/directory. It returns that local path and a manifest summary; it never accepts a user ID, task ID, output path, or token. scripts/kandev-logsremains the host-side fallback. Authentication-disabled instances need only a port. Authenticated use reads a PAT exclusively fromKANDEV_API_TOKEN; it never accepts or prints a token on the command line.- Agent guidance downloads the selected ZIP, extracts it into a fresh temporary directory, searches an exact task ID first, and broadens to session ID, route/error text, or a bounded time window when needed.
- The dev-only
/api/v1/system/debug/exportlog response is removed. Runtime metadata moves tomanifest.json; no replacement unbounded JSON log query is introduced. - Improve Kandev uses the same archive builder instead of writing snapshots
from the removed backend ring buffer. When Include logs is selected, the
initiating browser creates an authenticated
alljob and waits for ready/partial before task submission. It then calls an Improve Kandev lease endpoint with that caller-owned bundle ID and bootstrap directory; the backend verifies common ownership, copies the ZIP todiagnostic-bundle.zip, and returns the path appended to the task description. The bootstrap route never waits for frontend collection. The task-context copy retains the existing 24-hour cleanup window so a newly launched agent can read it.
API surface
POST /api/v1/system/logs/frontend-errors
The authenticated browser submits:
{
"client_timestamp": "2026-07-30T12:34:56.789Z",
"source": "sonner",
"task_id": "d7e9e92e-eeb3-41e3-86d9-b6eca5760ac1",
"title": "Failed to save settings",
"description": "The backend rejected the update",
"url": "http://localhost:38429/settings/system/logs?tab=current#tail",
"user_agent": "Mozilla/5.0 ...",
"language": "en-US",
"platform": "Linux x86_64",
"viewport": {
"width": 1440,
"height": 900
},
"stack": "Error: error toast emitted\n at ...",
"error": {
"name": "TypeError",
"message": "Failed to fetch",
"stack": "TypeError: Failed to fetch\n at ..."
}
}
sourceissonnerortoast-provider.titleanddescriptionare optional individually, but at least one must contain visible text.client_timestampis RFC 3339 when supplied. The backend log timestamp remains authoritative.task_idis optional and capped at 128 bytes. The frontend derives it only from recognized/t/:taskId,/tasks/:taskId,/office/tasks/:taskId, ortaskIdquery-parameter routes; other pages omit it.url,user_agent,language,platform,viewport,stack, anderrorare best-effort client observations and may be absent.- The request body is capped at 64 KiB. Before logging, the backend
UTF-8-safely truncates title, description, error message, and URL to 8 KiB
each; task ID to 128 bytes; user agent and platform/language fields to 2 KiB
each; and stacks to 16 KiB each. It adds
truncated: truewhen any value was shortened. - A valid report returns
204 No Content. - Invalid JSON, an unsupported source, or a report without visible text
returns
400 Bad Request. A body over 64 KiB returns413 Request Entity Too Large. - An exhausted identity or process-wide report bucket returns
429 Too Many RequestswithRetry-After.
The endpoint logs one structured error entry. Client fields remain fields;
they cannot replace the fixed frontend error toast message.
Diagnostic bundle jobs
POST /api/v1/system/logs/bundles accepts:
{
"sources": ["backend", "frontend", "runtime", "acp"],
"session_ids": ["session-uuid-1", "session-uuid-2"]
}
sourcesis a non-empty unique subset ofbackend,frontend,runtime, andacp.session_idsis rejected unlessacpis selected. Selectingacprequires one to ten unique session IDs.- The backend resolves each ACP session to an authorized task and current or retained debug source. A non-admin may select only sessions on tasks they own; an admin may select any eligible session. Unknown, foreign, or duplicate IDs fail the request without revealing whether a foreign session exists.
acpis rejected unless backend-authoritative ACP debug capture is enabled. A frontend runtime debug flag alone cannot enable or authorize ACP export.- A valid request returns
202 Acceptedwithid,status,reused,build_deadline, and nullableexpires_at.expires_atis populated when the job becomes ready/partial. An equivalent non-expired job for the same identity, source set, and normalized ACP session set returns that job withreused: true. - Backend-only jobs can become ready immediately. Jobs containing
frontendcollect browser evidence for at most 15 seconds. Jobs containingacpcollect selected executor evidence for at most 30 seconds; frontend and ACP collection may overlap under the global job bounds. - An invalid or empty source set returns
400 Bad Request. - A different request while that identity owns an active job returns
429 Too Many Requests; process-wide job saturation returns503 Service Unavailable. Both includeRetry-After: 5.
GET /api/v1/system/logs/capabilities returns backend-authoritative bundle
capabilities for the current identity:
{
"sources": ["backend", "frontend", "runtime"],
"acp_debug_enabled": false,
"acp_max_sessions": 10
}
acpappears insourceswhenever ACP debug capture is enabled. Candidate count may be zero; the UI still shows the debug action and its empty state.- This endpoint reveals capability only. It does not return paths, frames, another user's session IDs, or the value of environment variables.
GET /api/v1/system/logs/acp-sessions returns at most 500 eligible sessions,
newest first, and is available only while ACP debug capture is enabled:
{
"sessions": [
{
"task_id": "task-uuid",
"task_title": "Investigate browser disconnect",
"session_id": "session-uuid",
"agent": "claude-acp",
"provider": "anthropic",
"model": "sonnet",
"status": "running",
"executor_type": "local_docker",
"last_activity_at": "2026-08-02T12:00:00Z",
"acp_availability": "reachable"
}
]
}
acp_availabilityishost_retained,reachable, orunavailable. Unavailable sessions remain selectable only when a retained host file might still be discovered during collection; otherwise the control disables them with an explanation.- Non-admin responses contain only caller-owned sessions. Admin responses may contain all eligible sessions. The task title is returned only for the authorized ACP-picker row and is not copied into an archive. Descriptions, messages, prompts, tool payloads, file content, user identity, and log paths are never returned.
Reachable executor-side agentctl instances expose an internal
GET /api/v1/debug/acp/:session/export?max_bytes=N route only while
KANDEV_DEBUG_AGENT_MESSAGES=true:
- The response is a bounded ZIP containing only recognized retained raw and
normalized ACP JSONL files for that exact session.
Nis clamped to the backend's remaining ACP budget. - The route never accepts a filesystem path or task/user identity and never
returns unrelated session files. Unknown sessions return
404; disabled debug capture returns404; unreadable or expired evidence returns410. - The lifecycle/runtime client is the only bundle consumer. Browser clients
never call executor-side
agentctldirectly, and the backend revalidates ZIP entry names, byte bounds, and selected-session ownership before inclusion.
When frontend collection starts, the backend sends authenticated WebSocket
notification system.logs.capture_requested to every connected frontend for
the requesting identity:
{
"bundle_id": "01J...",
"capture_deadline": "2026-07-30T12:35:15Z",
"max_chunk_bytes": 1048576,
"max_browser_profiles": 4
}
Each frontend snapshots its local three-day store and uploads sequential chunks
to POST /api/v1/system/logs/bundles/:id/frontend:
{
"browser_id": "opaque-browser-id",
"capture_stream_id": "opaque-per-tab-response-id",
"chunk_index": 0,
"done": false,
"storage_mode": "indexeddb",
"capture_metadata": null,
"entries": []
}
- Each request body is capped at 1 MiB; each entry remains capped at 64 KiB.
- The first accepted chunk atomically binds a browser ID to its
capture_stream_idfor that job. Chunks must be sequential within that stream. Chunks from other tabs using the same browser ID are acknowledged without parsing or appending their entries, so concurrent tabs cannot produce a mixed snapshot. - A caller may upload only to its own unexpired bundle job.
- The backend accepts at most 10,000 entries and 20 MiB per browser profile.
- The backend accepts at most four profiles and 80 MiB of frontend data for the
whole job. Later profiles receive
429and are recorded as omitted. - The final
donechunk includes at most 8 KiB of server-validatedcapture_metadata: dropped counts by level/reason, persistence failures, entry truncations, and storage mode. Earlier chunks omit it. - Accepted chunks return
204 No Content; invalid ordering or bounds return400or413; unknown/expired jobs return404or410.
GET /api/v1/system/logs/bundles/:id returns the caller-owned job state:
collecting, building, ready, partial, failed, or expired, plus
source/capture counts, selected ACP session counts and aggregate availability,
warnings, expiry, and a download URL when available.
GET /api/v1/system/logs/bundles/:id/download returns
application/zip for ready and partial jobs. It returns 409 Conflict
while work is pending, 404 for unknown jobs, and 410 Gone after expiry.
POST /api/v1/system/improve-kandev/bundle/lease accepts a caller-owned
ready/partial bundle_id plus the caller's current Improve Kandev
bundle_dir. It verifies both artifacts belong to the authenticated identity,
copies the ZIP as diagnostic-bundle.zip, and returns that owner-only path.
Pending, foreign, expired, or invalid artifacts are rejected without copying.
Bootstrap directories carry a server-written owner marker outside the ZIP; a
path prefix or caller-supplied directory alone never proves ownership.
The existing log list, tail, individual-file download, and dev debug-export routes are removed.
Permissions
All diagnostic routes use the existing authenticated install-user boundary. Any signed-in user may create and download a bundle, matching the current System Logs access rule. A user can inspect, upload to, or download only bundle jobs created under the same identity. With authentication disabled, the synthetic single-user identity keeps local behavior unchanged.
Raw ACP selection adds a stricter task-ownership boundary. A non-admin may list and include ACP evidence only for sessions whose task owner matches the authenticated identity. An admin may list and include any eligible session. Authentication-disabled local installs use the synthetic admin identity. The backend authorizes every selected session before contacting host or executor storage; possessing or guessing a session ID does not grant access.
Frontend capture notifications and uploads never cross identities. Bundle ownership is derived from the authenticated request, not a client-supplied user or browser ID.
Task-mode MCP bundle requests derive identity from the immutable current execution task through the existing MCP identity scoper. The tool does not accept a caller-supplied identity, task/session pair, destination, or reusable credential.
Backend and bundle files use owner-only permissions (0600) where Unix
permission bits are supported. Diagnostic archives may contain install-wide
backend activity, full URLs, console arguments, stacks, paths, browser
metadata, and user-visible backend error text. The page must present that
disclosure before the download action.
ACP-inclusive archives carry a distinct high-sensitivity disclosure and may contain prompts, responses, tool arguments/results, file content, MCP payloads, environment-derived values, and secrets. Standard bundles never read raw ACP files or persisted message/transcript tables. The runtime index is authorized with the same task/session rules and excludes message bodies and user identity.
Failure modes
- If the log directory cannot be created or the active file cannot be opened, backend startup prints the intended path and a warning to stderr, continues with stdout/stderr logging, and retries the file sink every 30 seconds. Retention-cleanup failures are warnings and do not prevent startup.
- If midnight rollover fails after startup, the backend emits an error to stderr and its existing sinks, continues writing to the active file, and retries rollover on the next write without dropping the triggering entry.
- If browser persistence fails, capture continues in the bounded memory fallback and the bundle manifest marks the degraded storage mode.
- If no eligible frontend is connected, a frontend-only bundle becomes
partialwith no frontend file; anallbundle still includes backend files. - If some frontend connections time out, disconnect, reject the request, or exceed bounds, successful distinct-browser captures remain and the manifest lists aggregate omissions without exposing another user's identity.
- If ACP debug capture is disabled, an ACP request fails validation before a
job is created. If a selected host file or executor becomes unavailable after
authorization, other selected sources continue and the bundle becomes
partialwith a per-session availability warning in the manifest. - If executor ACP export times out, exceeds its clamp, returns invalid ZIP paths, or cannot be revalidated, that session is omitted. The backend does not retry continuously, does not copy other sessions, and does not fail a valid backend/frontend/runtime source.
- If the runtime index query fails, other selected sources continue and the
bundle becomes
partial; it never falls back to a broader unscoped query. - If ZIP construction fails, the job becomes
failed, temporary partial files are removed, and the UI surfaces a normal error toast. - If a backend or browser diagnostic queue is saturated, lower-priority entries may be omitted and the manifest records the loss; the product path that produced the log continues without waiting.
- If the bundle worker is busy, the request receives a busy response or reuses the caller's equivalent active job. It does not create unbounded concurrent archive or capture work.
- If the browser report endpoint receives a network, authentication, validation, or server failure, the original error toast remains unchanged and the reporting promise is discarded without retry.
- Unsupported or non-text toast content is omitted from the corresponding text field; reporting proceeds only when visible text can be extracted.
- Daily rollover uses UTC calendar boundaries and never uses client timestamps to select a backend file.
Persistence guarantees
backend-logs.logpreserves entries accepted by the file queue during the current UTC day until the fixed 256 MiB daily bound. Pressure drops, startup fallback gaps, bounded-shutdown loss, and archive truncation are explicit in counters and bundle manifests.- Dated archives preserve the two preceding UTC calendar days. Files outside that rolling window are deleted at startup and after successful rollover.
- Browser console history remains in the browser profile for three UTC days, subject to its entry/byte caps. It is not server-persistent before capture.
- Raw ACP files retain their existing debug-only executor/host retention: at most 48 hours by default, bounded by per-file rotation and total file count. Bundle creation reads selected files on demand and does not create permanent backend ACP storage. Executor teardown may make evidence unavailable before the retention age.
- The runtime index is generated per bundle from authorized current database state, capped to the diagnostic window/row limit, and is not persisted as a separate product record.
- A frontend bundle can contain only histories returned by browser profiles connected during its 15-second collection window.
- Bundle jobs and ZIPs survive frontend navigation. Collecting/building jobs are cancelled after five minutes; ready/partial archives expire 15 minutes after becoming downloadable. They are not durable product data.
- Improve Kandev's task-context ZIP is a separate internal lease on the shared archive builder and remains available for up to 24 hours.
- No toast report is stored in SQLite or queued in browser storage for retry.
- Concurrent backend processes sharing one Kandev home are unsupported.
Scenarios
- GIVEN no explicit logging override, WHEN Kandev starts normally,
THEN stdout prints the absolute backend log path, the file contains
info+, and stdout contains onlywarn+backend entries. - GIVEN Kandev starts with debug logging, WHEN it emits debug through error entries, THEN all are in the file while only warn/error are on stdout.
- GIVEN a same-UTC-day restart, WHEN the backend opens its log, THEN it appends without losing earlier entries.
- GIVEN the configured log directory is temporarily unwritable, WHEN Kandev starts, THEN product startup succeeds with a stderr warning and the backend retries file activation every 30 seconds.
- GIVEN more than three UTC days of backend files, WHEN cleanup runs, THEN only the current and two preceding days remain.
- GIVEN an error toast on a recognized task route, WHEN its report is
accepted, THEN one backend error entry includes its visible text,
browser context, and
task_id. - GIVEN console activity over three days, WHEN browser retention runs, THEN expired and oldest-over-cap entries are removed without uploading retained entries.
- GIVEN IndexedDB is slow or unavailable, WHEN an application emits a console entry, THEN the original console behavior returns without waiting for persistence and bounded staging/fallback preserves best-effort evidence.
- GIVEN a backend file sink is slow or blocked, WHEN application code emits logs, THEN logging does not block that code; lower-priority entries are shed first and the eventual bundle reports any loss.
- GIVEN stdout is blocked while the file sink is writable, WHEN backend logs are emitted, THEN the stdout queue may shed entries without stalling or dropping entries accepted by the independent file queue.
- GIVEN two users sign into Kandev from the same browser profile, WHEN the second user requests a bundle, THEN its frontend files contain only entries captured under the second user's identity partition.
- GIVEN a signed-in user opens System Logs, WHEN the page renders, THEN it names the frontend/backend event classes, states that the standard bundle does not read stored session or agent messages, warns about incidental emitted log content, and exposes one Customize bundle action without a tail or file table.
- GIVEN ACP debug capture is disabled, WHEN a user opens System Logs, THEN no ACP download action or ACP custom source is offered and a crafted ACP bundle request is rejected.
- GIVEN ACP debug capture is enabled, WHEN a user selects ACP debug messages in Customize bundle, THEN the session picker opens with the high-sensitivity disclosure and collection cannot start until at least one authorized session is selected.
- GIVEN the ACP picker lists an authorized session, WHEN a user reviews it, THEN its task title links to the task in a new tab and select-all includes no more than the allowed number of eligible sessions.
- GIVEN a non-admin can observe another user's session ID, WHEN they list ACP candidates or submit that ID directly, THEN the session is not disclosed or included and the response does not reveal its existence.
- GIVEN an admin selects two reachable sessions and one stopped unavailable
executor session, WHEN the debug bundle finishes, THEN raw and
normalized files for the reachable sessions are included, the unavailable
session is omitted, and the bundle is downloadable as
partialwith an explicit manifest warning. - GIVEN the runtime index is selected without ACP, WHEN a bundle is built, THEN it contains only authorized bounded session/runtime metadata and no task titles, prompts, responses, tool payloads, files, or message bodies.
- GIVEN two tabs from one browser profile answer a bundle request, WHEN their chunks interleave, THEN the first capture stream owns that browser ID and the ZIP contains one internally consistent frontend JSONL snapshot.
- GIVEN one browser answers and another times out, WHEN collection
expires, THEN the combined ZIP is downloadable as
partialwith backend files, the successful frontend file, and a manifest warning. - GIVEN retained backend files exceed 160 MiB, WHEN a bundle is built,
THEN it includes newest byte ranges within the budget, becomes
partial, and records each source offset and omitted byte count in the manifest. - GIVEN an agent needs only task-scoped backend evidence, WHEN it
calls
get_diagnostic_bundle_kandevforbackend, THEN the current task owner scopes the job, no frontend capture is triggered, and the tool returns an executor-local ZIP path the agent can extract and exact-grep. - GIVEN an agent requests frontend evidence while no browser is connected, WHEN collection finishes, THEN it receives a partial ZIP whose manifest states that no frontend capture was available.
- GIVEN a phone user downloads diagnostics, WHEN collection completes, THEN the same disclosure, progress, partial-state warning, and archive download remain available without horizontal scrolling or desktop-only controls.
- GIVEN a phone user customizes a bundle, WHEN they select sources and ACP sessions, THEN an inset bottom drawer keeps disclosure and actions reachable with one internal scroll owner, safe-area clearance, ≥44px targets, and the same validation/result as desktop.
Out of scope
- Continuous browser-log upload, hosted telemetry, or crash-reporting service.
- Continuous central upload or permanent backend retention of raw ACP frames.
- Including raw ACP in the standard bundle, Improve Kandev bundle, or
task-mode
get_diagnostic_bundle_kandevresponse. - Exporting database contents, task titles/descriptions, chat transcripts, stored agent messages, workspace files, secrets/configuration, or environment dumps as standalone bundle sources.
- Preserving backend or frontend diagnostic history beyond three UTC days.
- A server-side free-text/regex log search or unbounded JSON log export.
- Viewing, copying, refreshing, or individually downloading log files from the System Logs page.
- Retrying failed error-toast reports.
- Changing toast copy, placement, duration, or styling.
- Adding a setting for log path, retention, browser capture, or bundle bounds.
- Supporting configurable size-based, per-process, or local-time backend rotation.