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("stdin ('-') can be read only once, but {count} inputs request it (across --prompt-file and --system-file)")]
24    MultipleStdinConsumers { count: usize },
25
26    #[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>")]
27    BatchMultipleHarnesses { count: usize, selected: String },
28
29    #[error("a batch run (more than one prompt) cannot be combined with --resume/--fork (those continue a single session with a single prompt)")]
30    BatchResume,
31
32    #[error("--resume needs exactly one harness (a session belongs to one harness), but {count} were selected: {selected}")]
33    ResumeMultipleHarnesses { count: usize, selected: String },
34
35    #[error("harness `{id}` does not support --resume. supported: {supported}")]
36    ResumeUnsupported { id: String, supported: String },
37
38    #[error("harness `{id}` does not support --fork (it resumes linearly, appending in place). supported: {supported}")]
39    ForkUnsupported { id: String, supported: String },
40
41    #[error("--session needs exactly one harness (a named session belongs to one harness), but {count} were selected: {selected}")]
42    SessionMultipleHarnesses { count: usize, selected: String },
43
44    #[error("--session cannot be combined with a batch run (more than one prompt): a named session is one continued conversation, not a fan-out")]
45    SessionBatch,
46
47    #[error("harness `{id}` does not support --session: it exposes no session id headlessly, so a named handle cannot be mapped to it. supported: {supported}")]
48    SessionUnsupported { id: String, supported: String },
49
50    #[error("session `{name}` was created on harness `{was}`, so it cannot be continued on `{now}` (a named session is bound to one harness; use a different --session name)")]
51    SessionHarnessConflict {
52        name: String,
53        was: String,
54        now: String,
55    },
56
57    #[error("cannot resolve a session store directory (no --session-dir and no platform state dir): set --session-dir <DIR>")]
58    SessionNoStore,
59
60    #[error("harness `{id}` does not support `--mode {mode}`. supported modes: {supported}")]
61    ModeUnsupported {
62        id: String,
63        mode: String,
64        supported: String,
65    },
66
67    #[error("could not read harness config `{path}`: {source}")]
68    HarnessConfigRead {
69        path: String,
70        #[source]
71        source: std::io::Error,
72    },
73
74    #[error("cannot sync into `{path}`: {message} (oneharness only rewrites files it can parse, so it never destroys content it does not understand)")]
75    HarnessConfigUnmergeable { path: String, message: String },
76
77    #[error("harness `{id}` has no hook mapping, so oneharness cannot install a hook into it")]
78    HookUnsupported { id: String },
79
80    #[error("harness `{id}` has no user-global hook location, so oneharness cannot install a global hook into it")]
81    HookGlobalUnsupported { id: String },
82
83    #[error("cannot resolve the user-global hook directory for `{id}`: {var} is not set")]
84    HookGlobalDirMissing { id: String, var: &'static str },
85
86    #[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)")]
87    GlobalSyncOnlyHooks { id: String },
88
89    #[error(
90        "harness `{id}` has no pre-tool gate, so `oneharness gate {id}` cannot emit a verdict"
91    )]
92    GateUnsupported { id: String },
93
94    #[error("could not read mock rules file `{path}`: {source}")]
95    MockRulesFile {
96        path: String,
97        #[source]
98        source: std::io::Error,
99    },
100
101    #[error("invalid mock rules file `{path}`: {message}")]
102    MockRulesInvalid { path: String, message: String },
103
104    #[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`)")]
105    MockActionUnsupported { id: String, action: &'static str },
106
107    #[error(
108        "harness `{id}` cannot take a one-shot mock hook (`--mock-rules`/`--spy-file`): {reason}"
109    )]
110    MockDeliveryUnsupported { id: String, reason: &'static str },
111
112    #[error("cannot embed `{path}` into a hook command: it contains whitespace (hook commands are tokenized on spaces by some harnesses; use space-free paths)")]
113    MockPathWhitespace { path: String },
114
115    #[error("could not wire the one-shot mock hook: {message}")]
116    MockSetup { message: String },
117
118    #[error("could not write harness config `{path}`: {source}")]
119    HarnessConfigWrite {
120        path: String,
121        #[source]
122        source: std::io::Error,
123    },
124
125    #[error("could not read prompt file `{path}`: {source}")]
126    PromptFile {
127        path: String,
128        #[source]
129        source: std::io::Error,
130    },
131
132    #[error("could not read system prompt file `{path}`: {source}")]
133    SystemFile {
134        path: String,
135        #[source]
136        source: std::io::Error,
137    },
138
139    #[error("could not read schema file `{path}`: {source}")]
140    SchemaFile {
141        path: String,
142        #[source]
143        source: std::io::Error,
144    },
145
146    #[error("invalid --schema: {0}")]
147    Schema(String),
148
149    #[error("invalid --stream: {0}")]
150    StreamInvalid(String),
151
152    #[error("could not read config file `{path}`: {source}")]
153    ConfigRead {
154        path: String,
155        #[source]
156        source: std::io::Error,
157    },
158
159    #[error("invalid config file `{path}`: {message}")]
160    ConfigInvalid { path: String, message: String },
161
162    #[error("invalid environment-variable config override: {0}")]
163    EnvConfigInvalid(String),
164
165    #[error("invalid --bin override `{0}`: expected the form ID=PATH")]
166    BadBinOverride(String),
167
168    #[error("invalid --env `{0}`: expected the form KEY=VALUE")]
169    BadEnv(String),
170
171    #[error("could not write output to `{path}`: {source}")]
172    OutputDir {
173        path: String,
174        #[source]
175        source: std::io::Error,
176    },
177
178    #[error("could not access history under `{path}`: {source}")]
179    HistoryIo {
180        path: String,
181        #[source]
182        source: std::io::Error,
183    },
184
185    #[error("no history directory: pass --history-dir, set `history_dir` in config, or ONEHARNESS_HISTORY_DIR (a default under the platform state dir could not be resolved)")]
186    HistoryNoDir,
187
188    #[error("failed to write JSON output: {0}")]
189    Serialize(#[from] serde_json::Error),
190}