Loading
feat: add fallback periodic sync via launchd/systemd
What does this MR do?
Adds a fallback periodic sync so agent sessions still reach GitLab when the hooks fail to sync them (no network, glab crash, agent killed before the hook runs).
What glab govern setup installs
In addition to the existing Claude Code hooks, glab govern setup installs a scheduled job by default:
| Platform | Job | Files | Schedule |
|---|---|---|---|
| macOS | launchd agent | ~/Library/LaunchAgents/com.gitlab.glab-govern-audit-sync.plist |
every 30 minutes |
| Linux | systemd user timer | ~/.config/systemd/user/glab-govern-audit-sync.{service,timer} |
every 30 minutes, and 5 minutes after boot |
| Windows | not supported | – | – |
- The confirmation prompt and
--helpname the job.--no-fallback-syncinstalls only the hooks. glab govern setup --uninstallremoves the job. It leaves the hooks in place.- The job runs the
glabfound onPATHat setup time (for example/opt/homebrew/bin/glabor a mise shim), as long as it reports the same version and commit as the glab runningsetup. Otherwise it runs the glab runningsetup. Symlinks and shims aren't resolved, so package-manager upgrades don't break it. - The job doesn't depend on the current Git repository, so
setupinstalls it from any directory. - The job runs with
GLAB_CONFIG_DIRset to the glab configuration directory in use at setup time. launchd and systemd don't pass on the user's shell environment, so without this a customGLAB_CONFIG_DIRorXDG_CONFIG_HOMEwould point the job at a different directory from the hooks.
What gets uploaded, and where
The job runs glab govern audit sync --all:
- Which sessions: only sessions the hooks have already recorded. On each run the hook writes a
.metafile under~/.config/glab-cli/gaig/cursors/with the agent type, project, host, and how to find the transcript. Sessions the hooks never saw are never uploaded. - Where: each session goes to the project and host recorded by its hook (the Git remote of the directory the agent ran in), using the stored glab credentials for that host.
--allcan't be combined with-R. - What: the same audit events the hooks send (tool name and timestamp per tool call), plus the session goal.
- Unattended: the job runs with no agent or terminal open.
- Pausing: Claude Code sessions are skipped while the glab
Stophook is missing from~/.claude/settings.json, so removing the hooks pauses them. Sessions from other agents keep syncing.
Other new behavior
- Agents: session sync goes through a small
agentinterface (detect the session, open its transcript, reopen it later from.meta, report whether syncing is paused). Claude Code and OpenCode implement it. Adding an agent is one new type. - OpenCode: sessions are read with
opencode export <id>. This also fixes the hook path, which used to look for OpenCode transcripts under~/.claude/projects. glab doesn't read OpenCode's SQLite storage directly, so no new dependency is added. glab govern audit sync --all:- Reads each transcript before making any API call, and skips sessions with nothing new.
- Looks up each project once per run.
- Marks a session completed after 24 hours of inactivity, in case the
SessionEndhook never ran. Completed sessions are skipped on later runs. - Deletes the local cursor and
.metaof a session whose transcript the agent has deleted, so later runs don't keep looking for it.
- Run log: each
--allrun writes~/.config/glab-cli/gaig/fallback-sync-status.jsonwith its time, counts, paused agents, and up to 20 errors. glab govern doctorhas a new "Fallback periodic sync" check that shows:- whether the job is installed, and loaded in launchd or systemd;
- whether the
glabbinary it runs still exists (fails with a fix if not); - whether its last run exited with an error, which catches runs that fail before they can write the run log;
- the last run's results, with a warning if no run happened for more than an hour.
Limitations
- Windows is not supported: the job is not installed, and
doctorsays so. - Only sessions with a
.metafile are synced.setupinstalls hooks only for Claude Code, so OpenCode sessions are synced only if something callsglab govern audit syncwithOPENCODE_SESSION_IDset. - Local state for completed sessions is kept: each leaves a small cursor file and a
.metafile in~/.config/glab-cli/gaig/cursors/, which later runs skip without any API calls. It's removed once the agent deletes the transcript. - OpenCode syncing can't be paused by removing its integration, because glab doesn't install or manage it, so
--allhas nothing to check.glab govern setup --uninstallstops the job for every agent. Oncesetupinstalls an OpenCode integration, OpenCode can check for it the same way Claude Code checks its hooks. - With mise or asdf, the job runs the shim. launchd and systemd start the job in
/, so the shim uses the global glab version. If glab is only pinned in a project's.tool-versions, the job fails to start, anddoctorreports its non-zero exit. - OpenCode before 1.2 stored sessions per project, so
opencode exportrun from the job may not find them. Current versions look sessions up by ID in a single database. - If a hook and the job sync the same session at the same moment, both can post the same events. Event IDs are deterministic, so whether duplicates appear depends on GitLab deduplicating them.
- On Linux, if the token is stored in the system keyring and no desktop session is running, the job may not be able to read it.
doctorshows the resulting errors. - An OpenCode session's cursor is a count of tool calls, so tool calls made after a revert are not synced until the count passes the old cursor.
- Idle completion uses a fixed 24-hour threshold. If a session resumes after being auto-completed, its later events go to the completed GitLab session.
Who is affected
Only users who run glab govern setup (an experimental command). Nobody else sees any change.
How to remove it
- The job:
glab govern setup --uninstall, or manually:- macOS:
launchctl bootout gui/$(id -u)/com.gitlab.glab-govern-audit-sync && rm ~/Library/LaunchAgents/com.gitlab.glab-govern-audit-sync.plist - Linux:
systemctl --user disable --now glab-govern-audit-sync.timer && rm ~/.config/systemd/user/glab-govern-audit-sync.{service,timer} && systemctl --user daemon-reload
- macOS:
- The hooks: delete the
glab govern audit syncentries from~/.claude/settings.json.
Edited by Jean van der Walt