@@ -131,100 +131,130 @@ public void postRun(String output,Context context){
131131 if (context .getShell ()!=null &&
132132 context .getShell ().isOpen () &&
133133 context .getShell ().isPromptShell (getPreviousPrompt ()) &&
134- context .getShell ().getHost ().isShell () &&
135- shouldCheckExit (context ))
134+ context .getShell ().getHost ().isShell ())
136135 {
136+ boolean checkExit = shouldCheckExit (context );
137137
138- // Combined exit code capture + pwd + exit code restore in a single shSync
139- // to reduce per-command overhead from ~300ms (3 round-trips) to ~100ms (1 round-trip)
140- String combined = context .getShell ().shSync ("export __qdup_ec=$?; echo \" ${__qdup_ec}:::$(pwd)\" ; (exit $__qdup_ec)" );
138+ if (checkExit ) {
139+ // Combined exit code capture + pwd + exit code restore in a single shSync
140+ // to reduce per-command overhead from ~300ms (3 round-trips) to ~100ms (1 round-trip)
141+ String combined = context .getShell ().shSync ("export __qdup_ec=$?; echo \" ${__qdup_ec}:::$(pwd)\" ; (exit $__qdup_ec)" );
141142
142- String response = "" ;
143- String pwd = "" ;
144- boolean parsed = false ;
143+ String response = "" ;
144+ String pwd = "" ;
145+ boolean parsed = false ;
145146
146- // Parse combined response: "exitCode:::pwd"
147- if (combined != null && !combined .isBlank () && combined .contains (":::" )) {
148- String [] parts = combined .trim ().split (":::" , 2 );
149- if (parts .length == 2 && parts [0 ].matches ("-?\\ d+" )) {
150- response = parts [0 ].trim ();
151- pwd = parts [1 ].trim ();
152- parsed = true ;
147+ // Parse combined response: "exitCode:::pwd" using indexOf to avoid
148+ // issues if ::: appears in the pwd path
149+ if (combined != null && !combined .isBlank ()) {
150+ String trimmed = combined .trim ();
151+ int delimIndex = trimmed .indexOf (":::" );
152+ if (delimIndex > 0 ) {
153+ String exitPart = trimmed .substring (0 , delimIndex ).trim ();
154+ String pwdPart = trimmed .substring (delimIndex + 3 ).trim ();
155+ if (exitPart .matches ("-?\\ d+" )) {
156+ response = exitPart ;
157+ pwd = pwdPart ;
158+ parsed = true ;
159+ }
160+ }
153161 }
154- }
155162
156- // Fallback: if parsing failed due to noisy output from concurrent processes,
157- // retry by re-echoing the saved variable
158- int retry = 0 ;
159- while (!parsed && retry < 5 && combined != null && !combined .isBlank () && context .getShell ().isReady () && !context .isAborted ()) {
160- combined = context .getShell ().shSync ("echo \" ${__qdup_ec}:::$(pwd)\" " );
161- if (combined != null && combined .contains (":::" )) {
162- String [] parts = combined .trim ().split (":::" , 2 );
163- if (parts .length == 2 && parts [0 ].matches ("-?\\ d+" )) {
164- response = parts [0 ].trim ();
165- pwd = parts [1 ].trim ();
166- parsed = true ;
163+ // Fallback: if parsing failed due to noisy output from concurrent processes,
164+ // retry by re-echoing the saved variable
165+ int retry = 0 ;
166+ while (!parsed && retry < 5 && combined != null && !combined .isBlank () && context .getShell ().isReady () && !context .isAborted ()) {
167+ combined = context .getShell ().shSync ("echo \" ${__qdup_ec}:::$(pwd)\" " );
168+ if (combined != null ) {
169+ String trimmed = combined .trim ();
170+ int delimIndex = trimmed .indexOf (":::" );
171+ if (delimIndex > 0 ) {
172+ String exitPart = trimmed .substring (0 , delimIndex ).trim ();
173+ String pwdPart = trimmed .substring (delimIndex + 3 ).trim ();
174+ if (exitPart .matches ("-?\\ d+" )) {
175+ response = exitPart ;
176+ pwd = pwdPart ;
177+ parsed = true ;
178+ }
179+ }
167180 }
181+ retry ++;
182+ }
183+ // If we had to retry, $? was changed by the retries, so restore it
184+ if (retry > 0 && parsed ) {
185+ context .getShell ().shSync ("(exit $__qdup_ec)" );
168186 }
169- retry ++;
170- }
171- // If we had to retry, $? was changed by the retries, so restore it
172- if (retry > 0 && parsed ) {
173- context .getShell ().shSync ("(exit $__qdup_ec)" );
174- }
175187
176- if (parsed && context .getShell ().isReady () && !context .isAborted ()) {
177- context .setCwd (pwd );
178- if (response .matches ("\\ d+" )){
179- try {
180- context .getCommandTimer ().getData ().set ("exit_code" , Integer .parseInt (response ));
181- }catch (NumberFormatException e ){
188+ if (!parsed ) {
189+ // All retries failed — log warning and abort to avoid running in an unknown state
190+ context .error ("failed to parse exit code and pwd from postRun output [" + combined + "] for " + this );
191+ context .abort (false );
192+ } else if (context .getShell ().isReady () && !context .isAborted ()) {
193+ context .setCwd (pwd );
194+ if (response .matches ("\\ d+" )){
195+ try {
196+ context .getCommandTimer ().getData ().set ("exit_code" , Integer .parseInt (response ));
197+ }catch (NumberFormatException e ){
198+ context .getCommandTimer ().getData ().set ("exit_code" , response );
199+ }
200+ } else {
182201 context .getCommandTimer ().getData ().set ("exit_code" , response );
183202 }
184- } else {
185- context .getCommandTimer ().getData ().set ("exit_code" , response );
203+ context .getShell ().flushAndResetBuffer ();
186204 }
187- context .getShell ().flushAndResetBuffer ();
188- }
189- //log the output if not using stream logging
190- if (!context .getCoordinator ().getGlobals ().getSetting (Globals .STREAM_LOGGING ,false )){
191- String toLog = getLogOutput (output ,context );
192- if (toLog != null && !toLog .isBlank ()) {
193- if ("0" .equals (response )) {
194- context .log (toLog );
195- } else {
196- context .error (toLog );
205+ //log the output if not using stream logging
206+ if (!context .getCoordinator ().getGlobals ().getSetting (Globals .STREAM_LOGGING ,false )){
207+ String toLog = getLogOutput (output ,context );
208+ if (toLog != null && !toLog .isBlank ()) {
209+ if ("0" .equals (response )) {
210+ context .log (toLog );
211+ } else {
212+ context .error (toLog );
213+ }
197214 }
198215 }
199- }
200- //abort on non-zero exit if needed
201- if (!"0" .equals (response )){
202- boolean couldBeCtrlC = walk (CmdLocation .createTmp (), (cmd ) -> {
203- return cmd instanceof CtrlC ;
204- }).stream ().anyMatch (Boolean ::booleanValue );
216+ //abort on non-zero exit if needed
217+ if (parsed && !"0" .equals (response )){
218+ boolean couldBeCtrlC = walk (CmdLocation .createTmp (), (cmd ) -> {
219+ return cmd instanceof CtrlC ;
220+ }).stream ().anyMatch (Boolean ::booleanValue );
205221
206- if ( !couldBeCtrlC ) {
207- Cmd cmd = this ;
208- StringBuilder stack = new StringBuilder ();
209- while (cmd !=null ){
210- if ( !(cmd instanceof ScriptCmd ) ){
211- stack .append (System .lineSeparator ());
212- stack .append ((cmd instanceof Script ? "script: " :"" ) + cmd .toString ());
222+ if ( !couldBeCtrlC ) {
223+ Cmd cmd = this ;
224+ StringBuilder stack = new StringBuilder ();
225+ while (cmd !=null ){
226+ if ( !(cmd instanceof ScriptCmd ) ){
227+ stack .append (System .lineSeparator ());
228+ stack .append ((cmd instanceof Script ? "script: " :"" ) + cmd .toString ());
229+ }
230+ cmd = cmd .getParent ();
231+ }
232+ if (response .matches ("-?\\ d+" )){
233+ context .error ("aborting run due to exit code [" +response +"]\n host: " +context .getShell ().getHost ()+"\n command: " + this +(stack .length ()>0 ?"\n stack:" +stack .toString ():"" ));
234+ }else {
235+ context .error ("aborting run due to unexpected characters in exit code [" +response +"] The prompt may have changed\n host: " +context .getShell ().getHost ()+"\n command: " + this +(stack .length ()>0 ?"\n stack:" +stack .toString ():"" ));
213236 }
214- cmd = cmd .getParent ();
237+
238+ context .abort (false );
215239 }
216- if (response .matches ("-?\\ d+" )){
217- context .error ("aborting run due to exit code [" +response +"]\n host: " +context .getShell ().getHost ()+"\n command: " + this +(stack .length ()>0 ?"\n stack:" +stack .toString ():"" ));
218- }else {
219- context .error ("aborting run due to unexpected characters in exit code [" +response +"] The prompt may have changed\n host: " +context .getShell ().getHost ()+"\n command: " + this +(stack .length ()>0 ?"\n stack:" +stack .toString ():"" ));
240+ }
241+ } else {
242+ // Exit code checking disabled — still track pwd for queue-download relative paths
243+ String pwd = context .getShell ().shSync ("pwd" );
244+ if (pwd != null && !pwd .isBlank () && context .getShell ().isReady () && !context .isAborted ()) {
245+ context .setCwd (pwd .trim ());
246+ }
247+ context .getShell ().flushAndResetBuffer ();
248+ //log the output if not using stream logging
249+ if (!context .getCoordinator ().getGlobals ().getSetting (Globals .STREAM_LOGGING ,false )){
250+ String toLog = getLogOutput (output ,context );
251+ if (toLog != null && !toLog .isBlank ()) {
252+ context .log (toLog );
220253 }
221-
222-
223- context .abort (false );
224254 }
225255 }
226256 }else {
227- //exit code checking is disabled or shell is not a prompt shell, just log the output
257+ //shell is not a prompt shell, just log the output
228258 if (!context .getCoordinator ().getGlobals ().getSetting (Globals .STREAM_LOGGING ,false )){
229259 String toLog = getLogOutput (output ,context );
230260 if (toLog != null && !toLog .isBlank ()) {
0 commit comments