88
99Instead of raising a bare "Status Code: 504" error, this module builds a structured
1010diagnostic context block that includes the error code, deployment stage, runtime info,
11- common causes, suggested fixes, and a ready-to-use Copilot prompt.
11+ suggested fixes, and a ready-to-use Copilot prompt.
1212"""
1313
14- import yaml
14+ import re
15+
1516from knack .log import get_logger
1617from knack .util import CLIError
1718
1819from ._deployment_failure_patterns import match_failure_pattern
1920
2021logger = get_logger (__name__ )
2122
23+ # Patterns that reliably indicate an HTTP status code in CLI error messages.
24+ # Ordered from most specific to least specific; first match wins.
25+ _STATUS_CODE_PATTERNS = [
26+ re .compile (r'Status\s*Code[:\s]+(\d{3})' , re .IGNORECASE ), # "Status Code: 400"
27+ re .compile (r'\((([45]\d{2}))\)' ), # "Bad Request(400)"
28+ re .compile (r'HTTP\s+(\d{3})' , re .IGNORECASE ), # "HTTP 504"
29+ re .compile (r'\b([45]\d{2})\s+(?:Bad|Unauthorized|Forbidden|Not\s+Found|Conflict|Too\s+Many|Internal|Gateway|Service)' , re .IGNORECASE ), # "400 Bad Request"
30+ ]
2231
23- def _safe_yaml_dump (data ):
24- """Dump dict to YAML string, falling back to repr on error."""
25- try :
26- return yaml .dump (data , default_flow_style = False , sort_keys = False , allow_unicode = True ).rstrip ()
27- except Exception : # pylint: disable=broad-except
28- return repr (data )
32+
33+ def extract_status_code_from_message (message ):
34+ """Extract an HTTP status code (4xx/5xx) from a CLI error message string.
35+
36+ Uses targeted patterns ("Status Code: 400", "Bad Request(400)", "HTTP 504",
37+ "400 Bad Request") rather than blindly matching any 3-digit number, to avoid
38+ false positives from port numbers, exit codes, or counts.
39+
40+ Returns the integer status code, or None if no recognisable code is found.
41+ """
42+ if not message :
43+ return None
44+ for pattern in _STATUS_CODE_PATTERNS :
45+ m = pattern .search (message )
46+ if m :
47+ code = int (m .group (1 ))
48+ if 400 <= code <= 599 :
49+ return code
50+ return None
2951
3052
3153def _get_app_runtime (cmd , resource_group_name , webapp_name , slot = None ):
@@ -180,12 +202,10 @@ def build_enriched_error_context(params=None, *, cmd=None, resource_group_name=N
180202 params , src_url = _src_url , artifact_type = _artifact
181203 )
182204
183- # Causes and fixes
205+ # Suggested fixes
184206 if pattern :
185- context ["commonCauses" ] = pattern ["commonCauses" ]
186207 context ["suggestedFixes" ] = pattern ["suggestedFixes" ]
187208 else :
188- context ["commonCauses" ] = ["Unrecognised failure — see error details below" ]
189209 context ["suggestedFixes" ] = [
190210 "Check deployment logs: 'az webapp log deployment show -n {} -g {}'" .format (
191211 _name or '<app>' , _rg or '<rg>' ),
@@ -227,22 +247,15 @@ def format_enriched_error_message(context):
227247 """
228248 Format the structured context dict into a human-readable error message.
229249
230- The output includes the YAML context block and a ready-to-use Copilot prompt.
250+ The output is a single formatted block with diagnostics and a Copilot prompt.
231251 """
232252 lines = []
233253 lines .append ("" )
234254 lines .append ("=" * 72 )
235- lines .append ("DEPLOYMENT FAILED — Context-Enriched Diagnostics" )
255+ lines .append ("DEPLOYMENT FAILED: Context-Enriched Diagnostics" )
236256 lines .append ("=" * 72 )
237257 lines .append ("" )
238258
239- # YAML context block
240- lines .append ("--- COPILOT CONTEXT ---" )
241- lines .append (_safe_yaml_dump (context ))
242- lines .append ("--- END CONTEXT ---" )
243- lines .append ("" )
244-
245- # Human-readable summary
246259 lines .append (f"Error Code : { context .get ('errorCode' , 'Unknown' )} " )
247260 lines .append (f"Stage : { context .get ('stage' , 'Unknown' )} " )
248261 lines .append (f"Runtime : { context .get ('runtime' , 'Unknown' )} " )
@@ -251,11 +264,8 @@ def format_enriched_error_message(context):
251264 lines .append (f"Plan SKU : { context .get ('planSku' , 'Unknown' )} " )
252265 lines .append ("" )
253266
254- causes = context .get ("commonCauses" , [])
255- if causes :
256- lines .append ("Common Causes:" )
257- for c in causes :
258- lines .append (f" - { c } " )
267+ if context .get ("rawError" ):
268+ lines .append (f"Raw Error : { context ['rawError' ]} " )
259269 lines .append ("" )
260270
261271 fixes = context .get ("suggestedFixes" , [])
@@ -265,17 +275,12 @@ def format_enriched_error_message(context):
265275 lines .append (f" - { f } " )
266276 lines .append ("" )
267277
268- if context .get ("rawError" ):
269- lines .append (f"Raw Error : { context ['rawError' ]} " )
270- lines .append ("" )
271-
272278 # Copilot prompt
273279 lines .append ("-" * 72 )
274280 lines .append ("Ask Copilot:" )
275- lines .append (' Copy-paste the COPILOT CONTEXT block above into GitHub Copilot Chat,' )
276- lines .append (' or run:' )
277- lines .append (' gh copilot explain "Paste the COPILOT CONTEXT above and explain' )
278- lines .append (' why this deployment failed and what I should do"' )
281+ lines .append (" Paste the error above into GitHub Copilot Chat, or run:" )
282+ lines .append (' gh copilot explain "why did my deployment fail with' )
283+ lines .append (f' { context .get ("errorCode" , "this error" )} and what should I do"' )
279284 lines .append ("-" * 72 )
280285
281286 return "\n " .join (lines )
0 commit comments