Skip to main content
Blitz
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage
Edit page

021 · Service

The per-user service and its API (spec_service_021)

Status Implemented. Reverse-engineered from 53f8c53 (2026-09-24) and kept with the code since; its paths and names checked 2026-09-27 (//tools/specs).
Source proto/blitz/v1/{session,workspace,turn,worker}.proto, buf.yaml, buf.gen.yaml; apps/service/main.go (blitzd), apps/service/internal/{daemon,server}/*.go, apps/service/servicetest; pkg/socket; proto (generated, never edited); apps/cli/service.go
Tests apps/service/*_test.go, apps/service/internal/{daemon,server}/*_test.go, apps/cli/attach_test.go, service_test.go
Depends on spec_workspace_018
Used by spec_client_022, spec_workers_023, spec_desktop_024

1. Purpose

blitzd, its own program (apps/service), runs one engine process per user that holds every workspace a client opens, runs scheduled workers, and exposes api.Backend over Connect (gRPC, gRPC-Web or plain JSON) on a Unix socket. The CLI and the desktop app share one copy of each workspace through it. Handlers only translate between protos and pkg/engine; they hold no logic.

  • SVC-00 blitzd [--socket PATH] [--config FILE]: no arguments (a usage error, exit 2, as a bad flag or a service already answering on the socket); --version; --license[=full|third-party] prints the NOTICE, the Apache License or the third-party notices and exits without starting. It takes no model, agent or workspace flags: each workspace’s configuration decides. blitz serve, from before blitzd existed, is kept as a hidden command that runs blitzd (found as in SVC-50) with the same arguments, so login items installed by older versions keep working.

2. Transport and security

  • SVC-00a Since 2026-10-04 (the thin CLI, spec_cli_020 CLI-14). --idle-exit DURATION stops the service cleanly after that long with nothing to do: no request in flight, no turn running in any workspace, no background run going (Server.IdleFor; the CLI starts the shared service with 15m when there’s no login item). Hidden flags for a private service a client starts for a run of its own: --exit-with-stdin (stop when stdin, the client’s pipe, closes; such a service never runs the workers’ schedules, which would run them twice and open every workspace with an enabled worker, keeping it from the shared service), and the run’s settings over every workspace’s configuration: --model, --agent, --agency, --plugin-dir, --add-dir (as sandbox.allowed_paths), --session-dir, --trust-project, --append-system-prompt-file. blitzd doctor [--config] [--dir] [--model] [--agent] [--agency] [--plugin-dir] [--add-dir] [--online] runs the setup checks (spec_cli_020 §6), exit 1 when one fails. WorkspaceService.ForgetProjectTrust forgets the decision about a workspace’s project settings and reopens it without them (unless a turn runs); ConfigService.ListModels takes providers and returns each model with Blitz’s price.
  • SVC-01 Socket: $BLITZ_SOCKET or ~/.blitz/run/blitz.sock (blitzd --socket overrides it). The service is never on a network port: it runs shell commands. (On Windows the tray serves the desktop page on a loopback port, behind a cookie only its own link gives: spec_windows_036 WIN-11–13.)
  • SVC-02 Listen: create the directory 0700 and chmod it (no window where others could reach the socket), refuse if a service already answers (ErrRunning → exit 2), replace a stale socket, chmod the socket 0600. The socket is removed on exit.
  • SVC-03 HTTP/1.1 and unencrypted HTTP/2 (for gRPC clients); read-header timeout 10 s. Clients use base URL http://blitz with a dialer to the socket.
  • SVC-04 Shutdown on SIGINT/SIGTERM: stop accepting, wait up to 10 s for calls in progress, then close (turns still running are cut off); the scheduler stops its runs; all workspaces close.
  • SVC-06 GetServiceInfo reports the service’s version (the release, or dev), the program it runs from (os.Executable at start; the file may have been replaced or removed since), when it started, its process ID, and replaced: whether the program file is no longer the one it started from (another file in its place, as a package install or a new build leaves, or none; os.SameFile against the file at start), so clients can tell a stale service (desktop DSK-51a). Services older than it answer Unimplemented.
  • SVC-06a ListLogDays and ReadLog read the service’s own diagnostic log (spec_observability_003 LOG-06), in its log.dir (WithLogDir): the days there are and the directory; a day’s records by level and text, newest first, with the file and how many matched. DeleteLogDay deletes a past day’s log (DeleteLogDay, LOG-06): today’s is being written (LOG_IN_USE), and a day with no log, or logging off, is LOG_NOT_FOUND. With log.level = "off" there’s no directory: no days, no records. A bad day or level is invalid_argument.
  • SVC-05 Clients name their workspaces in each request. Request messages are limited to 32 MiB (an added image is the largest).

3. Workspaces in the service

  • SVC-10 Every request names its workspace by absolute directory. The server keys workspaces by canonical path (symlinks resolved), opens one on first use with a fresh configuration per workspace (a workspace owns and mutates its config), streaming on, warnings to the log, and the shared worker store.
  • SVC-11 Opening is asynchronous and de-duplicated: callers for the same workspace share one open; other workspaces don’t wait. A caller whose context ends gets Canceled. After close, requests get SHUTTING_DOWN.
  • SVC-12 The workspace lock (spec_workspace_018 WS-03) means a workspace held by the shared service can’t also be opened by a private one (blitz --local, WORKSPACE_BUSY) until the shared one closes it, which it does when asked and no turn runs there.
  • SVC-14 CloseWorkspace refuses while a turn runs in any of the workspace’s sessions (TURN_RUNNING, FailedPrecondition): closing would cut off the turn (maybe another client’s), and a turn waiting for an approval would hold the close forever. Closing an idle workspace is safe for workers: the scheduler reopens workspaces when a run is due.
  • SVC-13 Images: each workspace keeps the 16 most recently used prepared images by ID for later turns (LoadImage/AddImage return IDs; Turn.image_ids refer to them; unknown → UNKNOWN_IMAGE). PDFs are images here too, with their pages (spec_images_011 IMG-50). Every attachment carries its kind and, for audio and video, seconds; one the model can’t take is UNSUPPORTED_MEDIA.
  • SVC-13a Added content: Turn.context (ContextBlock: kind web_page, web_selection or console, URL, title, HTML or text), what the user added from the desktop app’s web preview (spec_electron_038 EL-104). The engine (withAdded) gives it to the agent after the prompt’s @ mentions, each block’s HTML made into text (textutil.HTMLToText, the web fetch tool’s), cut to 64 KB a block and 256 KB in all with a note, inside <user-added-content> with the note that it’s data written by the page’s authors, not instructions; a block can’t close its own fence. The transcript records the prompt with [added: <titles>], as it does [images: …].

4. Services

Service RPCs
SessionService ListSessions, GetActiveSession, NewSession, OpenSession, LoadSession, ListSessionTools, SaveSnapshot, RenameSession, DeleteSession (SES-23; SESSION_OPEN, SESSION_NOT_FOUND), RunTurn (server stream), Steer, Approve, Answer, GetUsage, Compact, SearchSession
WorkspaceService GetServiceInfo, ListLogDays, ReadLog, DeleteLogDay (SVC-06, SVC-06a), ListWorkspaces, CloseWorkspace, GetSandbox, ListAgents, SetAgent, GetModel, SetModel, PinModel, UnpinModel, GetModelSettings, UpdateModelSettings (every [model_settings] key, including reasoning_effort and thinking_budget), GetSettings (includes permission_mode, effort, the speech model with why it can’t be used, speech_model and speech_error, and what the active agent’s model takes as attachments, accepted_media: spec_images_011 IMG-63), SetSetting, SetPermissionMode, ListCommands, ListPermissionRules (with problem: why rules were left out at the last reload), AddPermissionRule, RemovePermissionRule, ListSkills, GetSkill, ListEnvs, RemoveEnv, PruneEnvs, ListMCPServers, ListTools, ReloadMemory, ReloadAgentFiles (/reload, spec_memory_012 MEM-25), AddMemory, ListLocales, SetLocale, ListCheckpoints, Undo, GetDiff (with git, every change since the last commit and new files: WS-61), GetGitStatus (the branch and changed files, and whether git is installed, for the desktop’s status bar and Changes; a repository with no commit yet is one), InitGitRepo (git init in a workspace that isn’t in a repository; NO_GIT, GIT_FAILED), DraftCommit (the staged files, everything staged first with stage_all, and a commit message the suggestions model, else the reviewer’s, else the main one, drafts from the staged diff, cut at 24,000 characters with secrets redacted, following the style of the last ten subjects; nothing asked with nothing staged; a failing model gives problem, its redacted summary, instead of an error; NOT_A_REPOSITORY, GIT_FAILED), Commit (what’s staged, git commit -F -, the repository’s hooks run, two minutes at most; the new commit’s short hash and subject; NOT_A_REPOSITORY, NOTHING_STAGED, EMPTY_MESSAGE, GIT_FAILED), FetchRemote, Pull, Push, ResolveConflict, CompleteMerge, AbortMerge (the workspace’s remote, SVC-36), ListBranches, SwitchBranch, CreateBranch, MergeBranch (its branches, SVC-37), ListApprovals, RevokeApprovals, ListProcesses, GetProcessOutput, KillProcess (a background process records the session whose turn started it; each call names the client’s sessions and sees only their processes, UNKNOWN_PROCESS otherwise), AuditShell, MoveSession (on SessionService, for /cd, SES-18; SessionInfo.origin says what started a session, SES-24), ListTasks, GetTask, StopTask, WatchTasks (a stream: active tasks and waiting requests, ready, then changes), ListTaskRequests (spec_background_agents_032; Approve and Answer also answer tasks’ requests), GetProjectSettings, TrustProject (records the decision and reopens the workspace unless a turn runs in it; PROJECT_CHANGED for content changed since it was shown, spec_project_config_031), LoadImage, AddImage, GetSearchProvider, SearchWeb
FileService ListDir, ReadFile, ReadPreview (images, PDFs and sound), WriteFile, WriteBinaryFile (bytes, as WriteFile writes text: an exported PDF, spec_desktop_024 DSK-93), CreateFolder, RenameFile, DeleteFile, FindFiles, StatFiles (spec_files_029 §3; reasons FILE_CHANGED with current_version, BAD_PATH, FILE_NOT_FOUND, FILE_EXISTS)
ConfigService DescribeConfig, GetSignIn, SignIn, SignOut, SetApiKey, SecureApiKey, RemoveApiKey, SetProvider, SetConfigValue, DescribePermissions, AddPermission, RemovePermission, CheckPermission, SetReadOnlyDefaults, GetConfigFile, SaveConfigFile, CheckConfigFile, GetSettingsReference (SVC-35), ListModels (SVC-55)
WorkerService ListWorkers (one workspace or every registered one), CreateWorker, EnableWorker, DisableWorker, DeleteWorker (WK-01c), RunWorker, ListWorkerRuns, GetWorkerRun, WatchWorkerRun (server stream), GetWorkerSpec, UpdateWorker, UndoWorkerRun, ListRunningWorkerRuns, StopWorkerRun (WK-53a), RewriteWorker (WK-53b)
  • SVC-35 ConfigService edits the settings of a scope — workspace an absolute directory, "" the global settings — through pkg/config (spec_config_002 §6), in the service’s settings directory (--config). It never returns a key, only where each comes from. A change reloads the open workspaces it touches (every one for the global scope): each reloads its configuration and rebuilds its model and its agents’ pinned models (Workspace.ReloadProviders; only the providers and the default model change; other settings apply when a workspace next opens; a pin that can’t be built keeps its model), and the response carries the scope’s workspace’s model error, if any. A workspace whose model couldn’t be built tries again (Workspace.RetryModel) when GetModel asks and before each turn or worker run, so a sign-in made outside Blitz (gcloud’s Application Default Credentials, ant auth login) is noticed without a settings change. Invalid input (a relative directory, an unknown provider or setting, a file that doesn’t decode) is InvalidArgument.
  • SVC-36 Since v0.6.3. The workspace’s remote, for the desktop’s Changes view (spec_desktop_024 DSK-90b; pkg/engine/gitremote.go). GetGitStatus also reports remote, the branch’s upstream, ahead and behind (as of the last fetch), a merge or rebase in progress (operation) and the files in conflicts. FetchRemote fetches the upstream’s remote (else every remote) with --prune. Pull runs git pull --autostash, adding --no-rebase only when none of pull.rebase, branch.<name>.rebase and pull.ff is set (git refuses divergent branches unasked); its outcome is UP_TO_DATE, FAST_FORWARD, MERGED, REBASED or CONFLICTS (not an error: the merge or rebase waits), with the commits it brought in. Push pushes to the upstream, or publishes the branch (--set-upstream to origin, or the only remote) and returns remote/branch. ResolveConflict settles a conflicted file: MINE, THEIRS (swapped into git’s terms during a rebase, whose “ours” is the upstream; a side that deleted the file deletes it) or RESOLVED (git add, refused while conflict markers remain). CompleteMerge commits the merge (git commit --no-edit) or continues the rebase, which may stop on the next commit’s conflicts; AbortMerge runs git merge --abort or git rebase --abort. Git never prompts or opens an editor (GIT_TERMINAL_PROMPT=0, GIT_EDITOR=true, GIT_MERGE_AUTOEDIT=no) and has two minutes. Settings that run commands — core.sshCommand, core.gitProxy, core.askPass, credential helpers, filter drivers, merge drivers, remote.*.uploadpack/receivepack, protocol.*.allow, gpg.*.program — are taken from the user’s global and system config only: the agent can write the repository’s .git/config, so when it (or what it includes, or the worktree’s config) sets one, these actions refuse with REPO_CONFIG_COMMAND naming the key. Hooks run, as Commit’s do. Errors: NOT_A_REPOSITORY, NO_REMOTE, NO_UPSTREAM, GIT_BUSY (a merge or rebase waits), PUSH_REJECTED (the remote has commits the branch hasn’t), REMOTE_AUTH (credentials wanted), NO_MERGE, CONFLICTS_REMAIN, MARKERS_REMAIN, NOT_CONFLICTED, BAD_PATH, INVALID_CHOICE, GIT_FAILED.
  • SVC-37 2026-10-16 (the owner’s request). The workspace’s branches, for the desktop’s Changes view (spec_desktop_024 DSK-90c; pkg/engine/gitbranches.go). ListBranches lists the local branches, then the remotes’ branches no local branch tracks (a remote’s HEAD left out), each newest commit first, with current, a local branch’s upstream, and its last commit’s subject and time. SwitchBranch runs git switch to a local branch (--no-guess), or to a remote’s (origin/feature) with --track, which makes a local branch that tracks it; uncommitted changes come along unless they clash (LOCAL_CHANGES, nothing changed). CreateBranch runs git switch --create at the current commit, keeping uncommitted changes. MergeBranch runs git merge --no-edit --autostash with a local or remote branch: its outcome is UP_TO_DATE, FAST_FORWARD, MERGED or CONFLICTS (as a pull’s: the merge waits for ResolveConflict, CompleteMerge and AbortMerge, where “mine” is the current branch and “theirs” the merged one), with the commits it brought in. GetGitStatus also reports merging, the branch a merge in progress brings in (git name-rev of MERGE_HEAD over the local and remote branches; empty when it isn’t a branch’s tip), so the Changes view names it. A name must pass git check-ref-format --branch and can’t start with -. These check out files and merge them, which runs filters, merge drivers and hooks, so they take SVC-36’s care: they refuse while a merge or rebase waits (GIT_BUSY), the repository’s own config may not set a command (REPO_CONFIG_COMMAND), and git never prompts or opens an editor. Errors: NOT_A_REPOSITORY, NO_BRANCH, BRANCH_EXISTS, BAD_BRANCH_NAME, LOCAL_CHANGES, MERGE_SELF (merging the current branch), GIT_BUSY, REPO_CONFIG_COMMAND, GIT_FAILED.

5. Turns over the stream

  • SVC-20 RunTurn(workspace, session_id, Turn) streams TurnEvent{author, oneof: accepted | text | tool_call | tool_result | approval_request | question | tasks | finished | notice}. Events are sent under a lock (the agent and approval requests can produce them concurrently). The last event is always finished{output, before, after, leftover, error}; a hook refusal finishes with reason PROMPT_BLOCKED. Cancelling the call interrupts the turn. A notice (text, error, transient) is Blitz’s own message about the turn, not the agent’s: a transcript message that couldn’t be saved (WS-28). Since v0.6.3 a transient notice is a passing status, the model’s connection failing and being retried (spec_models_015 MDL-51b): the desktop shows it in a snackbar and doesn’t keep it in the chat; the terminal prints it as a line.
  • SVC-21 accepted marks the prompt recorded; from then on the client may Steer.
  • SVC-22 Approval broker: the workspace’s approver and question prompter become approval_request{request_id, tool, kind, detail, diff, scope_label} / question{request_id, question, options} events on the running turn’s stream, and the agent waits until Approve(request_id, decision) or Answer(request_id, text) arrives, or the turn ends. Request IDs are random 96-bit hex. Answering an unknown or expired request → NotFound UNKNOWN_REQUEST. Decisions: DENY, ONCE, SESSION (until the service restarts), ALWAYS (saved); unspecified → INVALID_DECISION.
  • SVC-23 An approval or question outside a turn with a client (e.g. from a worker) is refused (no client is attached to answer).

6. Errors

  • SVC-30 Errors are Connect errors with an ErrorInfo{reason, metadata, message} detail, mapped from pkg/engine typed errors. Reasons: UNKNOWN_COMMAND, BAD_RULE, UNKNOWN_MODE, BYPASS_NEEDS_SANDBOX, MAX_TURNS, COST_LIMIT, TIME_LIMIT (a turn stopped at a limit; Turn.max_cost_usd and Turn.timeout carry the limits), INVALID_WORKSPACE, OPEN_FAILED, WORKSPACE_BUSY, SHUTTING_DOWN, NO_ACTIVE_SESSION, SESSION_NOT_FOUND, RESUME_FAILED, SNAPSHOT_NAME_TAKEN, NO_NOTE, NO_GOAL, PROMPT_BLOCKED, INVALID_TURN, UNKNOWN_REQUEST, INVALID_DECISION, UNKNOWN_AGENT, BAD_MODEL_REF, INVALID_SETTING, UNKNOWN_SETTING, INVALID_AGENCY, UNKNOWN_LOCALE, NOTHING_TO_COMPACT, OUTPUT_LIMIT (the turn’s last model reply stopped at max_tokens, cut off: a tool call it was writing is dropped), UNDO_CONFLICT, NOTHING_TO_UNDO, GIT_FAILED, NO_GIT, IMAGES_DISABLED, UNKNOWN_IMAGE, NO_FETCH, NO_SEARCH, SCRIPTS_DISABLED, SKILL_NOT_FOUND, UNKNOWN_WORKER, WORKER_INVALID, WORKER_EXISTS, WORKER_DISABLED, WORKERS_DISABLED, HASH_MISMATCH, RUN_IN_PROGRESS, RUN_NOT_RUNNING, UNKNOWN_RUN, TOO_MANY_RUNS, NO_SCHEDULER, INTERNAL.
  • SVC-31 Errors reported inside a response (e.g. a failed save, a turn failure) use the same ErrorInfo.

7. API evolution

  • SVC-40 Protos live in proto/blitz/v1 (package blitz.v1), linted with buf STANDARD, breaking-change checked with FILE. Their Go code is generated by Bazel and never committed: one package, github.com/retail-cortex/blitz/proto/blitz/v1 (blitzv1), holds the messages and Connect’s clients and handlers (protoc-gen-connect-go with package_suffix=; //proto/blitz/v1:blitzv1_go_proto). So is the desktop page’s TypeScript (//apps/desktop/web:api_ts, protoc-gen-es pinned by the page’s lockfile). CI checks the protos’ formatting and breaking changes with buf (spec_release_025 REL-04).
  • SVC-41 A deliberate breaking change is declared with a Breaking-API: <why> line in the commit message, which CI’s buf breaking step honours.

8. Login item (blitz service)

  • SVC-50 The login item lives in pkg/loginitem, shared by the CLI and the desktop app (which installs, restarts and stops the service itself, DSK-51a). On Linux, installing restarts a running unit, so a reinstall runs the new program. install: finds blitzd beside the real (symlinks resolved) blitz executable, else on PATH (neither: usage error), and registers it; macOS writes ~/Library/LaunchAgents/dev.blitz.service.plist (RunAtLoad, KeepAlive on unsuccessful exit, stdout/stderr to ~/.blitz/logs/service.log) and launchctl bootout/bootstrap gui/<uid>; Linux writes ~/.config/systemd/user/blitz.service (Restart=on-failure, WantedBy=default.target), daemon-reload, enable --now. Other OSes: usage error. Prints that it must be re-run after upgrading, and warns about API keys set only in the shell environment (a login item doesn’t see them) by reloading the config without them.
  • SVC-51 uninstall stops and removes the item; status reports whether the item is installed and whether the service answers.
  • SVC-52 loginitem.Start and Restart start or restart the login item’s service (systemctl --user start|restart blitz.service; launchd: bootstrap if unloaded, then kickstart, with -k to restart); without the item they are ErrNotInstalled.
  • SVC-53 The tray (apps/tray, blitz-tray): an icon in the system tray, a white bolt on a transparent background (apps/tray/icons/*.svg; a template icon on macOS, which tints it to suit the menu bar), filled while the service answers on its socket and outlined when it doesn’t, with a tooltip and a first, disabled menu line saying so (“Blitz service: running (v1.4.0)”, “stopped” for a login item that isn’t running, “not running”); then Start the service (while it’s stopped), Stop the service and Restart the service (while it runs), Open Blitz, Open the logs, since v0.7.1 MCP servers and Workers (submenus, shown while the service runs and answers: spec_mcp_009 MCP-48, spec_workers_023 WK-57), Quit the tray (the service keeps running). It asks every 2 s (Probe: the socket, and GetServiceInfo for the version and process). Start, Stop and Restart go through the login item when there is one (SVC-52), else Start runs blitzd from beside the tray or PATH on its own, and Stop asks the service to stop (Shutdown), then sends the process that answered SIGTERM; each waits for the service to be (or not be) running, up to 15 s, and says “Couldn’t: …” in the first line for 5 s when it fails. Open Blitz runs the bundle the tray is in (macOS: the nearest .app above it, Blitz.app/Contents/Resources/app/bin in the Electron app) or blitz-desktop (Linux: the Electron app’s, three folders up from resources/app/bin, then beside the tray, then on PATH) (on Windows it serves and opens the page: spec_windows_036); Open the logs opens the running service’s log folder in the file manager (what ListLogDays says, the log.dir it started with), else log.dir of the global settings. The menu is in the desktop app’s language (its Interface language, from ~/.blitz/desktop.json; System follows the system’s languages: macOS’s list, then LANGUAGE, LC_ALL, LC_MESSAGES, LANG), from the same catalogs (tray.* keys). One tray at a time: it holds ~/.blitz/run/tray.lock (flock; LockFileEx on Windows) while it runs, and a second one exits. On macOS the tray is an accessory app (NSApplicationActivationPolicyAccessory, apps/tray/dock_darwin.go), set before its menu bar item: run from Blitz.app/Contents/MacOS it would otherwise take the bundle’s Info.plist and show a second Blitz icon in the Dock and the app switcher.
  • SVC-53a Since v0.7.0 (the Electron trial, spec_windows_036 WIN-14). blitz-tray --serve [--page <dir>] serves the desktop page for a window that shows it, with no icon and without the tray’s lock (a tray can show its icon beside it): the page in <dir> (it must have index.html), else the one built in (Windows builds; elsewhere it’s an error without --page), the API through the service’s socket, and /host/ (pkg/pagehost). It writes a one-time link to the page as a JSON line ({"url": …}) on standard output, a new one for each link line it reads, and stops serving when standard input closes, so it never outlives the window that started it (tray.Serve).
  • SVC-54 The tray starts at login from its own entry: blitz-tray --install writes it and starts the tray (Linux: an XDG autostart entry, $XDG_CONFIG_HOME/autostart/blitz-tray.desktop, and the tray started now in a graphical session; macOS: a launchd agent dev.blitz.tray, LimitLoadToSessionType Aqua, which starts it); --uninstall removes it (on macOS, stopping it). On Windows it’s a value in the registry’s Run key, which the tray’s Start Blitz at login also toggles (spec_windows_036 WIN-05). The desktop app’s Show in the system tray (Settings › Service, DSK-60) does the same through its host (pkg/pagehost SetTray): it writes the entry and, on Linux and Windows, starts the tray now (Host.StartTray), or removes it. Since v0.7.1 the host also repairs entries an older install wrote when it starts (Host.Repair, loginitem.RepairTray/RepairService): an entry whose program is gone (the desktop app’s moved in v0.7.0, from /usr/lib/blitz-desktop/blitz-tray to …/resources/app/bin/blitz-tray) is rewritten for the current one, the tray started and the service’s login item restarted; an entry whose program is there is left alone. On Linux the icon is the bolt in colour, as on Windows (amber running, grey stopped), so it shows on light panels and dark; macOS keeps the white template icon. Linux needs a StatusNotifierItem host: KDE and most desktops have one; GNOME has it with the AppIndicator extension (on by default on Ubuntu); without one there’s no icon.
  • SVC-55 2026-10-01. ConfigService.ListModels(workspace): each configured provider’s models from its API (runtime.ListModels, as blitz models), with why a provider couldn’t be listed and the default provider, for the desktop’s model fields to suggest and check names. Kept per scope for 10 minutes (1 minute when a provider failed), and dropped whenever a settings change goes through ConfigService (a new key, provider or file). Model names for gemini, anthropic and vertex-anthropic are lowercased wherever a model is chosen (runtime.ParseModelRef): their IDs are always lowercase, and a name a text field capitalized (“Gemini-3.8-flash”) is otherwise a 400 from the API.
  • SVC-56 2026-10-04 (the owner’s request: sign in to each provider, as well as API keys, never instead). Sign-ins with each vendor’s own tool, run by the service so the credentials land where it reads them (pkg/signin): google (gcloud auth application-default login; signed in when the Application Default Credentials file parses, read without asking Google), anthropic (ant auth login, the profile through ANTHROPIC_PROFILE; signed in when the profile has credentials), aws (aws sso login [--profile]; signed in when the chain Bedrock uses gives credentials) and azure (az login; signed in when Entra ID gives a token for Azure OpenAI). Each also signs out (revoke, logout). The tools are found on the service’s PATH, else the login shell’s (pkg/shellpath, which the desktop app’s CLI install shares), run with a fixed argument list (a profile is letters, digits, ., _ and -), no stdin, at most 10 minutes, one sign-in per provider at a time. ConfigService.GetSignIn(workspace, provider?, profile?) returns each provider’s status (the four checked at once, each within 5 s; AWS and Azure answer at once when nothing for them is set up, without waiting on a cloud’s metadata service); SignIn streams the tool’s lines with the sign-in page’s URL and a device code pulled out, then done with the status; SignOut. After either, the service forgets the model listings and builds again the open workspaces’ models that had failed. Errors: UNKNOWN_PROVIDER, INVALID_PROFILE, NO_TOOL (with where to get it), SIGN_IN_RUNNING, SIGN_IN_FAILED (the tool’s last lines). OpenAI has no sign-in: it takes an API key.
  • SVC-57 2026-10-09 (found when pyright, installed with nvm, never answered in the desktop app). A service started by the tray, launchd or systemd has the system’s bare PATH (/usr/bin:/bin:/usr/sbin:/sbin on macOS), without the folders the user’s tools are in (nvm, Homebrew, ~/go/bin). At start it takes the PATH of the user’s login shell (AdoptUserPath in pkg/shellpath: the shell’s folders first, in its order, then the service’s own), so language servers, the agent’s commands and sign-in tools find what the terminal finds. A private service (--exit-with-stdin, started by the CLI) keeps its terminal’s. Windows has no login shell to ask: nothing changes there.