Documentation
¶
Overview ¶
Package watchd implements the chunk watch background daemon and its client.
Index ¶
- Constants
- Variables
- func BuildID() string
- func CancelSession(id string) error
- func ClaimNotice(overlaps []ClaimState) string
- func ConflictNotice(rep ConflictReport) string
- func ConflictStatus(rep ConflictReport) string
- func EnsureDir() (string, error)
- func EnsureLaunched(subArgs []string) error
- func EnsureRunning(subArgs []string) error
- func IsDaemonCompatible() bool
- func IsDaemonRunning() bool
- func IsRunning(path string) (bool, int, error)
- func LogPath() (string, error)
- func PIDPath() (string, error)
- func RegisterCommand(reg CommandReg)
- func ResumeSession(id string) error
- func RunDaemon(ctx context.Context, client *circleci.Client, authMessage string, ...) error
- func SocketPath() (string, error)
- func StartAsyncValidate(projectRoot string, args []string, circleCIToken string) (string, error)
- func StartSession(req SessionRequest) (string, error)
- func StopForCredentialChange()
- func TCPListenAddr() string
- func TCPRemoteAddr() string
- func TCPToken() string
- type AsyncValidateResponse
- type ClaimState
- type CollectResponse
- type CommandReg
- type CommandState
- type ConflictReport
- type ConflictState
- type Connection
- type EditedSinceError
- type FileChange
- type FixState
- type Option
- type OutputChunk
- type PRCheck
- type PRComment
- type PRState
- type ProjectSnapshot
- type PromptRunState
- type ProvisionRequest
- type ProvisionResponse
- type Resources
- type RestorePoint
- type RestoreRequest
- type RestoreResult
- type ReviewConfig
- type ReviewPool
- type ReviewPoolSpec
- type ReviewPrompt
- type ReviewResult
- type RiskSummary
- type Round
- type RoundDetail
- type RoundFix
- type RoundState
- type Session
- type SessionDetail
- type SessionList
- type SessionRefused
- type SessionRequest
- type SessionStartResponse
- type SessionState
- type SidecarState
- type Snapshot
- type Stage
- type StageID
- type StageState
- type StreamFunc
- type SubmitFunc
- type TaskState
- type ValidateRequest
- type ValidateResponse
- type ValidateRunner
Constants ¶
const ( // ConflictInterval is how often the daemon re-evaluates whether each // project's branch still merges cleanly. Far slower than PollInterval // because the answer only changes when a commit lands on either side, and // the check costs a merge in the object database rather than a stat. ConflictInterval = 60 * time.Second // FetchInterval is how often the merge target's remote-tracking ref is // refreshed. The check is worthless against a ref nobody has updated in a // week, and expensive if refreshed every time: this is the compromise. FetchInterval = 3 * time.Minute // MaxConflictPaths caps the paths reported for one branch. A merge that // conflicts in hundreds of files is one fact — "this branch has diverged // badly" — and listing all of them buries it. MaxConflictPaths = 20 )
const ( // PollInterval is how often the daemon refreshes project state from disk. PollInterval = 5 * time.Second // RecentEvents is the maximum number of events kept per project. RecentEvents = 300 // RunningTimeout is how long after the last non-terminal event a sidecar is // considered to still be running. RunningTimeout = 5 * time.Minute )
const ( // MaxCommandBytes is the default per-command output buffer cap in bytes. // Override at runtime with CHUNK_OUTPUT_BUFFER_SIZE so the cap can be tuned // via a sandbox-provisioner deploy without rebuilding images. MaxCommandBytes = 10 << 20 // 10 MB // MaxCommands caps retained commands per project. Only finished commands are // evicted, so a project running more than this many at once keeps them all. MaxCommands = 20 )
const ( // SampleInterval is how often the remote sampler emits a reading. SampleInterval = 2 * time.Second // StaleSamples is how many intervals a sample may age before the dashboard // should treat it as stale. A sampler that dies must look stalled rather than // look like an idle sidecar, so the last value is kept and marked, not // discarded. // // The budget has to cover more than SampleInterval: the reading crosses an SSH // connection before the daemon sees it, and the dashboard then renders that // same snapshot until its next 5s poll while re-evaluating the age every // frame. Measured against real sidecars, a healthy sampler reaches ~10s of // apparent age just before a poll lands, so a tighter bound flags working // samplers as stale for part of every cycle. Twelve seconds clears that and // still catches a dead sampler within two polls. StaleSamples = 6 )
const ( BandLow = "low" BandMedium = "medium" BandHigh = "high" )
Risk score bands. They exist so a caller can act on the score without hard-coding a number, and so the number itself stays arguable: a band is a claim about caution, not a probability of failure.
const DefaultAsyncMaxLines = 500
DefaultAsyncMaxLines is how large a change may be, in lines, and still be validated in the background.
The number is a judgement, not a measurement. What it is really choosing is how much of the inner loop runs without waiting: nearly every edit an agent makes in one turn lands under it, and the changes that do not are the ones where a developer is most likely to want the answer before doing anything else. Projects that disagree can say so — see config.AsyncValidateMaxLines.
const MaxRounds = 3
MaxRounds is how many review-and-fix rounds one session runs at most.
const MaxSessionsPerProject = 10
MaxSessionsPerProject caps retained sessions per project. Only finished ones are evicted, so a live session is never lost.
const MaxTasksPerProject = 20
MaxTasksPerProject caps retained validation tasks per project. Only finished tasks are evicted, so a project cannot lose a run that is still going. It is also what bounds delivered results, which collect keeps rather than deletes.
const PRPollInterval = 60 * time.Second
PRPollInterval is the minimum time between PR fetches for a given branch. GitHub's API is rate-limited, so polling too frequently is wasteful. A branch change resets the clock so the new branch's PR is fetched promptly.
Variables ¶
var ErrAsyncRefused = errors.New("async validation refused")
ErrAsyncRefused is returned by StartAsyncValidate when the daemon will not take this run asynchronously: the tree could not be fingerprinted, so a stale result would be undetectable, or the project already has its cap of runs in flight. Callers fall back to running inline, where the answer reaches whoever asked for it while it is still true. The daemon's reason is wrapped alongside the sentinel.
var ErrDaemonPermission = errors.New("the watch daemon socket is not accessible to this user")
ErrDaemonPermission reports that the socket is there but this user cannot open it — a daemon running as somebody else, or a directory whose mode has been changed.
Kept apart from ErrDaemonUnreachable for the same reason ErrDaemonTimeout is: the advice differs. Starting a second daemon leaves the same socket just as unreadable, so "run chunk watch" is the one thing that cannot help here.
var ErrDaemonTimeout = errors.New("the watch daemon did not answer in time")
ErrDaemonTimeout reports that a daemon was there but did not answer within conflictTimeout.
Kept apart from ErrDaemonUnreachable because the two call for different advice: one means start the daemon, the other means it is already running and busy, so asking again is what helps. Collapsing them told people with a working daemon to go start one.
ErrDaemonUnavailable is returned by RunValidate when the daemon socket is unreachable, so callers can distinguish a transient connectivity failure from a real validation error and fall back to inline execution.
var ErrDaemonUnreachable = errors.New("no watch daemon is running")
ErrDaemonUnreachable reports that no watch daemon answered.
Callers on the hook path are expected to treat this as "nothing to say" and carry on. The daemon is optional — it runs when the developer has `chunk watch` open — and a hook that complains about its absence would fire on every session end for everyone who does not.
var ErrEditedSince = errors.New("files were edited after the session changed them")
ErrEditedSince is returned by restoreSession when files the session changed have been edited since, and restoring would overwrite those edits.
ErrUnauthorized reports that a remote daemon rejected the bearer token. It is its own sentinel because the fix (set CHUNK_WATCHD_TCP_TOKEN to the daemon's value) has nothing in common with the fix for an unreachable daemon.
Functions ¶
func BuildID ¶ added in v0.7.164
func BuildID() string
BuildID identifies the binary a process was started from.
The daemon serves snapshots shaped by the code it was started from: a field added to SidecarState since then is absent rather than wrong, so a newer client renders a well-formed view of stale data with nothing to say why. A sidecar owned by a session, for instance, arrives from a pre-session daemon looking like a sidecar nobody owns. Comparing this on every ping is what makes that visible instead of silent.
The version alone will not do: every local build reports the same development version, so the executable's path, size and modification time come along to tell two of them apart. Path is included so a dev build and an installed one are never mistaken for each other.
func CancelSession ¶ added in v0.7.196
CancelSession stops a session. It is the only thing that does: a viewer that quits merely detaches.
func ClaimNotice ¶ added in v0.7.188
func ClaimNotice(overlaps []ClaimState) string
ClaimNotice renders the advisory an agent should be told about when another session is actively validating overlapping paths. Returns "" when there are no overlaps — the common case — so an agent that checks this field sees nothing in the vast majority of turns and is not trained to skip it.
The wording states plainly that this is informational and must not divert the agent from its current task.
func ConflictNotice ¶ added in v0.7.186
func ConflictNotice(rep ConflictReport) string
ConflictNotice renders the advisory an agent should be told about, or "" when there is nothing to advise.
Empty is the common case and the important one: no conflict, no answer yet, no daemon, a branch that is itself the merge target — all of them produce no text at all. An advisory that speaks when it has nothing to say trains the reader to skip it, which costs it the one time it matters.
The wording states plainly that this is not a gate. The agent reading it is the same one that treats a failed `chunk validate` as work to do before it can continue, and a notice that reads like a check would divert it into a rebase in the middle of an unrelated task.
func ConflictStatus ¶ added in v0.7.186
func ConflictStatus(rep ConflictReport) string
ConflictStatus renders the full state for someone who ran the command by hand, including every reason ConflictNotice stays silent about.
The two differ on purpose. A person typing `chunk conflicts` and getting no output cannot tell a clean merge from a daemon that is not running, and both answers change what they do next; an agent being handed the same distinction mid-task gains nothing from it.
func EnsureLaunched ¶ added in v0.7.164
EnsureLaunched starts the daemon when nothing is answering and otherwise leaves whatever is there alone.
Unlike EnsureRunning it never replaces a daemon from another build. It is called when a poll fails mid-session, and a dashboard that has been open for a while has no business restarting a daemon another one is using: the build check is a startup decision, made once, where the cost of being wrong is one restart rather than a restart per poll for as long as two dashboards are open.
func EnsureRunning ¶
EnsureRunning checks whether the watch daemon is running and serving, and launches it if not. subArgs are the CLI arguments used to invoke the daemon (e.g. ["watch", "_daemon"]).
func IsDaemonCompatible ¶ added in v0.7.175
func IsDaemonCompatible() bool
IsDaemonCompatible reports whether the watch daemon is reachable and suitable for delegation. For a local daemon that means matching the current build (a build mismatch means it may not support all API endpoints). For a remote daemon the build ID can never match — the binary lives on a different host with a different path and mtime — so reachability is the meaningful check.
func IsDaemonRunning ¶ added in v0.7.174
func IsDaemonRunning() bool
IsDaemonRunning reports whether the watch daemon is reachable. Use IsDaemonCompatible when the caller needs to confirm the build identity too.
func IsRunning ¶
IsRunning reports whether the process whose PID is stored in path is alive. Returns (false, 0, nil) when the file doesn't exist.
func RegisterCommand ¶ added in v0.7.173
func RegisterCommand(reg CommandReg)
RegisterCommand tells the running watch daemon to stream and buffer a command's output.
It is best-effort by design and reports no error. If the daemon is not running, the command still runs and still streams to the caller's own stdout; the only thing lost is the buffered copy. Notably this does not start the daemon: spawning a background process as a side effect of a hook firing is intrusive, and a hook that hangs waiting for a daemon launch is a far worse failure than a missing logs pane.
func ResumeSession ¶ added in v0.7.196
ResumeSession continues a paused session with the files as they are now.
func RunDaemon ¶
func RunDaemon(ctx context.Context, client *circleci.Client, authMessage string, runner ValidateRunner, ghClient *github.Client, opts ...Option) error
RunDaemon is the watch daemon entry point, called by the hidden _daemon subcommand.
client and authMessage support the output-buffering feature; runner is called in-process to handle /validate requests. Both client and runner may be nil (the daemon still records commands without a client, and /validate returns an error without a runner). ghClient may be nil; PR monitoring is skipped when no GitHub credentials are available.
func SocketPath ¶
SocketPath returns the path to the daemon Unix socket.
func StartAsyncValidate ¶ added in v0.7.186
StartAsyncValidate asks the daemon to validate projectRoot in the background and returns the new task's ID without waiting for the run.
func StartSession ¶ added in v0.7.196
func StartSession(req SessionRequest) (string, error)
StartSession asks the daemon to start a session and returns its ID without waiting for it.
func StopForCredentialChange ¶ added in v0.7.173
func StopForCredentialChange()
StopForCredentialChange stops a running watch daemon so that the next launch picks up newly stored credentials.
The daemon resolves its CircleCI client once, at startup, so one that started before a login holds a nil client for the rest of its life and streams no output however many times the developer retries. Stopping it here is what makes `chunk auth login` take effect: a `chunk watch` already on screen relaunches it through EnsureLaunched on its next poll, and otherwise the next `chunk watch` starts a daemon that can authenticate.
Best-effort and silent, like RegisterCommand. Failing to stop the daemon must not fail a login that has otherwise succeeded, and the cost of not stopping it is the buffered output of a daemon that was not streaming anything anyway.
func TCPListenAddr ¶ added in v0.7.187
func TCPListenAddr() string
TCPListenAddr returns the TCP address the daemon should bind, read from CHUNK_WATCHD_TCP_ADDR (e.g. "0.0.0.0:7777"). Empty means TCP is disabled.
func TCPRemoteAddr ¶ added in v0.7.187
func TCPRemoteAddr() string
TCPRemoteAddr returns the address of a remote daemon to connect to, read from CHUNK_WATCHD_REMOTE_ADDR (e.g. "sandbox-host:7777"). Empty means clients use the local Unix socket.
Types ¶
type AsyncValidateResponse ¶ added in v0.7.186
type AsyncValidateResponse struct {
TaskID string `json:"task_id"`
// ClaimWarning is an advisory when another session is actively validating
// overlapping paths. Empty in the common case (no overlap). Never a gate.
ClaimWarning string `json:"claim_warning,omitempty"`
}
AsyncValidateResponse acknowledges an accepted async run.
type ClaimState ¶ added in v0.7.188
type ClaimState struct {
SessionID string `json:"session_id"`
ProjectRoot string `json:"project_root"`
// Paths are the repo-relative paths changed in this session's working tree,
// captured at claim registration time. Nil means the change could not be
// measured; treat as potentially overlapping anything in the project.
Paths []string `json:"paths,omitempty"`
ClaimedAt time.Time `json:"claimed_at"`
ExpiresAt time.Time `json:"expires_at"`
}
ClaimState describes one active validate claim held by a session. It is advisory — no blocking is done based on it.
type CollectResponse ¶ added in v0.7.186
type CollectResponse struct {
Tasks []TaskState `json:"tasks"`
}
CollectResponse carries the finished results for a project.
type CommandReg ¶ added in v0.7.173
type CommandReg struct {
CommandID string `json:"command_id"`
SidecarID string `json:"sidecar_id"`
ProjectRoot string `json:"project_root"`
Op string `json:"op"`
Name string `json:"name"`
SubmittedAt time.Time `json:"submitted_at"`
}
CommandReg is the registration a process sends after submitting a remote command, so the daemon can stream and buffer that command's output. The submitting process may exit immediately afterwards — that is the whole point, since most remote commands are run by a hook that exits as soon as the command finishes.
type CommandState ¶ added in v0.7.173
type CommandState struct {
CommandID string `json:"command_id"`
SidecarID string `json:"sidecar_id"`
Op string `json:"op"`
Name string `json:"name"`
SubmittedAt time.Time `json:"submitted_at"`
EndedAt *time.Time `json:"ended_at,omitempty"`
ExitCode *int `json:"exit_code,omitempty"`
Running bool `json:"running"`
Bytes int64 `json:"bytes"`
Truncated bool `json:"truncated"`
}
CommandState describes one remote command the daemon is buffering output for.
type ConflictReport ¶ added in v0.7.186
type ConflictReport struct {
Root string `json:"root"`
// Conflict is nil when the daemon has no answer for this root — either it
// does not know the project, or no check has run yet.
Conflict *ConflictState `json:"conflict,omitempty"`
// Known reports whether the daemon is tracking the root at all. Without it
// "unknown project" and "checked, nothing to report" are the same response.
Known bool `json:"known"`
}
ConflictReport is the response to a conflict query for one project root.
func FetchConflicts ¶ added in v0.7.186
func FetchConflicts(root string) (ConflictReport, error)
FetchConflicts asks the running daemon whether root's branch still merges cleanly into its merge target.
Unlike RegisterCommand this reports its errors, because the caller decides how loudly to fail: a hook stays quiet, a person running the command by hand gets told why there is no answer.
type ConflictState ¶ added in v0.7.186
type ConflictState struct {
// Branch is the branch that was compared, empty when there was none.
Branch string `json:"branch,omitempty"`
// Target is the merge target, qualified as the remote names it —
// "origin/main".
Target string `json:"target,omitempty"`
// HeadSHA and TargetSHA are the two commits actually merged. They are what
// makes a result reusable: neither side moving means the merge would
// resolve identically.
HeadSHA string `json:"head_sha,omitempty"`
TargetSHA string `json:"target_sha,omitempty"`
// Conflicted reports that the merge does not resolve automatically. False
// with an empty Unavailable is a real all-clear; false with Unavailable set
// means no merge was attempted.
Conflicted bool `json:"conflicted"`
// Paths lists the conflicted paths, capped at MaxConflictPaths.
Paths []string `json:"paths,omitempty"`
// TotalPaths is how many paths conflicted in total, which exceeds len(Paths)
// when the list was cut.
TotalPaths int `json:"total_paths,omitempty"`
// CheckedAt is when the answer was produced, TargetFetchedAt when the
// target's remote-tracking ref was last refreshed. The second is the one
// that decides how much the answer is worth.
CheckedAt time.Time `json:"checked_at"`
TargetFetchedAt time.Time `json:"target_fetched_at"`
// TargetStale reports that the last refresh of the target ref failed, so
// the comparison ran against whatever was already on disk. The answer may
// simply be out of date, which is worth saying rather than implying.
TargetStale bool `json:"target_stale,omitempty"`
// one. A detached HEAD, a repo with no recorded default branch, and a
// branch that is itself the merge target all land here — none of them are
// faults, and all of them would otherwise look like "no conflicts".
Unavailable string `json:"unavailable,omitempty"`
}
ConflictState is the daemon's answer to whether one project's branch still merges cleanly into its merge target.
It describes committed history only. The preview merges HEAD against the target, so uncommitted work in the tree is invisible to it: a conflict that exists solely in unstaged edits is not reported here, and anything presenting this to a person has to say so rather than let the silence read as an all-clear.
type Connection ¶ added in v0.7.196
type Connection struct {
// Remote is the TCP address of a remote daemon, empty for the local socket.
Remote string
}
Connection describes which daemon this process talks to.
func CurrentConnection ¶ added in v0.7.196
func CurrentConnection() Connection
CurrentConnection reports the daemon this process is configured to use.
func (Connection) Label ¶ added in v0.7.196
func (c Connection) Label() string
Label is the short human-readable form: "local", or "remote host:port".
type EditedSinceError ¶ added in v0.7.196
type EditedSinceError struct{ Paths []string }
EditedSinceError names the files, and wraps ErrEditedSince.
func (*EditedSinceError) Error ¶ added in v0.7.196
func (e *EditedSinceError) Error() string
func (*EditedSinceError) Unwrap ¶ added in v0.7.196
func (e *EditedSinceError) Unwrap() error
type FileChange ¶ added in v0.7.196
type FileChange struct {
Path string `json:"path"`
Insertions int `json:"insertions"`
Deletions int `json:"deletions"`
}
FileChange is one file a round's fixes changed in the working tree.
type FixState ¶ added in v0.7.196
type FixState string
FixState is where the fix half of a round stands.
type Option ¶ added in v0.7.196
type Option func(*daemonOptions)
Option customizes RunDaemon.
func WithReview ¶ added in v0.7.196
func WithReview(cfg ReviewConfig) Option
WithReview configures the daemon's review capability.
type OutputChunk ¶ added in v0.7.173
type OutputChunk struct {
// Data is raw command output, exactly as the remote command wrote it —
// interleaved stdout and stderr, ANSI and carriage returns intact.
Data []byte `json:"data"`
// NextOffset is the offset to pass on the following read.
NextOffset int64 `json:"next_offset"`
Running bool `json:"running"`
ExitCode *int `json:"exit_code,omitempty"`
// Truncated reports that output before the returned data was evicted and is
// gone. Saying so is the difference between showing a partial run and
// showing a partial run that looks whole.
Truncated bool `json:"truncated"`
// Found is false when the daemon knows nothing about the command.
Found bool `json:"found"`
// Error explains why streaming stopped early, when it did. Without it a
// failed stream is indistinguishable from a command that produced no output,
// which sends the reader looking for a bug in their own command.
Error string `json:"error,omitempty"`
}
OutputChunk is one response to an output read.
func FetchOutput ¶ added in v0.7.173
func FetchOutput(commandID string, offset int64) (OutputChunk, error)
FetchOutput reads buffered output for a command starting at offset.
type PRCheck ¶ added in v0.7.186
type PRCheck struct {
Name string `json:"name"`
Status string `json:"status"` // QUEUED, IN_PROGRESS, COMPLETED
Conclusion string `json:"conclusion"` // SUCCESS, FAILURE, NEUTRAL, CANCELLED, etc.
}
PRCheck is one CI check on a PR's latest commit.
type PRComment ¶ added in v0.7.186
type PRComment struct {
Author string `json:"author"`
Body string `json:"body"`
CreatedAt time.Time `json:"created_at"`
Resolved bool `json:"resolved,omitempty"`
}
PRComment is one review thread comment on a PR.
type PRState ¶ added in v0.7.186
type PRState struct {
Number int `json:"number"`
Title string `json:"title"`
URL string `json:"url"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
CheckState string `json:"check_state,omitempty"` // rollup: SUCCESS, FAILURE, PENDING, ERROR, EXPECTED
Checks []PRCheck `json:"checks,omitempty"`
Comments []PRComment `json:"comments,omitempty"`
ChangesRequested bool `json:"changes_requested,omitempty"`
FetchedAt time.Time `json:"fetched_at"`
}
PRState is the daemon's advisory view of the open PR for a project's current branch. It is nil when no open PR exists for the branch or when GitHub credentials are absent.
type ProjectSnapshot ¶
type ProjectSnapshot struct {
Root string `json:"root"`
Branch string `json:"branch"`
HeadRef string `json:"head_ref"`
RepoName string `json:"repo_name"`
Sidecars []SidecarState `json:"sidecars"`
Events []eventlog.Event `json:"events"`
Commands []CommandState `json:"commands,omitempty"`
// Conflict is nil until the first conflict check for this project has run.
// Nil is "not known yet", distinct from a ConflictState reporting no
// conflict, and the two must not be collapsed by a reader.
Conflict *ConflictState `json:"conflict,omitempty"`
// PR is the advisory PR state for the current branch. Nil when no open PR
// exists, or when GitHub credentials are not configured.
PR *PRState `json:"pr,omitempty"`
// ActiveClaims lists validate claims from all sessions currently active for
// this project. Nil when no sessions are validating.
ActiveClaims []ClaimState `json:"active_claims,omitempty"`
// Sessions lists this project's pre-PR sessions, newest first. State only:
// the text of a review is fetched on demand through GET /session/{id}.
Sessions []Session `json:"sessions,omitempty"`
}
ProjectSnapshot is the daemon's view of one project at a point in time.
type PromptRunState ¶ added in v0.7.196
type PromptRunState string
PromptRunState is where one review of a round stands. The values match review.PromptState one for one, spelled out so they read in JSON and survive the enum being reordered.
const ( PromptQueued PromptRunState = "queued" PromptRunning PromptRunState = "running" PromptDone PromptRunState = "done" PromptFailed PromptRunState = "failed" )
Review states.
func (PromptRunState) Progress ¶ added in v0.7.196
func (s PromptRunState) Progress() review.PromptState
Progress maps the state onto the review package's own, which is what the row renderers shared with `chunk review` are written against. An unknown value reads as queued: it comes from a daemon that may be newer than this client.
type ProvisionRequest ¶ added in v0.7.187
type ProvisionRequest struct {
OrgID string `json:"org_id"`
Name string `json:"name"`
Image string `json:"image,omitempty"`
}
ProvisionRequest is sent to POST /sidecar.
type ProvisionResponse ¶ added in v0.7.187
type ProvisionResponse struct {
SidecarID string `json:"sidecar_id"`
}
ProvisionResponse is returned from POST /sidecar.
type Resources ¶ added in v0.7.174
type Resources struct {
CPUPercent float64 `json:"cpu_percent"`
MemUsedBytes int64 `json:"mem_used_bytes"`
MemLimitBytes int64 `json:"mem_limit_bytes"`
DiskUsedBytes int64 `json:"disk_used_bytes"`
DiskTotalBytes int64 `json:"disk_total_bytes"`
SampledAt time.Time `json:"sampled_at"`
}
Resources is one sample of a sidecar's resource usage.
Memory is reported as used-of-limit rather than a percentage so the display can show both, and because a limit of zero (unknown) has to be distinguishable from a usage of zero.
type RestorePoint ¶ added in v0.7.196
type RestorePoint struct {
// Ref is the git ref that holds the snapshot, so it survives garbage
// collection and a daemon restart.
Ref string `json:"ref"`
HeadSHA string `json:"head_sha"`
SavedAt time.Time `json:"saved_at"`
// Paths are the files the session's fixes changed: what a restore puts back.
Paths []string `json:"paths,omitempty"`
Restored bool `json:"restored,omitempty"`
}
RestorePoint is the saved state a session can be undone to.
type RestoreRequest ¶ added in v0.7.196
type RestoreRequest struct {
// Force restores even files that were edited after the session changed them,
// discarding those edits.
Force bool `json:"force,omitempty"`
}
RestoreRequest asks to undo a session's changes.
type RestoreResult ¶ added in v0.7.196
type RestoreResult struct {
Paths []string `json:"paths"`
}
RestoreResult lists the files a restore put back (or removed, if the session had created them).
func RestoreSession ¶ added in v0.7.196
func RestoreSession(id string, force bool) (RestoreResult, error)
RestoreSession undoes everything a session changed in the working tree.
type ReviewConfig ¶ added in v0.7.196
type ReviewConfig struct {
Credential review.Credential
// BaseURL is forwarded to claude when it is not Anthropic's own.
BaseURL string
// AuthError explains a missing Credential, reported in the snapshot so an
// absent capability is explained rather than silent.
AuthError string
// The fields below are test seams. Left nil, the daemon builds a real pool
// from its CircleCI client and talks to the sandbox through it.
NewPool func(ctx context.Context, spec ReviewPoolSpec) (*ReviewPool, error)
Submit SubmitFunc
Stream StreamFunc
}
ReviewConfig is everything the daemon needs to run sessions. The credential is resolved once by the caller at daemon start; it is only ever placed in the environment of a Claude command, and never logged or put in a snapshot.
type ReviewPool ¶ added in v0.7.196
type ReviewPool struct {
Acquire func(context.Context) (*sidecar.PoolEntry, error)
Release func(*sidecar.PoolEntry)
WaitReady func(context.Context) error
Close func(context.Context)
}
ReviewPool is the sandbox pool one round draws from. It mirrors the methods of sidecar.Pool that RunPass needs, as fields, so a test can supply a fake without booting anything.
type ReviewPoolSpec ¶ added in v0.7.196
type ReviewPoolSpec struct {
// Root is the tracked project; its config decides org and image.
Root string
// WorkDir is the tree synced to the sandboxes: the user's project itself.
WorkDir string
Size int
}
ReviewPoolSpec says what pool a round needs.
type ReviewPrompt ¶ added in v0.7.196
type ReviewPrompt struct {
Name string `json:"name"`
State PromptRunState `json:"state"`
SidecarID string `json:"sidecar_id,omitempty"`
// CommandID names the buffered output of this review's Claude run, readable
// through /output while it runs and after.
CommandID string `json:"command_id,omitempty"`
DurationMS int64 `json:"duration_ms,omitempty"`
Error string `json:"error,omitempty"`
// Findings counts the structured findings parsed from this review's output.
Findings int `json:"findings,omitempty"`
}
ReviewPrompt is one review's progress inside a round. It carries state only: what the review said is fetched on demand through SessionDetail, the way command output is fetched through /output, so a snapshot stays small however much Claude wrote.
type ReviewResult ¶ added in v0.7.196
type ReviewResult struct {
Prompt string `json:"prompt"`
SidecarID string `json:"sidecar_id,omitempty"`
// Output is Claude's prose, capped at maxReviewOutput.
Output string `json:"output,omitempty"`
Error string `json:"error,omitempty"`
DurationMS int64 `json:"duration_ms,omitempty"`
// Findings are the structured findings the review gave, each with an ID
// unique within the round. Empty when it gave none or failed; Error tells
// the two apart.
Findings []review.Finding `json:"findings,omitempty"`
// FindingsDropped counts findings that were unusable or over the cap.
FindingsDropped int `json:"findings_dropped,omitempty"`
}
ReviewResult is one review's full output.
type RiskSummary ¶ added in v0.7.186
type RiskSummary struct {
// Score is 0–100. Higher means more caution: bigger, broader, or already
// failing. It does not decide anything on its own; see decideRisk.
Score int `json:"score"`
// Lines and Files are what was measured, kept apart from the score so a
// caller can compare two changes without unpicking one.
Lines int `json:"lines"`
Files int `json:"files"`
// Inert reports that every changed path was docs or text.
Inert bool `json:"inert,omitempty"`
// Band is Score bucketed into BandLow, BandMedium or BandHigh.
Band string `json:"band"`
// Parts are the facts behind the score, each with what it contributed.
Parts []string `json:"parts,omitempty"`
// Advice is what a developer or agent could do about it, or "" when there is
// nothing useful to say.
Advice string `json:"advice,omitempty"`
}
RiskSummary is what the daemon made of a change, as reported to a caller.
It is deliberately not just a number. A score on its own cannot be argued with — "risk 62" invites either trust or dismissal and supports neither — so the facts it was built from travel with it, and the advice that follows from them is spelled out rather than left to be inferred.
func (RiskSummary) String ¶ added in v0.7.186
func (r RiskSummary) String() string
String renders a summary as one line of terminal output.
type Round ¶ added in v0.7.196
type Round struct {
Number int `json:"number"`
State RoundState `json:"state"`
Reviews []ReviewPrompt `json:"reviews"`
// Findings counts every finding the round's reviews reported; Worth counts
// the ones worth changing (severity high or medium).
Findings int `json:"findings"`
Worth int `json:"worth"`
Fix *RoundFix `json:"fix,omitempty"`
// Note says why the round ended the way it did, such as why the loop stopped.
Note string `json:"note,omitempty"`
StartedAt time.Time `json:"started_at"`
EndedAt *time.Time `json:"ended_at,omitempty"`
}
Round is one pass of reviewing the work and fixing what the reviews found.
type RoundDetail ¶ added in v0.7.196
type RoundDetail struct {
Number int `json:"number"`
Results []ReviewResult `json:"results,omitempty"`
}
RoundDetail is the text of one round.
type RoundFix ¶ added in v0.7.196
type RoundFix struct {
State FixState `json:"state"`
Files []FileChange `json:"files,omitempty"`
Insertions int `json:"insertions,omitempty"`
Deletions int `json:"deletions,omitempty"`
// FindingIDs are the findings the agent was asked to fix.
FindingIDs []string `json:"finding_ids,omitempty"`
Error string `json:"error,omitempty"`
}
RoundFix is what a round's fixes did to the user's files.
type RoundState ¶ added in v0.7.196
type RoundState string
RoundState is where one review-and-fix round stands.
const ( RoundReviewing RoundState = "reviewing" RoundFixing RoundState = "fixing" RoundApplying RoundState = "applying" RoundDone RoundState = "done" RoundFailed RoundState = "failed" // RoundSuperseded marks a round abandoned because the files changed under it; // the round is run again against the new files. RoundSuperseded RoundState = "superseded" )
Round states.
type Session ¶ added in v0.7.196
type Session struct {
ID string `json:"id"`
ProjectRoot string `json:"project_root"`
Branch string `json:"branch,omitempty"`
HeadSHA string `json:"head_sha,omitempty"`
State SessionState `json:"state"`
// PauseReason says why a paused session is waiting, and PausedPaths which
// files changed underneath it.
PauseReason string `json:"pause_reason,omitempty"`
PausedPaths []string `json:"paused_paths,omitempty"`
Error string `json:"error,omitempty"`
// Stages is always the full flow, in order.
Stages []Stage `json:"stages"`
Rounds []Round `json:"rounds"`
// Restore is nil until the first fix is about to change the user's files.
Restore *RestorePoint `json:"restore,omitempty"`
StartedAt time.Time `json:"started_at"`
EndedAt *time.Time `json:"ended_at,omitempty"`
}
Session is the record of one pre-PR session: the review loop, and the stages that will follow it. It holds state only; text is in SessionDetail.
func ListSessions ¶ added in v0.7.196
ListSessions returns the daemon's sessions, newest first per project. An empty root lists every project's.
type SessionDetail ¶ added in v0.7.196
type SessionDetail struct {
Session
Details []RoundDetail `json:"details,omitempty"`
}
SessionDetail is a session plus the text of its reviews.
func FetchSession ¶ added in v0.7.196
func FetchSession(id string) (SessionDetail, error)
FetchSession returns one session with the text of its reviews.
type SessionList ¶ added in v0.7.196
type SessionList struct {
Sessions []Session `json:"sessions"`
}
SessionList answers GET /session.
type SessionRefused ¶ added in v0.7.196
SessionRefused is the daemon's refusal of a session request: it was reachable and understood the request, and said no. The message is the daemon's own and is meant to be shown as is; Status tells a caller which kind of no it was (409 a session is already active, 503 the daemon lacks a credential, ...).
func (*SessionRefused) Error ¶ added in v0.7.196
func (e *SessionRefused) Error() string
type SessionRequest ¶ added in v0.7.196
type SessionRequest struct {
// ProjectRoot is a project the daemon tracks. Required.
ProjectRoot string `json:"project_root"`
// PromptsDir is a directory of review prompts relative to the project root;
// empty means .chunk/reviews. Absolute paths and paths that leave the
// project are refused.
PromptsDir string `json:"prompts_dir,omitempty"`
Parallelism int `json:"parallelism,omitempty"`
Model string `json:"model,omitempty"`
// TimeoutSeconds bounds each review; zero means review.DefaultTimeout.
TimeoutSeconds int `json:"timeout_seconds,omitempty"`
// MaxRounds lowers the number of rounds; zero or more than MaxRounds means
// MaxRounds.
MaxRounds int `json:"max_rounds,omitempty"`
}
SessionRequest starts a session.
type SessionStartResponse ¶ added in v0.7.196
type SessionStartResponse struct {
ID string `json:"id"`
}
SessionStartResponse answers an accepted POST /session.
type SessionState ¶ added in v0.7.196
type SessionState string
SessionState is where a whole session stands.
const ( SessionRunning SessionState = "running" // SessionPaused means the session stopped on its own and is waiting for a // person: the files changed underneath it. PauseReason says what. SessionPaused SessionState = "paused" SessionDone SessionState = "done" SessionFailed SessionState = "failed" SessionCancelled SessionState = "cancelled" )
Session states.
func (SessionState) Finished ¶ added in v0.7.196
func (s SessionState) Finished() bool
Finished reports whether the session has ended for good. A paused session has not: it is waiting.
type SidecarState ¶
type SidecarState struct {
ID string `json:"id"`
Name string `json:"name"`
// SessionID is the agent session that owns this sidecar, empty for state
// written outside a session or before sessions existed. Sidecars are
// isolated per session, so two entries for one project and branch are two
// sessions working in the same tree — this is what tells them apart.
SessionID string `json:"session_id,omitempty"`
ProjectName string `json:"project_name"`
RepoName string `json:"repo_name"`
SnapshotName string `json:"snapshot_name"`
FileMtime time.Time `json:"file_mtime"`
// Workspace is the sidecar-side repo path, used to sample disk usage where
// the work actually happens rather than wherever a shell starts.
Workspace string `json:"workspace,omitempty"`
// OrgID is the org the sidecar lives in: the one its state file records, or
// the project's org for state written before that was recorded.
OrgID string `json:"org_id,omitempty"`
// Verified reports that the API's sidecar list confirmed this sidecar
// exists. Sidecars the list has confirmed gone never reach a snapshot, so
// false means only that nothing has confirmed it yet.
Verified bool `json:"verified,omitempty"`
LastActivity time.Time `json:"last_activity"`
LastOp eventlog.Op `json:"last_op"`
Running bool `json:"running"`
// Resources is the most recent resource sample, or nil when none has
// arrived — sampling only runs while a dashboard is attached.
Resources *Resources `json:"resources,omitempty"`
// contains filtered or unexported fields
}
SidecarState describes one active sidecar as maintained by the daemon.
type Snapshot ¶
type Snapshot struct {
Projects []ProjectSnapshot `json:"projects"`
// AuthError explains why output streaming is unavailable, when it is. An
// empty logs pane with no explanation sends people hunting the wrong fault,
// so the daemon reports this rather than silently serving nothing.
AuthError string `json:"auth_error,omitempty"`
// ReviewAuthError explains why the daemon cannot run sessions (no Claude
// credential, or no CircleCI login), empty when it can. It never contains
// the credential itself.
ReviewAuthError string `json:"review_auth_error,omitempty"`
}
Snapshot is a point-in-time view of all watched projects.
func FetchSnapshot ¶
FetchSnapshot connects to the running watch daemon and returns the current snapshot for the given project roots. If roots is empty all known projects are returned.
type Stage ¶ added in v0.7.196
type Stage struct {
ID StageID `json:"id"`
State StageState `json:"state"`
// Note says more about the state: why a loop ended, what failed.
Note string `json:"note,omitempty"`
}
Stage is one stage of the flow and how it is going.
type StageID ¶ added in v0.7.196
type StageID string
StageID names one stage of the whole pre-PR flow.
const ( StageReviewLoop StageID = "review_loop" StageRebase StageID = "rebase" StageCI StageID = "ci" StageApproval StageID = "approval" StagePR StageID = "pr" )
The stages, in order. Only the review loop is built; the rest are on the record from the start so the dashboard can show where the flow is going, and so building one is filling in its Stage and not changing the record's shape.
type StageState ¶ added in v0.7.196
type StageState string
StageState is where one stage stands.
const ( // StageNotBuilt marks a stage the daemon has no implementation of yet. It is // shown as "not built yet" and is never run. StageNotBuilt StageState = "not_built" StagePending StageState = "pending" StageRunning StageState = "running" StagePaused StageState = "paused" StageDone StageState = "done" StageFailed StageState = "failed" StageSkipped StageState = "skipped" )
Stage states.
type StreamFunc ¶ added in v0.7.196
type StreamFunc func(ctx context.Context, entry *sidecar.PoolEntry, commandID string, onOutput circleci.OutputFn) (int, error)
StreamFunc reads a submitted command's output to its end and returns its exit code.
type SubmitFunc ¶ added in v0.7.196
type SubmitFunc func(ctx context.Context, entry *sidecar.PoolEntry, script string, env map[string]string) (string, error)
SubmitFunc submits a script on a pool member and returns its command ID.
type TaskState ¶ added in v0.7.186
type TaskState struct {
ID string `json:"id"`
// ProjectRoot is the key the task is filed under — the root of the repo,
// which may sit above the directory the run was actually asked for. See
// taskStore.projectKey.
ProjectRoot string `json:"project_root"`
StartedAt time.Time `json:"started_at"`
FinishedAt time.Time `json:"finished_at,omitempty"`
Running bool `json:"running"`
// ExitCode is the validate run's exit status. Meaningful only once the task
// has finished.
ExitCode int `json:"exit_code"`
// Output is what the run printed, held for whoever collects the result.
// Capped at maxTaskOutput, keeping the tail.
Output string `json:"output,omitempty"`
// Stale reports that the working tree changed between the run starting and
// its result being read, so the result describes code that is no longer on
// disk. For a live-tree run ExitCode and Output are cleared when it is set: a
// stale task is reported so that the discard is visible, and carrying the
// verdict would invite it to be read as one. For a snapshot run they are
// kept — see Snapshot, where the verdict outlives the tree moving.
//
// The tree most often moved because of the run itself — output written,
// goldens regenerated, a lockfile touched. Reporting the discard is what
// lets that be diagnosed instead of looking like no run happened.
//
// It is not a fix for the cause. A mid-run edit by the developer and a file
// written by the run are the same event as far as a content digest is
// concerned, so nothing here can tell them apart, and relaxing the
// comparison to let artifacts through would let a real edit through with
// them — trading a silence for a false pass, which is the one direction this
// store must not fail in. Actually keeping the result means stopping the
// tree from moving under the run, which is a matter of where the run
// happens rather than how its result is judged.
Stale bool `json:"stale"`
// Snapshot reports that the run validated a checked-out copy of the tree
// rather than the tree itself. Such a result is exact about the state it
// ran against whatever happened afterwards, so it is reported even when
// stale — qualified rather than thrown away.
Snapshot bool `json:"snapshot,omitempty"`
// DeliveredAt records when this result was handed to a caller. A stamped
// task is never reported again; an unstamped one is still owed to somebody.
//
// It exists so that handing a result over and forgetting it are two steps
// rather than one. See taskStore.collect.
DeliveredAt time.Time `json:"delivered_at,omitempty"`
}
TaskState is a validation task as reported to a caller.
func CollectValidateResults ¶ added in v0.7.186
CollectValidateResults returns the finished async results for projectRoot and clears them from the daemon, so a result is reported once and not repeated.
Best-effort: with no daemon running there is nothing to collect and nothing to report, which is not an error worth surfacing on a hook path.
func (TaskState) Passed ¶ added in v0.7.186
Passed reports whether a finished task validated the tree successfully.
A stale live-tree task never passes, whatever its exit code says. Its verdict is stripped when it is reported, which leaves ExitCode at zero — so without the Stale check a discarded run would read here as a clean pass, which is exactly the claim discarding it exists to avoid making.
A stale snapshot task can still pass. It ran against a copy that could not move, so its exit code remains exactly true about the state it was handed; what staleness says there is that the state has been overtaken, not that the verdict is unreliable. Callers are expected to report that qualification — see printResults.
type ValidateRequest ¶ added in v0.7.174
type ValidateRequest struct {
// Args is os.Args[1:] from the caller, e.g. ["validate", "test", "--remote"].
Args []string `json:"args"`
// CircleCIToken is forwarded to the subprocess as CIRCLE_TOKEN.
CircleCIToken string `json:"circleci_token,omitempty"`
// Env is the caller's os.Environ(), forwarded verbatim to the subprocess so
// session-identity variables (e.g. CLAUDE_CODE_SESSION_ID) reach it intact.
Env []string `json:"env,omitempty"`
// ProjectRoot is the repo the run applies to, already resolved by the client
// (so it reflects the caller's --project, or its cwd). It decides what gets
// validated, and it is also what a task is filed and fingerprinted under,
// what a change is measured against, and what a verdict is remembered by —
// so anything the daemon decides on a project's behalf needs it.
//
// It cannot be left to the daemon to infer. The daemon's own cwd is wherever
// it was launched from, which is one arbitrary repo out of all the repos it
// serves, so a run that resolved the project itself would validate that one
// and report the answer under whichever project asked. Empty is accepted on
// the synchronous path only, for a client too old to send it; such a run is
// simply run, with nothing judged or recorded.
ProjectRoot string `json:"project_root,omitempty"`
// AllowAsync says the caller will accept being released before the answer
// exists. It is an offer, not an instruction: the daemon weighs the change
// and may hold the caller anyway.
//
// Only a caller that has somewhere to hear the answer later should set it.
// A hook does — the next turn collects background results — while a developer
// watching a terminal does not, and releasing them would leave the run's
// output going nowhere they are looking.
AllowAsync bool `json:"allow_async,omitempty"`
// OrgID is the CircleCI org UUID for this project. When set and the daemon
// has credentials, the daemon provisions a fresh sidecar for the run rather
// than expecting one to already be registered on its filesystem.
OrgID string `json:"org_id,omitempty"`
// HookCodex says the run is a hook invocation from Codex, and the daemon
// passes it on to the run as --hook-codex.
//
// It travels as a field rather than in Args because a remote daemon's build
// cannot be checked, and one that predates the flag would reject the whole
// run with a non-blocking exit — a commit gate would pass having checked
// nothing. A daemon that predates this field ignores it instead, and the run
// goes ahead, just without Codex's quieter output.
HookCodex bool `json:"hook_codex,omitempty"`
// WorkDir is the caller's working directory, which the daemon passes to the
// subprocess as --project so it can load .chunk/config.json from the right
// location. It equals ProjectRoot for callers whose working directory is the
// git top-level; it differs when the .chunk directory sits below the git root.
//
// A daemon that predates this field ignores it and falls back to ProjectRoot,
// which is the caller's cwd and therefore also correct in the common case.
WorkDir string `json:"work_dir,omitempty"`
// SidecarImage is the image the caller resolved for this run from its own
// config, per-command override included. The daemon boots the sidecar it
// provisions from it, since the subprocess is handed that sidecar by ID and
// never picks an image itself. A remote daemon may not have the checkout to
// read the config from, so the client resolving it is what makes the image
// right there.
//
// Empty means the caller configured none, or predates this field; the daemon
// then reads validation.sidecarImage from the project's config itself.
SidecarImage string `json:"sidecar_image,omitempty"`
}
ValidateRequest is the payload sent to POST /validate and POST /validate/async.
type ValidateResponse ¶ added in v0.7.174
type ValidateResponse struct {
ExitCode int `json:"exit_code"`
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
// TaskID is set when the daemon took the run into the background rather than
// running it here. Nothing has run yet: ExitCode is zero because there is no
// exit code, and the result is collected on a later turn.
TaskID string `json:"task_id,omitempty"`
// Risk is what the daemon made of the change: a score, the facts behind it,
// and any advice. Nil when the caller never offered to be released, since
// then no change was judged.
Risk *RiskSummary `json:"risk,omitempty"`
// Reason says why the run was released or held, in words a caller can print
// as one line. Empty when the caller never offered to be released, since
// then there was no decision to explain.
Reason string `json:"reason,omitempty"`
// ClaimWarning is an advisory when another session is actively validating
// overlapping paths. Empty in the common case (no overlap). Never a gate.
ClaimWarning string `json:"claim_warning,omitempty"`
}
ValidateResponse is the response from POST /validate.
func RunValidate ¶ added in v0.7.174
func RunValidate(req ValidateRequest) (ValidateResponse, error)
RunValidate delegates a validate run to the daemon. req.ProjectRoot is the repo to validate, already resolved by the caller.
The caller is responsible for deciding which fields to populate: req.Env and req.CircleCIToken should only be set when using the local Unix socket — over TCP the remote daemon uses its own credentials and environment. See runValidateViaDaemon in cmd/ for the canonical call site.
Set req.AllowAsync to offer the daemon the option of releasing this caller and reporting later; a response carrying a TaskID is that offer taken, and means nothing has run yet.
It takes the request type rather than a list of arguments because what the daemon needs to know about a run keeps growing, and every addition would otherwise be another positional parameter at two call sites.
type ValidateRunner ¶ added in v0.7.174
type ValidateRunner func(ctx context.Context, projectRoot, workDir string, args []string, env []string, stdout, stderr io.Writer) int
ValidateRunner runs a validate command in-process. projectRoot is the repo the daemon uses for task tracking and state keying; workDir is the caller's working directory, passed to the subprocess as --project so it can load .chunk/config.json from the right location (workDir equals projectRoot unless the .chunk directory sits below the git root, in which case workDir is the subdirectory and projectRoot is the git top-level). When workDir is empty the runner falls back to projectRoot. args is os.Args[1:] from the caller; env is the caller's os.Environ(). stdout and stderr capture command output. Returns the exit code.
Source Files
¶
- build.go
- claims.go
- client.go
- conflicts.go
- daemon.go
- history.go
- ipc.go
- launch_unix.go
- liveness.go
- load.go
- notice.go
- output.go
- paths.go
- pid.go
- pid_notwindows.go
- prmonitor.go
- provision.go
- resources.go
- risk.go
- score.go
- session_client.go
- session_fix.go
- session_http.go
- session_loop.go
- session_run.go
- session_store.go
- session_tree.go
- session_types.go
- tasks.go
- types.go
- validate.go