Operations and security
Daemon health, macOS startup, and private local data.
The default runtime home is ~/.guildscribe. --home <absolute-path> selects
another home for every command.
~/.guildscribe/
.env
guildscribe.sqlite
guildscribe.lock
runtime.json
logs/
daemon.jsonl
daemon.jsonl.1
launchd.stdout.log
launchd.stderr.log
in-progress/
<recordingID>/
participants.json
intervals/
<intervalID>.json
<intervalID>.pcm
audio.part.mp3
recordings/
<year>/
<month>/
<recordingID>/
participants.json
intervals/
<intervalID>.json
<intervalID>.pcm
audio.mp3guildscribe.lock exists only while the daemon is running and contains a
versioned JSON record with its process ID and start time. runtime.json is an
owner-only atomic heartbeat containing daemon phase, Discord readiness, update
time, and the last sanitized runtime error. The daemon refreshes it every 10
seconds; a live lock with no matching heartbeat within 30 seconds is stale.
Completed raw PCM files and their JSON interval sidecars remain permanently with the published
recording so speaker audio, identity, timing, and capture gaps stay inspectable
and available to optional processing.
The runtime home uses mode 0700 and .env uses mode 0600 on POSIX systems.
Guildscribe rejects a runtime home or configuration file that is a symbolic
link, is owned by another user, has group/world permissions, or is not the
expected filesystem type. Runtime files never belong in the source checkout.
daemon.jsonl is the structured operational log. Guildscribe rotates it at 10
MB and retains three prior files. The launchd stdout and stderr files capture
wrapper failures and unhandled output before structured logging is available.
Status
guildscribe status reads saved state and reports daemon liveness, uptime,
heartbeat time, Discord readiness, last sanitized runtime error, and one health
classification: stopped, starting, ready, recording, degraded,
stopping, or stale. It also reports the configured channel allowlist,
whether transcription is enabled, the active recording, the latest published
recording, and aggregate storage totals. The empty-channel grace timer is kept
in memory and is not included in this report. It never contacts Discord, returns
the token, or mutates state. A malformed lock is not proof that a daemon exists;
a live lock without a current, PID-matched heartbeat is stale.
macOS startup
scripts/installLaunchAgent.sh builds Guildscribe and invokes guildscribe startup install. Installation requires an initialized runtime home and writes
~/Library/LaunchAgents/dev.guildscribe.daemon.plist with mode 0600. The
plist contains no Discord token. It launches /bin/bash scripts/daemon, the
selected runtime home, and the exact Node executable used for installation.
scripts/daemon sets the system and Homebrew PATH before executing
guildscribe daemon run. See the contributor guide
for the modules that implement startup.
The agent uses RunAtLoad, a 30-second launchd throttle, and a KeepAlive
condition that restarts only after an unsuccessful exit. A crash is therefore
restarted, while startup stop sends SIGTERM and the daemon's clean exit keeps
it stopped. startup start or startup restart resumes it. Process type is
Standard because live voice capture should not be background-throttled.
startup uninstall unloads the agent and removes only its plist. It never
removes .env, recordings, in-progress recovery data, or logs. The scripts
must run as the logged-in user and reject root execution.
Protect the runtime home
Keep tokens and private requests out of source control, command-line arguments,
and logs. config show redacts secrets. Recordings, transcripts, participant
metadata, messages, exports, and backups can contain private content. History
commands return actual saved text; avoid dumping it into public logs.
Only explicitly configured slash interactions can execute user action hooks. Ordinary messages and context-menu interactions cannot. Treat executable allowlists and Discord integration permissions as security boundaries.
Upgrade and migration
Before upgrading, use database backup and preserve media and participant/interval
files separately. Do not copy only a live SQLite main file: committed data can
remain in its WAL.
On startup the daemon validates legacy recording/transcript documents, writes
recovery bundles, imports SQLite state, and removes obsolete metadata only after
successful import. Keep legacy-storage-backup-*.json bundles. Audio and interval/
participant files remain. Existing hooks must use the database references rather
than expect recording.json. Building alone does not migrate a live home.
Linux capture is supported, but built-in Linux service management is not yet implemented. Choose your own supervisor there. Installing or starting a service is an explicit operational step, separate from building the package.