167167EXTRACT_CACHE_MODE = EXTRACT_CACHE_MODE_RUN_LOCAL
168168
169169_PRINT_LOCK = threading .Lock ()
170+ _LIBCLANG_PREFLIGHT_CODE = """
171+ import clang.cindex as cindex
172+
173+ cindex.Index.create()
174+ print(cindex.__file__)
175+ """
170176
171177
172178class CodeRevisionError (RuntimeError ):
@@ -181,6 +187,39 @@ class CodeRevisionDriftError(CodeRevisionError):
181187 """The checkout changed after the conveyor captured its revision."""
182188
183189
190+ def verify_libclang_preflight (python : Path = VENV_PYTHON ) -> str :
191+ """Prove the stage interpreter can load and initialize libclang."""
192+
193+ try :
194+ completed = subprocess .run (
195+ [str (python ), "-c" , _LIBCLANG_PREFLIGHT_CODE ],
196+ check = False ,
197+ capture_output = True ,
198+ text = True ,
199+ timeout = 30 ,
200+ )
201+ except subprocess .TimeoutExpired as exc :
202+ raise SystemExit (
203+ f"libclang preflight timed out after 30s: python={ python } "
204+ ) from exc
205+ except OSError as exc :
206+ raise SystemExit (
207+ f"libclang preflight could not start: python={ python } : { exc } "
208+ ) from exc
209+ if completed .returncode != 0 :
210+ detail = (completed .stderr or completed .stdout ).strip ()
211+ raise SystemExit (
212+ "libclang preflight failed: "
213+ f"python={ python } exit={ completed .returncode } : { detail } "
214+ )
215+ binding_path = completed .stdout .strip ()
216+ if not binding_path :
217+ raise SystemExit (
218+ f"libclang preflight returned no binding path: python={ python } "
219+ )
220+ return binding_path
221+
222+
184223def _sha256_bytes (payload : bytes ) -> str :
185224 return hashlib .sha256 (payload ).hexdigest ()
186225
@@ -2511,6 +2550,67 @@ def claim_code_project_identity(
25112550 return previous
25122551
25132552
2553+ def claim_code_repo_for_submission (
2554+ * ,
2555+ repo : str ,
2556+ repo_list : Path ,
2557+ project_identity_claims : dict [str , str ],
2558+ manifest : Manifest ,
2559+ manifest_lock : threading .Lock ,
2560+ progress : ProgressWriter ,
2561+ ) -> bool :
2562+ """Claim a code repo without letting one unresolved identity stop the run.
2563+
2564+ Missing canonical identity is a fail-closed, repo-local data rejection: no
2565+ indexing subprocess may run, but unrelated repositories must continue.
2566+ Corrupt repo-list files and other unexpected failures still propagate.
2567+ """
2568+
2569+ try :
2570+ project_id = sr .resolve_project_identity (repo , repo_list )
2571+ except SymbolIdentityError as exc :
2572+ detail = str (exc )
2573+ unit = code_key (repo )
2574+ with manifest_lock :
2575+ manifest .mark_failed (unit , "project_identity" , detail )
2576+ progress .emit (
2577+ "unit_failed" ,
2578+ stream = "code" ,
2579+ repo = repo ,
2580+ unit = unit ,
2581+ stage = "project_identity" ,
2582+ detail = detail [:2000 ],
2583+ )
2584+ _log (f"QUARANTINE { unit } : unresolved project identity: { detail } " )
2585+ return False
2586+
2587+ previous = claim_code_project_identity (
2588+ project_identity_claims ,
2589+ repo ,
2590+ project_id ,
2591+ )
2592+ if previous is None :
2593+ return True
2594+
2595+ info = {
2596+ "source" : "code" ,
2597+ "skipped" : True ,
2598+ "skip_reason" : "duplicate_project_identity" ,
2599+ "project_identity" : project_id ,
2600+ "canonical_repo" : previous ,
2601+ "recorded_at" : time .strftime ("%Y-%m-%dT%H:%M:%S" ),
2602+ }
2603+ with manifest_lock :
2604+ manifest .mark_done (code_key (repo ), info )
2605+ progress .emit (
2606+ "duplicate_project_identity_skipped" ,
2607+ repo = repo ,
2608+ stream = "code" ,
2609+ ** info ,
2610+ )
2611+ return False
2612+
2613+
25142614def no_git_key (repo : str ) -> str :
25152615 """Checkpoint key proving the .git-preserving stream saw no git metadata."""
25162616 return f"{ repo } ::no_git"
@@ -4336,6 +4436,12 @@ def _on_signal(signum, _frame):
43364436 for path in required_paths :
43374437 if not Path (path ).exists ():
43384438 raise SystemExit (f"required path missing: { path } " )
4439+ if not args .source_cache_populate_only :
4440+ libclang_binding = verify_libclang_preflight (VENV_PYTHON )
4441+ _log (
4442+ "libclang preflight: OK "
4443+ f"python={ VENV_PYTHON } binding={ libclang_binding } "
4444+ )
43394445
43404446 # Pre-create output trees for BOTH streams.
43414447 CONVEYOR_ROOT .mkdir (parents = True , exist_ok = True )
@@ -4599,34 +4705,14 @@ def claim_repo_once(repo: str) -> bool:
45994705 submitted_repo_names .add (repo )
46004706 if args .streams != "code" or repo_list is None :
46014707 return True
4602- try :
4603- project_id = sr .resolve_project_identity (repo , repo_list )
4604- except Exception as exc :
4605- raise RepoFailure (repo , "project_identity" , str (exc )) from exc
4606- previous = claim_code_project_identity (
4607- submitted_code_project_identities ,
4608- repo ,
4609- project_id ,
4610- )
4611- if previous is None :
4612- return True
4613- info = {
4614- "source" : "code" ,
4615- "skipped" : True ,
4616- "skip_reason" : "duplicate_project_identity" ,
4617- "project_identity" : project_id ,
4618- "canonical_repo" : previous ,
4619- "recorded_at" : time .strftime ("%Y-%m-%dT%H:%M:%S" ),
4620- }
4621- with manifest_lock :
4622- manifest .mark_done (code_key (repo ), info )
4623- progress .emit (
4624- "duplicate_project_identity_skipped" ,
4708+ return claim_code_repo_for_submission (
46254709 repo = repo ,
4626- stream = "code" ,
4627- ** info ,
4710+ repo_list = repo_list ,
4711+ project_identity_claims = submitted_code_project_identities ,
4712+ manifest = manifest ,
4713+ manifest_lock = manifest_lock ,
4714+ progress = progress ,
46284715 )
4629- return False
46304716
46314717 def mark_no_git_repo (repo : str ) -> None :
46324718 info = {
0 commit comments