Problem
Logging currently uses slog.NewTextHandler writing to stderr. This makes it impossible to trace a specific job using journalctl structured queries — you can't filter by repo or ticket number.
Three concrete issues:
1. slog-journal not wired up
ADR-002 chose systemd/slog-journal and CLAUDE.md documents it in the tech stack, but the dependency was never added. setupLogger() in cmd/hive/main.go creates a plain TextHandler:
handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: level,
})
2. Duplicate timestamps
TextHandler emits its own time= prefix on every line. When hive runs as a systemd service (or under systemd-run), journald also stamps each line. The result is two timestamps per log entry — one from slog, one from the journal — neither of which is useful for structured queries.
3. No way to filter by repo/ticket
Structured fields like repo and issue are passed as slog key-value args today, but TextHandler flattens them into free-text. There's no way to run:
journalctl HIVE_REPO=ivy/hive HIVE_ISSUE=42
Additionally, systemd-run in internal/jail/systemd.go doesn't pass any LogExtraFields= properties, so child-process output (the agent itself) is completely untagged in the journal.
Expected behavior
slog-journal handler used when stderr is connected to the journal (fall back to TextHandler for interactive use)
- Structured slog attrs map to journal fields automatically (this is what slog-journal does)
systemd-run invocations include LogExtraFields=HIVE_REPO=<repo> and LogExtraFields=HIVE_ISSUE=<number> so agent output is queryable
journalctl HIVE_REPO=owner/repo HIVE_ISSUE=42 --output=json returns structured entries for a single job
Relevant code
| File |
What's wrong |
cmd/hive/main.go:58-73 |
setupLogger() uses TextHandler instead of journal handler |
internal/jail/systemd.go:22-35 |
systemd-run args have no LogExtraFields |
go.mod |
Missing github.com/systemd/slog-journal dependency |
References
- ADR-002 (
docs/adrs/002-tech-stack.md) — chose slog-journal, documented expected behavior
- Architecture doc (
docs/architecture.md) — describes journald as the logging target
- slog-journal: https://github.com/systemd/slog-journal
Problem
Logging currently uses
slog.NewTextHandlerwriting to stderr. This makes it impossible to trace a specific job usingjournalctlstructured queries — you can't filter by repo or ticket number.Three concrete issues:
1. slog-journal not wired up
ADR-002 chose
systemd/slog-journaland CLAUDE.md documents it in the tech stack, but the dependency was never added.setupLogger()incmd/hive/main.gocreates a plainTextHandler:2. Duplicate timestamps
TextHandleremits its owntime=prefix on every line. When hive runs as a systemd service (or undersystemd-run), journald also stamps each line. The result is two timestamps per log entry — one from slog, one from the journal — neither of which is useful for structured queries.3. No way to filter by repo/ticket
Structured fields like
repoandissueare passed as slog key-value args today, butTextHandlerflattens them into free-text. There's no way to run:Additionally,
systemd-runininternal/jail/systemd.godoesn't pass anyLogExtraFields=properties, so child-process output (the agent itself) is completely untagged in the journal.Expected behavior
slog-journalhandler used when stderr is connected to the journal (fall back toTextHandlerfor interactive use)systemd-runinvocations includeLogExtraFields=HIVE_REPO=<repo>andLogExtraFields=HIVE_ISSUE=<number>so agent output is queryablejournalctl HIVE_REPO=owner/repo HIVE_ISSUE=42 --output=jsonreturns structured entries for a single jobRelevant code
cmd/hive/main.go:58-73setupLogger()usesTextHandlerinstead of journal handlerinternal/jail/systemd.go:22-35systemd-runargs have noLogExtraFieldsgo.modgithub.com/systemd/slog-journaldependencyReferences
docs/adrs/002-tech-stack.md) — chose slog-journal, documented expected behaviordocs/architecture.md) — describes journald as the logging target