Documentation
¶
Overview ¶
Package guard keeps builds from taking the machine down. Run takes one of the machine's build slots and runs a command in a memory-capped systemd scope; Hook is the PreToolUse hook that routes build, test, lint and lab commands through it and refuses a third kind lab.
The slots are memcap's: flock files <slotDir>/<i>.lock with a <i>.holder record in memcap's format, so beekeeper and the memcap shell wrapper share them.
Index ¶
- Constants
- func CommandHead(argv []string) string
- func CurrentContext(kubeconfig string) string
- func IsTestScope(unit string) bool
- func KubeGuardOff(production string) string
- func MandatoryReads(repo string, budget int) string
- func ParseSize(s string) (int64, error)
- func ParseWait(s string) (time.Duration, error)
- func RepoRoot(p string) string
- func ReportCheck(input []byte, check func(string) []string) []byte
- func Run(o Options, argv []string) int
- func RunCommand(detail string) string
- func RunScope(detail string) string
- func ShellQuote(s string) string
- type Hook
- type ModelServer
- type Options
- type ReadsMarker
- type Request
Constants ¶
const ( // ExitBusy: no slot, or not enough memory for the cap, within the wait. ExitBusy = 75 // ExitNotFound: the command could not be started. ExitNotFound = 127 )
Exit codes Run returns besides the command's own.
const ( VerbStart = "run.start" VerbEnd = "run.end" )
The event log verbs of a capped run. Their detail is "<scope> <facts>: <command>": the scope's unit name exactly as the kernel prints it in an OOM kill's memcg path, then the slot and the cap (start) or the exit code, the duration and the cap's victims (end), then CommandHead.
const ( ScopePrefix = "memcap-" TestScopePrefix = "memcap-test-" )
A capped run's scope is <ScopePrefix><pid>-<n>.scope in memcap.slice. A test's run (Options.Test) takes TestScopePrefix instead, so that the cap kill it provokes on purpose reads as a test kill, never as a build's.
const AskTool = "AskUserQuestion"
AskTool is Claude Code's tool that puts a question to the person and blocks the session until the person answers.
const LogPrefix = "beekeeper run: "
LogPrefix starts every line Run writes, the cap's victim line included.
const ModeAcceptEdits = "acceptEdits"
ModeAcceptEdits is the mode Claude Desktop turns a bypassPermissions session into when it imports it (claude://resume).
const PostTool = "slack_send_message"
PostTool is the suffix of the tool that posts a report: the Slack connector's slack_send_message.
const ReadsBudget = 10000
ReadsBudget bounds the context one repository's reads add to a call, in bytes.
const SendMessageTool = "SendMessage"
SendMessageTool is the tool one Claude session messages another with.
Variables ¶
This section is empty.
Functions ¶
func CommandHead ¶ added in v0.11.0
CommandHead is the command a run's events name: a shell's -c script rather than the shell (the hook wraps every build in zsh -c), on one line, at most 100 characters.
func CurrentContext ¶ added in v0.33.0
CurrentContext is the machine kubeconfig's current context, "" when it has none or cannot be read.
func IsTestScope ¶ added in v0.15.3
IsTestScope reports whether a scope's unit name is a test run's.
func KubeGuardOff ¶ added in v0.47.0
KubeGuardOff says why the kube guard is off, "" while it is on.
func MandatoryReads ¶ added in v0.32.0
MandatoryReads gathers repo's instructions for an agent that did not start in it: CLAUDE.md and AGENTS.md with their @imports, .claude/rules, the hooks and permissions of .claude/settings.json, and the files these name as mandatory reading; within budget bytes, "" when the repository has none. Only regular text files inside repo are read, never a secret name; repo is a RepoRoot, its symlinks resolved.
func RepoRoot ¶ added in v0.32.0
RepoRoot is the top of the git repository or worktree holding p, "" when p is in none. It reads only the file system, never runs git.
func ReportCheck ¶ added in v0.27.0
ReportCheck decides one PreToolUse event of a reporter session: a post whose message check finds wrong (post.Report) is denied, with what to fix as the reason. Any other tool passes, nil; a post without a message is denied.
func Run ¶
Run runs argv in a capped scope and returns its exit code: the command's, 128+signal when a signal ended it, ExitBusy when the wait ran out. Without a user systemd, or inside a capped scope already, it execs argv.
func RunCommand ¶ added in v0.11.0
RunCommand is the command a run event names.
func ShellQuote ¶
ShellQuote quotes s for a POSIX shell as Python's shlex.quote does.
Types ¶
type Hook ¶
type Hook struct {
// Self is the absolute path of the beekeeper binary a rewrite names.
Self string
// ConfigErr is why the configuration did not load: every Bash call is
// refused with it, since the guards it configures cannot run.
ConfigErr error
// Shell is the shell a rewritten command runs in (sh -c semantics).
Shell string
// MaxLabs is how many kind clusters the machine runs at most; read
// only for a command that creates one.
MaxLabs func() int
// Production is the installation the kube guard protects; empty, the
// guard is off.
Production string
// ContextHint is how a refusal writes an installation's context
// ("login.example.com-<installation>"); empty, "<context>".
ContextHint string
// Clusters lists the running kind clusters.
Clusters func() []string
// Leases lists the held leases.
Leases func() []lease.Holder
// Guide reports whether the session is the guide's, and the person the
// guide asks; read only for an AskUserQuestion call.
Guide func(session string) (bool, string)
// Peer names the running CLI of a desktop session id, "" when none
// runs; an error refuses the send. Nil passes every SendMessage.
Peer func(host string) (string, error)
// Project is the session's own project ($CLAUDE_PROJECT_DIR), whose
// instructions Claude Code loads itself; "" takes the call's cwd.
Project string
// Reads reports whether a call is the session's first write in the
// repository, and records it; nil adds no repository reads.
Reads func(session, repo string) bool
// Kubeconfig is the kubeconfig list the session's commands use by
// default ($KUBECONFIG, else MachineKubeconfig).
Kubeconfig string
// MachineKubeconfig is the machine kubeconfig (~/.kube/config), which
// keeps no current context.
MachineKubeconfig string
// ModelServer reads the host's model server, whose loads need its
// lease; called only for a command that may load a model, nil guards
// nothing.
ModelServer func() ModelServer
}
Hook decides one PreToolUse event.
type ModelServer ¶ added in v0.40.0
type ModelServer struct {
// URL is ollama's API, LemonadeURL Lemonade Server's; "" guards
// neither.
URL string
LemonadeURL string
// LabTests are the agentlab subcommands that run turns on its models.
LabTests []string
}
ModelServer is what the hook knows of the host's model servers.
type Options ¶
type Options struct {
// Max is the scope's MemoryMax, a systemd size ("12G").
Max string
// Swap is the scope's MemorySwapMax; "0": a build does not swap.
Swap string
// Wait bounds the wait for a slot and for the memory the cap needs.
Wait time.Duration
SlotDir string
Slots int
Stderr io.Writer
// Record appends a run event (VerbStart, VerbEnd) to the event log; nil
// records nothing. It must not fail or block the run: it swallows its
// own errors and bounds its wait.
Record func(verb, detail string)
// Test names the scope TestScopePrefix…: a test's run, whose cap kill
// snapshot and watch report as a test kill.
Test bool
}
Options configure one capped run.
type ReadsMarker ¶ added in v0.32.0
type ReadsMarker struct {
Dir string
}
ReadsMarker records which repositories a session was given, one empty file per session and repository under Dir.
func (ReadsMarker) First ¶ added in v0.32.0
func (m ReadsMarker) First(session, repo string) bool
First reports whether this is the session's first call for repo, and records it: the marker is created exclusively, so of two concurrent calls only one is first. Any error reads as not first: the reads are extra.
type Request ¶ added in v0.22.0
type Request struct {
Event string `json:"hook_event_name"`
Session string `json:"session_id"`
Mode string `json:"permission_mode"`
Tool string `json:"tool_name"`
}
Request is the part of a PermissionRequest event the hook reads.
func Permission ¶ added in v0.22.0
Permission decides one PermissionRequest event. It allows the request only when the session runs in acceptEdits and started reports it as a session beekeeper started in bypassPermissions; any other event, malformed input included, gets no decision (nil) and the person gets the normal card. started is asked only for an acceptEdits request, so every other request is decided without reading beekeeper's state.