Skip to content

Commit c5f0b3d

Browse files
committed
feat: Improve the copy and formatting for apply validation error. Fix pretty print error for NOOP. Added to CLAUDE.md
1 parent f6563de commit c5f0b3d

3 files changed

Lines changed: 27 additions & 3 deletions

File tree

CLAUDE.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,27 @@ Parent Process Plugin Process
184184
3. **Plugin IPC**: Plugins cannot directly read stdin (security isolation)
185185
4. **Sudo Caching**: Password cached in memory during session unless `--secure` flag used
186186
5. **File Watcher**: Use `persistent: false` option to prevent hanging processes
187-
6. **Linting**: ESLint enforces single quotes, specific import ordering, and strict type safety
187+
6. **Linting**: ESLint enforces single quotes, specific import ordering, and strict type safety
188+
7. **Reporter display methods are async**: All `Reporter` interface display methods (`displayPlan`, `displayImportResult`, `displayFileModifications`, `displayMessage`, `displayPluginError`) return `Promise<void>`. Always `await` them at call sites — `DefaultReporter.updateRenderState()` has a 50ms sleep, so unawaited calls cause `process.exit(1)` to fire before the UI renders.
189+
8. **Mock reporter async assertions**: Assertions inside `MockReporter` config callbacks (e.g. `displayFileModifications`) will silently pass if the call isn't awaited. Making display methods async surfaced latent bugs where expected file paths were wrong.
190+
191+
## Plugin Error Handling Architecture
192+
193+
Plugin errors flow as structured `PluginErrorData` over IPC and are caught as `PluginError` instances on the CLI side:
194+
195+
**IPC envelope** (`@codifycli/schemas`):
196+
```typescript
197+
interface PluginErrorData {
198+
errorType: string; // 'apply_validation' | 'sudo_error' | 'unknown'
199+
message: string;
200+
data?: unknown;
201+
}
202+
```
203+
204+
**CLI carrier** (`src/common/errors.ts`): `PluginError extends CodifyError` holds `pluginName`, `resourceType`, and `errorData: PluginErrorData`.
205+
206+
**Reporter as view model**: Reporters (not components) decide how to render each `errorType`. `DefaultReporter.displayPluginError()` branches on `errorType` to set the appropriate `RenderStatus` (`APPLY_VALIDATION_ERROR` with a `ResourcePlan` for plan diffs, `PLUGIN_ERROR` with a message string for generic errors). The `DefaultComponent` is purely display.
207+
208+
**Shared formatter**: `src/ui/plugin-error-formatter.ts` exports `formatApplyValidationError(error: PluginError): string` used by both `PlainReporter` and `DefaultComponent`.
209+
210+
**Backward compat**: `plugin.ts#toErrorData()` validates IPC data against `ErrorResponseDataSchema` (AJV); falls back to `{ errorType: 'unknown', message: data }` for old plugins sending bare strings.

src/ui/components/default-component.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,13 @@ export function DefaultComponent(props: {
5656
(resourcePlan, idx) => (
5757
<Box key={idx} flexDirection="column" marginTop={1}>
5858
<Text color="red" bold>
59-
{`Apply validation failed: resource "${resourcePlan.id}" did not reach its desired state. \nExiting...`}
59+
{`Apply failed: resource "${resourcePlan.id}" did not reach its desired state. \nExiting...`}
6060
</Text>
6161
<Text> </Text>
6262
<Text bold backgroundColor={'red'}>Changes still needed:</Text>
6363
<Text>{prettyFormatResourcePlan(resourcePlan)}</Text>
6464
<Text> </Text>
65-
<Text>Try re-running to see if the changes have applied.</Text>
65+
<Text color="red" bold>Try re-running the command to see if the changes have applied.</Text>
6666
</Box>
6767
)
6868
}</Static>

src/ui/plan-pretty-printer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ function formatArray(parameter: PlanResponseData['parameters'][0]): string {
234234
if (operation === ParameterOperation.NOOP) {
235235
return JSON.stringify(mappedB, null, 4)
236236
.split(/\n/g)
237+
.map((l, idx) => idx === 0 ? `"${name}": ${l}` : l)
237238
.map((l) => ` ${l}`)
238239
.join('\n') + ','
239240
}

0 commit comments

Comments
 (0)