Skip to content

Commit 2779a2d

Browse files
author
Alex Godoroja
committed
fix(pilotctl): make appstore call timeout configurable (default 120s)
appstore call hardcoded an 8s socket read deadline, which cut off any legitimately slow method (multi-step research, cold LLM synthesis) long before it could return — surfacing as a spurious 'i/o timeout' even though the app and backend were healthy. Default to 120s and allow per-call --timeout / $PILOT_APPSTORE_CALL_TIMEOUT. The 8s was a reply deadline, not a dial timeout; dialing the local socket stays fast.
1 parent 8c63d20 commit 2779a2d

1 file changed

Lines changed: 39 additions & 4 deletions

File tree

cmd/pilotctl/appstore.go

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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
120122
Install 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+
18971905
func 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

Comments
 (0)