@@ -114,8 +114,10 @@ Usage:
114114 pilotctl appstore caps <id> show the manifest's spend caps and current rolling-window usage
115115 pilotctl appstore actions [--tail N] [--event NAME]
116116 show the pilotctl-side action log (install/uninstall — survives app removal)
117- pilotctl appstore call <id> <method> [json-args]
117+ pilotctl appstore call <id> <method> [json-args] [--timeout 2m]
118118 dispatch an IPC call into an app
119+ (--timeout / $PILOT_APPSTORE_CALL_TIMEOUT;
120+ default 120s — raise for slow methods)
119121
120122Install root is taken from $PILOT_APPSTORE_ROOT or ~/.pilot/apps.
121123`
@@ -1894,10 +1896,41 @@ func cmdAppStoreActions(args []string) {
18941896
18951897// ── call ───────────────────────────────────────────────────────────────
18961898
1899+ // callTimeoutDefault bounds how long `appstore call` waits for an app's reply.
1900+ // It must comfortably exceed the slowest legitimate method (e.g. multi-step
1901+ // research / cold LLM synthesis), which can run tens of seconds. Override per
1902+ // call with --timeout, or globally with $PILOT_APPSTORE_CALL_TIMEOUT.
1903+ const callTimeoutDefault = 120 * time .Second
1904+
18971905func cmdAppStoreCall (args []string ) {
1906+ // Resolve the reply timeout (env default, then --timeout flag) and strip
1907+ // the flag from the positional args so <app-id> <method> [json] still parse.
1908+ callTimeout := callTimeoutDefault
1909+ if v := os .Getenv ("PILOT_APPSTORE_CALL_TIMEOUT" ); v != "" {
1910+ if d , err := time .ParseDuration (v ); err == nil && d > 0 {
1911+ callTimeout = d
1912+ }
1913+ }
1914+ var pos []string
1915+ for i := 0 ; i < len (args ); i ++ {
1916+ switch {
1917+ case args [i ] == "--timeout" && i + 1 < len (args ):
1918+ if d , err := time .ParseDuration (args [i + 1 ]); err == nil && d > 0 {
1919+ callTimeout = d
1920+ }
1921+ i ++
1922+ case strings .HasPrefix (args [i ], "--timeout=" ):
1923+ if d , err := time .ParseDuration (strings .TrimPrefix (args [i ], "--timeout=" )); err == nil && d > 0 {
1924+ callTimeout = d
1925+ }
1926+ default :
1927+ pos = append (pos , args [i ])
1928+ }
1929+ }
1930+ args = pos
18981931 if len (args ) < 2 {
18991932 fatalHint ("invalid_argument" ,
1900- "usage: pilotctl appstore call <app-id> <method> [json-args]" ,
1933+ "usage: pilotctl appstore call <app-id> <method> [json-args] [--timeout 2m] " ,
19011934 "missing arguments" )
19021935 }
19031936 appID := args [0 ]
@@ -1920,7 +1953,7 @@ func cmdAppStoreCall(args []string) {
19201953 "socket %s not present: %v" , sockPath , err )
19211954 }
19221955
1923- ctx , cancel := context .WithTimeout (context .Background (), 10 * time .Second )
1956+ ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Second )
19241957 defer cancel ()
19251958 dialer := & net.Dialer {Timeout : 3 * time .Second }
19261959 conn , err := dialer .DialContext (ctx , "unix" , sockPath )
@@ -1930,7 +1963,9 @@ func cmdAppStoreCall(args []string) {
19301963 "dial %s: %v" , sockPath , err )
19311964 }
19321965 defer conn .Close ()
1933- _ = conn .SetDeadline (time .Now ().Add (8 * time .Second ))
1966+ // Reply deadline (not the dial): bounds the whole call so a slow method
1967+ // (research, cold LLM) isn't cut off. Tune with --timeout / env.
1968+ _ = conn .SetDeadline (time .Now ().Add (callTimeout ))
19341969
19351970 // args is a JSON value, but ipc.Call takes a Go value. Marshal it
19361971 // back into a json.RawMessage so the wrapper round-trips cleanly.
0 commit comments