@@ -568,13 +568,23 @@ async function acquireFlowEditingLock(
568568 flowId +
569569 '"}) { createResult { canEdit id editingUserDisplayName __typename } __typename } __typename } __typename } }'
570570 var resp = await client . post ( "/api/now/graphql" , { variables : { } , query : mutation } )
571+ var gqlErrors = resp . data ?. errors
572+ if ( gqlErrors && gqlErrors . length > 0 ) {
573+ debug . graphql_errors = gqlErrors . map ( function ( e : any ) {
574+ return e . message || JSON . stringify ( e )
575+ } )
576+ }
571577 var result = resp . data ?. data ?. global ?. snFlowDesigner ?. safeEdit ?. createResult
572- debug . graphql_response = result
573- if ( result ?. canEdit !== true && result ?. canEdit !== "true" ) {
574- var editingUser = result ?. editingUserDisplayName || "another user"
578+ debug . graphql_response = result || null
579+ debug . graphql_raw_keys = resp . data ?. data ? Object . keys ( resp . data . data ) : null
580+ if ( result ?. canEdit === true || result ?. canEdit === "true" ) {
581+ debug . graphql_canEdit = true
582+ } else if ( result ) {
583+ var editingUser = result . editingUserDisplayName || "another user"
575584 return { success : false , error : "Flow is locked by " + editingUser , debug }
585+ } else {
586+ debug . graphql_result_null = true
576587 }
577- debug . graphql_canEdit = true
578588 } catch ( e : any ) {
579589 debug . graphql_error = e . message
580590 }
@@ -4869,14 +4879,24 @@ async function buildSubflowInputsForInsert(
48694879 try {
48704880 var inpResp = await client . get ( "/api/now/table/sys_hub_flow_variable" , {
48714881 params : {
4872- sysparm_query : "flow=" + subflowSysId + "^variable_type=input" ,
4882+ sysparm_query : "flow=" + subflowSysId + "^variable_type=input^ORDERBYorder " ,
48734883 sysparm_fields :
48744884 "sys_id,name,label,internal_type,mandatory,default_value,order,max_length,hint,read_only,extended,data_structure,reference,reference_display,ref_qual,choice_option,table_name,column_name,use_dependent,dependent_on,show_ref_finder,local,attributes,sys_class_name" ,
48754885 sysparm_display_value : "false" ,
48764886 sysparm_limit : 50 ,
48774887 } ,
48784888 } )
4879- inputParams = inpResp . data . result || [ ]
4889+ var rawInputs = inpResp . data . result || [ ]
4890+ var seenIds : Record < string , boolean > = { }
4891+ var seenNames : Record < string , boolean > = { }
4892+ for ( var ri = 0 ; ri < rawInputs . length ; ri ++ ) {
4893+ var rid = str ( rawInputs [ ri ] . sys_id )
4894+ var rname = str ( rawInputs [ ri ] . name )
4895+ if ( seenIds [ rid ] || seenNames [ rname ] ) continue
4896+ seenIds [ rid ] = true
4897+ seenNames [ rname ] = true
4898+ inputParams . push ( rawInputs [ ri ] )
4899+ }
48804900 } catch ( e : any ) {
48814901 console . warn (
48824902 "[snow_manage_flow] sys_hub_flow_variable input query failed for flow=" + subflowSysId + ": " + ( e . message || "" ) ,
@@ -4885,13 +4905,23 @@ async function buildSubflowInputsForInsert(
48854905 try {
48864906 var outResp = await client . get ( "/api/now/table/sys_hub_flow_variable" , {
48874907 params : {
4888- sysparm_query : "flow=" + subflowSysId + "^variable_type=output" ,
4908+ sysparm_query : "flow=" + subflowSysId + "^variable_type=output^ORDERBYorder " ,
48894909 sysparm_fields : "sys_id,name,label,internal_type,mandatory,order,reference,attributes" ,
48904910 sysparm_display_value : "false" ,
48914911 sysparm_limit : 50 ,
48924912 } ,
48934913 } )
4894- outputParams = outResp . data . result || [ ]
4914+ var rawOutputs = outResp . data . result || [ ]
4915+ var seenOutIds : Record < string , boolean > = { }
4916+ var seenOutNames : Record < string , boolean > = { }
4917+ for ( var ro = 0 ; ro < rawOutputs . length ; ro ++ ) {
4918+ var roid = str ( rawOutputs [ ro ] . sys_id )
4919+ var roname = str ( rawOutputs [ ro ] . name )
4920+ if ( seenOutIds [ roid ] || seenOutNames [ roname ] ) continue
4921+ seenOutIds [ roid ] = true
4922+ seenOutNames [ roname ] = true
4923+ outputParams . push ( rawOutputs [ ro ] )
4924+ }
48954925 } catch ( e : any ) {
48964926 console . warn (
48974927 "[snow_manage_flow] sys_hub_flow_variable output query failed for flow=" +
@@ -4943,15 +4973,16 @@ async function buildSubflowInputsForInsert(
49434973 var inputs = inputParams . map ( function ( rec : any ) {
49444974 var paramType = str ( rec . internal_type ) || "string"
49454975 var name = str ( rec . name )
4976+ var varId = str ( rec . sys_id )
49464977 var userVal = resolvedInputs [ name ] || ""
49474978 return {
4948- id : str ( rec . sys_id ) ,
4979+ id : varId ,
49494980 name : name ,
49504981 children : [ ] ,
49514982 displayValue : { value : "" } ,
4952- value : { value : userVal } ,
4983+ value : { schemaless : false , schemalessValue : "" , value : userVal } ,
49534984 parameter : {
4954- id : str ( rec . sys_id ) ,
4985+ id : varId ,
49554986 label : str ( rec . label ) || name ,
49564987 name : name ,
49574988 type : paramType ,
@@ -5140,6 +5171,12 @@ async function addSubflowCallViaGraphQL(
51405171 outputs : built . outputs . length ,
51415172 resolved : built . resolvedInputs ,
51425173 missing : built . missingMandatory ,
5174+ input_ids : built . inputs . map ( function ( inp : any ) {
5175+ return inp . id + ":" + inp . name
5176+ } ) ,
5177+ output_names : built . outputs . map ( function ( out : any ) {
5178+ return out . name
5179+ } ) ,
51435180 }
51445181
51455182 const subPatch : any = {
@@ -8049,6 +8086,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
80498086 case "open_flow" : {
80508087 var openFlowId = await resolveFlowId ( client , args . flow_id )
80518088 var openSummary = summary ( )
8089+ var openDebug : any = { }
80528090
80538091 // Step 1: Load flow data via processflow GET (same as UI)
80548092 try {
@@ -8057,8 +8095,22 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
80578095 /* best-effort — flow data load is not critical for lock acquisition */
80588096 }
80598097
8060- // Step 2: Acquire editing lock via safeEdit create mutation + REST fallback
8098+ // Step 2: Pre-release any existing locks (the OAuth service account may hold a stale lock
8099+ // from a previous session, and safeEdit(create) returns canEdit=false for the SAME user's lock)
8100+ var preRelease = await releaseFlowEditingLock ( client , openFlowId )
8101+ openDebug . pre_release = {
8102+ success : preRelease . success ,
8103+ safe_edit_cleaned : preRelease . debug ?. safe_edit_records_cleaned ,
8104+ flow_lock_cleaned : preRelease . debug ?. flow_lock_records_cleaned ,
8105+ }
8106+ // Brief delay to let ServiceNow propagate the lock deletion
8107+ await new Promise ( function ( resolve ) {
8108+ setTimeout ( resolve , 1500 )
8109+ } )
8110+
8111+ // Step 3: Acquire editing lock via safeEdit create mutation + REST fallback
80618112 var lockResult = await acquireFlowEditingLock ( client , openFlowId )
8113+ openDebug . acquire = lockResult . debug
80628114 if ( lockResult . success ) {
80638115 openSummary
80648116 . success ( "Flow opened for editing (lock acquired)" )
@@ -8070,7 +8122,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
80708122 flow_id : openFlowId ,
80718123 editing_session : true ,
80728124 lock_acquired_at : new Date ( ) . toISOString ( ) ,
8073- lock_debug : lockResult . debug ,
8125+ lock_debug : openDebug ,
80748126 lock_warning :
80758127 "IMPORTANT: Editing lock is held. You MUST call close_flow with flow_id='" +
80768128 openFlowId +
@@ -8082,28 +8134,53 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
80828134 )
80838135 }
80848136
8085- // Step 3: Lock failed — auto-release ghost lock and retry once
8086- // releaseFlowEditingLock now also cleans sys_hub_flow_lock table
8087- openSummary . line ( "Lock acquisition failed, attempting ghost lock cleanup..." )
8088- await releaseFlowEditingLock ( client , openFlowId )
8089- // Brief delay to let ServiceNow propagate the lock deletion before re-acquiring
8137+ // Step 4: Lock still failed — try force cleanup of ALL lock tables + longer delay + retry
8138+ openSummary . line ( "Lock acquisition failed after pre-release, attempting aggressive cleanup..." )
8139+ openDebug . first_attempt_error = lockResult . error
8140+ // Aggressively clean all lock tables (same as force_unlock)
8141+ try {
8142+ var seResp = await client . get ( "/api/now/table/sys_hub_flow_safe_edit" , {
8143+ params : { sysparm_query : "document_id=" + openFlowId , sysparm_fields : "sys_id" , sysparm_limit : 50 } ,
8144+ } )
8145+ var seRecs = seResp . data ?. result || [ ]
8146+ for ( var sei = 0 ; sei < seRecs . length ; sei ++ ) {
8147+ try {
8148+ await client . delete ( "/api/now/table/sys_hub_flow_safe_edit/" + seRecs [ sei ] . sys_id )
8149+ } catch ( _ ) { }
8150+ }
8151+ openDebug . force_safe_edit_deleted = seRecs . length
8152+ } catch ( _ ) { }
8153+ try {
8154+ var flResp = await client . get ( "/api/now/table/sys_hub_flow_lock" , {
8155+ params : { sysparm_query : "flow=" + openFlowId , sysparm_fields : "sys_id" , sysparm_limit : 50 } ,
8156+ } )
8157+ var flRecs = flResp . data ?. result || [ ]
8158+ for ( var fli = 0 ; fli < flRecs . length ; fli ++ ) {
8159+ try {
8160+ await client . delete ( "/api/now/table/sys_hub_flow_lock/" + flRecs [ fli ] . sys_id )
8161+ } catch ( _ ) { }
8162+ }
8163+ openDebug . force_flow_lock_deleted = flRecs . length
8164+ } catch ( _ ) { }
8165+ // Longer delay after aggressive cleanup
80908166 await new Promise ( function ( resolve ) {
8091- setTimeout ( resolve , 1000 )
8167+ setTimeout ( resolve , 2000 )
80928168 } )
80938169 var retryResult = await acquireFlowEditingLock ( client , openFlowId )
8170+ openDebug . retry = retryResult . debug
80948171 if ( retryResult . success ) {
80958172 openSummary
8096- . success ( "Flow opened for editing (ghost lock cleared)" )
8173+ . success ( "Flow opened for editing (stale lock cleared)" )
80978174 . field ( "Flow" , openFlowId )
80988175 . line ( "You can now use add_action, add_flow_logic, etc. Call close_flow when done." )
80998176 return createSuccessResult (
81008177 {
81018178 action : "open_flow" ,
81028179 flow_id : openFlowId ,
81038180 editing_session : true ,
8104- ghost_lock_cleared : true ,
8181+ stale_lock_cleared : true ,
81058182 lock_acquired_at : new Date ( ) . toISOString ( ) ,
8106- lock_debug : retryResult . debug ,
8183+ lock_debug : openDebug ,
81078184 lock_warning :
81088185 "IMPORTANT: Editing lock is held. You MUST call close_flow with flow_id='" +
81098186 openFlowId +
@@ -8122,7 +8199,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
81228199 return createErrorResult (
81238200 "Cannot open flow for editing: " +
81248201 ( retryResult . error || "lock acquisition failed" ) +
8125- ( retryResult . debug ? " | debug: " + JSON . stringify ( retryResult . debug ) : "" ) ,
8202+ " | debug: " +
8203+ JSON . stringify ( openDebug ) ,
81268204 )
81278205 }
81288206
0 commit comments