@@ -32,7 +32,7 @@ import fetch from "../../lib/fetch";
3232import { OnlyHookErrors } from "../../types" ;
3333import { API_ENDPOINTS } from "../../constants/API_ENDPOINTS" ;
3434import { SLICE_MACHINE_USER_AGENT } from "../../constants/SLICE_MACHINE_USER_AGENT" ;
35- import { UnauthorizedError } from "../../errors" ;
35+ import { InferSliceAbortError , UnauthorizedError } from "../../errors" ;
3636
3737import { BaseManager } from "../BaseManager" ;
3838import { CustomTypeFormat } from "./types" ;
@@ -42,13 +42,16 @@ import { randomUUID } from "node:crypto";
4242import { join as joinPath , relative as relativePath } from "node:path" ;
4343import {
4444 mkdtemp ,
45- rename ,
4645 rm ,
4746 writeFile ,
4847 readFile ,
4948 readdir ,
49+ copyFile ,
5050} from "node:fs/promises" ;
51- import { query as queryClaude } from "@anthropic-ai/claude-agent-sdk" ;
51+ import {
52+ AbortError as ClaudeAbortError ,
53+ query as queryClaude ,
54+ } from "@anthropic-ai/claude-agent-sdk" ;
5255
5356type SliceMachineManagerReadCustomTypeLibraryReturnType = {
5457 ids : string [ ] ;
@@ -580,6 +583,8 @@ export class CustomTypesManager extends BaseManager {
580583 console . info ( `inferSlice (${ source } ) started for request ${ requestId } ` ) ;
581584 const startTime = Date . now ( ) ;
582585
586+ const claudeErrors : string [ ] = [ ] ;
587+
583588 try {
584589 if ( source === "figma" ) {
585590 const { libraryID } = args ;
@@ -595,6 +600,7 @@ export class CustomTypesManager extends BaseManager {
595600 . parse ( exp . payload ) ;
596601
597602 let tmpDir : string | undefined ;
603+
598604 try {
599605 const config = await this . project . getSliceMachineConfig ( ) ;
600606
@@ -814,13 +820,23 @@ FINAL REMINDERS:
814820- DO NOT ATTEMPT TO BUILD THE APPLICATION
815821- START IMMEDIATELY WITH STEP 1.1 - NO PRELIMINARY ANALYSIS` ;
816822
823+ void this . telemetry . track ( {
824+ event : "slice-generation:started" ,
825+ source,
826+ llmProxyUrl,
827+ } ) ;
828+
817829 const queries = queryClaude ( {
818830 prompt,
819831 options : {
820832 cwd,
821- stderr : ( data ) => {
822- if ( ! data . startsWith ( "Spawning Claude Code process" ) ) {
823- console . error ( "inferSlice error:" + data ) ;
833+ stderr : ( error ) => {
834+ if ( ! error . startsWith ( "Spawning Claude Code process" ) ) {
835+ claudeErrors . push ( error ) ;
836+ console . error (
837+ `inferSlice - stderr for request ${ requestId } :` ,
838+ error ,
839+ ) ;
824840 }
825841 } ,
826842 model : "claude-haiku-4-5" ,
@@ -850,7 +866,7 @@ FINAL REMINDERS:
850866 ] ) ,
851867 ] ,
852868 env : {
853- ...process . env ,
869+ ...this . sanitizeClaudeEnv ( process . env ) ,
854870 ANTHROPIC_BASE_URL : llmProxyUrl ,
855871 ANTHROPIC_CUSTOM_HEADERS :
856872 `x-prismic-token: ${ authToken } \n` +
@@ -873,10 +889,25 @@ FINAL REMINDERS:
873889 for await ( const query of queries ) {
874890 switch ( query . type ) {
875891 case "result" :
876- if ( query . subtype === "success" ) {
877- newSliceAbsPath = query . result . match (
878- / < n e w _ s l i c e _ p a t h > ( .* ) < \/ n e w _ s l i c e _ p a t h > / s,
879- ) ?. [ 1 ] ;
892+ switch ( query . subtype ) {
893+ case "success" :
894+ if ( ! query . is_error ) {
895+ newSliceAbsPath = query . result . match (
896+ / < n e w _ s l i c e _ p a t h > ( .* ) < \/ n e w _ s l i c e _ p a t h > / s,
897+ ) ?. [ 1 ] ;
898+ } else {
899+ claudeErrors . push ( query . result ) ;
900+ }
901+ break ;
902+ case "error_during_execution" :
903+ case "error_max_budget_usd" :
904+ case "error_max_turns" :
905+ claudeErrors . push ( ...query . errors ) ;
906+ console . error (
907+ `inferSlice - result query error for request ${ requestId } }:` ,
908+ query . errors ,
909+ ) ;
910+ break ;
880911 }
881912 break ;
882913 }
@@ -897,12 +928,21 @@ FINAL REMINDERS:
897928 ) ;
898929 }
899930
900- // move the screenshot image to the new slice directory
901- await rename (
931+ // copy instead of moving because the file might be in a different volume
932+ await copyFile (
902933 tmpImageAbsPath ,
903934 joinPath ( newSliceAbsPath , "screenshot-default.png" ) ,
904935 ) ;
905936
937+ try {
938+ await rm ( tmpImageAbsPath ) ;
939+ } catch ( error ) {
940+ console . warn (
941+ `inferSlice - Failed to delete temporary slice screenshot at ${ tmpImageAbsPath } ` ,
942+ error ,
943+ ) ;
944+ }
945+
906946 return InferSliceResponse . parse ( { slice : JSON . parse ( model ) } ) ;
907947 } finally {
908948 if ( tmpDir && existsSync ( tmpDir ) ) {
@@ -930,12 +970,25 @@ FINAL REMINDERS:
930970 return InferSliceResponse . parse ( json ) ;
931971 }
932972 } catch ( error ) {
973+ if (
974+ error instanceof ClaudeAbortError ||
975+ ( error instanceof Error && error . name === "AbortError" )
976+ ) {
977+ console . warn ( `inferSlice (${ source } ) request ${ requestId } was aborted` ) ;
978+ throw new InferSliceAbortError ( ) ;
979+ }
980+
933981 console . error (
934982 `inferSlice (${ source } ) failed for request ${ requestId } ` ,
935983 error ,
936984 ) ;
937-
938- throw error ;
985+ throw new Error ( `inferSlice encountered errors` , {
986+ cause : {
987+ error,
988+ ...( claudeErrors . length > 0 ? { claudeErrors } : { } ) ,
989+ args,
990+ } ,
991+ } ) ;
939992 } finally {
940993 this . inferSliceAbortControllers . delete ( requestId ) ;
941994 clearTimeout ( timeoutId ) ;
@@ -947,6 +1000,35 @@ FINAL REMINDERS:
9471000 }
9481001 }
9491002
1003+ // https://code.claude.com/docs/en/settings#environment-variables
1004+ private claudeExcludePatterns = [
1005+ "ANTHROPIC_" ,
1006+ "CLAUDE_" ,
1007+ "MCP_" ,
1008+ "VERTEX_" ,
1009+ "DISABLE_" ,
1010+ "BASH_" ,
1011+ "AWS_" ,
1012+ "SLASH_COMMAND_" ,
1013+ "NO_PROXY" ,
1014+ "HTTP_PROXY" ,
1015+ "HTTPS_PROXY" ,
1016+ "HTTP_MAX_REDIRECTS" ,
1017+ "MAX_THINKING_TOKENS" ,
1018+ "USE_BUILTIN_RIPGREP" ,
1019+ ] ;
1020+
1021+ private sanitizeClaudeEnv ( env : Record < string , string | undefined > ) {
1022+ return Object . fromEntries (
1023+ Object . entries ( env ) . filter ( ( [ key , value ] ) => {
1024+ return (
1025+ value !== undefined &&
1026+ ! this . claudeExcludePatterns . some ( ( pattern ) => key . startsWith ( pattern ) )
1027+ ) ;
1028+ } ) ,
1029+ ) ;
1030+ }
1031+
9501032 cancelInferSlice ( args : { requestId : string } ) : { cancelled : boolean } {
9511033 const { requestId } = args ;
9521034 const abortController = this . inferSliceAbortControllers . get ( requestId ) ;
0 commit comments