{"record":{"id":"d0c6436f6f1ed198","repo":"facebook/relay","slug":"expected-at-least-one-of-an-updatable-reader-ast","errorCode":null,"errorMessage":"Expected at least one of an @updatable reader AST, or normalization AST to be present","messagePattern":"Expected at least one of an @updatable reader AST, or normalization AST to be present","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs","lineNumber":177,"sourceCode":"                // Therefore this must be an updatable query in order to continue.\n                if reader\n                    .directives\n                    .named(*UPDATABLE_DIRECTIVE)\n                    .is_some()\n                {\n                    let source_hash = source_hashes\n                        .get(&reader.name.item.into())\n                        .cloned()\n                        .unwrap();\n                    return generate_updatable_query_artifact(\n                        ArtifactSourceKey::ExecutableDefinition(reader.name.item.into()),\n                        project_config,\n                        &operations,\n                        source_hash,\n                    )\n                }\n            }\n            panic!(\"Expected at least one of an @updatable reader AST, or normalization AST to be present\");\n        })\n        .chain(programs.reader.fragments().map(|reader_fragment| {\n            let source_name = if let Some(client_edges_directive) =\n                ClientEdgeGeneratedQueryMetadataDirective::find(&reader_fragment.directives)\n            {\n                client_edges_directive.source_name.item\n            } else {\n                reader_fragment.name.item.into()\n            };\n            // If the fragment is generated for the RelayResolver model (id, or model instance)\n            // we need to update the source definition to include the original text of the resolver.\n            let source_hash = source_hashes.get(&source_name).cloned();\n\n            // We need this `if/else` here because of the way the compiler is handling the aritfacts\n            // deletion (see commit_project in compiler.rs).\n            // To remove the artifact, the artifact map should not contain any document/source that may\n            // generate the artifact. If we merge these sources (fragment name and resolver hash)\n            // then the removal of the source hash won't trigger the removal of the artifact, because","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/facebook/relay/blob/668b1b85e06261aa3b58dabfc51f8b5524a70955/compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs#L159-L195","documentation":"generate_artifacts panics when an operation grouping has neither an @updatable reader AST nor a normalization AST. Every grouped operation must produce at least one of these artifacts; an empty group means an earlier compiler stage left inconsistent data.","triggerScenarios":"During artifact generation, a grouped operation (in the match arm before this panic) matches no known artifact kind, so neither reader nor normalization AST exists for it.","commonSituations":"Compiler bug or partially-compiled operation (e.g. an @updatable query missing normalization output) after a Relay upgrade; inconsistent compiler state from an interrupted run.","solutions":["Delete generated artifacts/compiler cache and rerun the compiler","Reduce the offending operation to a minimal repro and check which directives (@updatable etc.) it uses","Pin/report against the Relay version — this panic usually indicates a compiler bug"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Wrap compiler invocation in a process-level guard and retry a clean build\n// Node wrapper example:\nconst { spawnSync } = require('child_process');\nconst r = spawnSync('relay-compiler', ['--watchman', 'false'], { stdio: 'inherit' });\nif (r.status !== 0) {\n  require('fs').rmSync('__generated__', { recursive: true, force: true });\n  spawnSync('relay-compiler', [], { stdio: 'inherit' });\n}","typeGuard":null,"tryCatchPattern":"try {\n  runRelayCompiler();\n} catch (e) {\n  if (/reader AST, or normalization AST/.test(String(e))) {\n    cleanCachesAndRetry(); // report upstream if reproducible\n  } else throw e;\n}","preventionTips":["Clean __generated__ and compiler caches after Relay upgrades","Keep @updatable usage aligned with current docs","Minimize and report reproducible compiler panics upstream"],"tags":["compiler","rust","panic","codegen","artifacts"],"backgroundTag":"compiler-internal-invariant-panic","analyzedSha":"668b1b85e06261aa3b58dabfc51f8b5524a70955","analyzedAt":"2026-09-02T19:57:20.783Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}