Skip to main content

oneharness_core/
errors.rs

1//! Typed errors surfaced only at the application boundary (see `dispatch`).
2//!
3//! These cover usage and configuration faults — the kind that should abort the
4//! process. A harness's own behavior (missing binary, non-zero exit, hang) is
5//! never an error here: it is recorded as data in the JSON report.
6
7use thiserror::Error;
8
9#[derive(Debug, Error)]
10pub enum OneharnessError {
11    #[error("no harness selected: pass --all or --harness <id>, or set `all`/`harnesses` in oneharness.toml (see `oneharness list`)")]
12    NoSelection,
13
14    #[error("unknown harness id `{id}`. valid ids: {valid}")]
15    UnknownHarness { id: String, valid: String },
16
17    #[error("no prompt provided: pass --prompt <text> or --prompt-file <path>")]
18    NoPrompt,
19
20    #[error("--prompt-file - (stdin) can be given only once, but it was passed {count} times")]
21    MultipleStdinPrompts { count: usize },
22
23    #[error("a batch run (more than one prompt) needs exactly one harness (a shared cache prefix is per harness/model), but {count} were selected: {selected}. Select one with --harness <id>")]
24    BatchMultipleHarnesses { count: usize, selected: String },
25
26    #[error("a batch run (more than one prompt) cannot be combined with --resume/--fork (those continue a single session with a single prompt)")]
27    BatchResume,
28
29    #[error("--resume needs exactly one harness (a session belongs to one harness), but {count} were selected: {selected}")]
30    ResumeMultipleHarnesses { count: usize, selected: String },
31
32    #[error("harness `{id}` does not support --resume. supported: {supported}")]
33    ResumeUnsupported { id: String, supported: String },
34
35    #[error("harness `{id}` does not support --fork (it resumes linearly, appending in place). supported: {supported}")]
36    ForkUnsupported { id: String, supported: String },
37
38    #[error("harness `{id}` does not support `--mode {mode}`. supported modes: {supported}")]
39    ModeUnsupported {
40        id: String,
41        mode: String,
42        supported: String,
43    },
44
45    #[error("could not read harness config `{path}`: {source}")]
46    HarnessConfigRead {
47        path: String,
48        #[source]
49        source: std::io::Error,
50    },
51
52    #[error("cannot sync into `{path}`: {message} (oneharness only rewrites files it can parse, so it never destroys content it does not understand)")]
53    HarnessConfigUnmergeable { path: String, message: String },
54
55    #[error("harness `{id}` has no hook mapping, so oneharness cannot install a hook into it")]
56    HookUnsupported { id: String },
57
58    #[error("harness `{id}` has no user-global hook location, so oneharness cannot install a global hook into it")]
59    HookGlobalUnsupported { id: String },
60
61    #[error("cannot resolve the user-global hook directory for `{id}`: {var} is not set")]
62    HookGlobalDirMissing { id: String, var: &'static str },
63
64    #[error("`oneharness sync --global` installs hooks only, but permission rules or `settings` are configured for `{id}` — those are project-scoped (sync them without --global, or remove them)")]
65    GlobalSyncOnlyHooks { id: String },
66
67    #[error(
68        "harness `{id}` has no pre-tool gate, so `oneharness gate {id}` cannot emit a verdict"
69    )]
70    GateUnsupported { id: String },
71
72    #[error("could not read mock rules file `{path}`: {source}")]
73    MockRulesFile {
74        path: String,
75        #[source]
76        source: std::io::Error,
77    },
78
79    #[error("invalid mock rules file `{path}`: {message}")]
80    MockRulesInvalid { path: String, message: String },
81
82    #[error("harness `{id}` cannot express the mock action `{action}`, so `oneharness mock {id}` refuses this ruleset (see `mock_rewrite`/`supports_mock_deny` in `oneharness list`)")]
83    MockActionUnsupported { id: String, action: &'static str },
84
85    #[error(
86        "harness `{id}` cannot take a one-shot mock hook (`--mock-rules`/`--spy-file`): {reason}"
87    )]
88    MockDeliveryUnsupported { id: String, reason: &'static str },
89
90    #[error("cannot embed `{path}` into a hook command: it contains whitespace (hook commands are tokenized on spaces by some harnesses; use space-free paths)")]
91    MockPathWhitespace { path: String },
92
93    #[error("could not wire the one-shot mock hook: {message}")]
94    MockSetup { message: String },
95
96    #[error("could not write harness config `{path}`: {source}")]
97    HarnessConfigWrite {
98        path: String,
99        #[source]
100        source: std::io::Error,
101    },
102
103    #[error("could not read prompt file `{path}`: {source}")]
104    PromptFile {
105        path: String,
106        #[source]
107        source: std::io::Error,
108    },
109
110    #[error("could not read schema file `{path}`: {source}")]
111    SchemaFile {
112        path: String,
113        #[source]
114        source: std::io::Error,
115    },
116
117    #[error("invalid --schema: {0}")]
118    Schema(String),
119
120    #[error("invalid --stream: {0}")]
121    StreamInvalid(String),
122
123    #[error("could not read config file `{path}`: {source}")]
124    ConfigRead {
125        path: String,
126        #[source]
127        source: std::io::Error,
128    },
129
130    #[error("invalid config file `{path}`: {message}")]
131    ConfigInvalid { path: String, message: String },
132
133    #[error("invalid environment-variable config override: {0}")]
134    EnvConfigInvalid(String),
135
136    #[error("invalid --bin override `{0}`: expected the form ID=PATH")]
137    BadBinOverride(String),
138
139    #[error("invalid --env `{0}`: expected the form KEY=VALUE")]
140    BadEnv(String),
141
142    #[error("could not write output to `{path}`: {source}")]
143    OutputDir {
144        path: String,
145        #[source]
146        source: std::io::Error,
147    },
148
149    #[error("failed to write JSON output: {0}")]
150    Serialize(#[from] serde_json::Error),
151}