Skip to content

Commit 9e10b4b

Browse files
author
埃博拉酱
committed
fix: harden terminal session startup checks and review rationale
Update terminal session startup so createSession always waits for the axs HTTP endpoint to become reachable even when a stale or still-booting PID already exists. This removes the race where POST /terminals could run before the embedded server was actually ready during slow startup or crash recovery. Replace the fragile PTY open error substring detection with structured JSON parsing so normal output that happens to contain overlapping text does not trigger the binary refresh and retry path. Clarify in init-alpine.sh that the temporary allow-any-origin switch cannot be tightened from the shell wrapper because Origin validation must be implemented inside axs itself.
1 parent 6dfa3b8 commit 9e10b4b

File tree

2 files changed

+54
-31
lines changed

2 files changed

+54
-31
lines changed

src/components/terminal/terminal.js

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -640,39 +640,41 @@ export default class TerminalComponent {
640640
// In debug builds, refresh axs binary from assets before starting
641641
await Terminal.refreshAxsBinary();
642642
await Terminal.startAxs(false, () => {}, console.error);
643+
}
644+
645+
// Always wait for the HTTP endpoint, even if the PID is already alive.
646+
// kill -0 only tells us the outer process exists; the embedded server may
647+
// still be starting, especially after crash recovery on slower devices.
648+
const initialPollRetries = axsRunning ? 10 : 30;
649+
if (!(await pollAxs(initialPollRetries))) {
650+
// AXS failed to become reachable — attempt auto-repair
651+
toast("Repairing terminal environment...");
652+
653+
try {
654+
await Terminal.stopAxs();
655+
} catch (_) {
656+
/* ignore */
657+
}
643658

644-
// Wait for axs HTTP server to become reachable
645-
const pollResult = await pollAxs(30);
646-
if (!pollResult) {
647-
// AXS failed to start — attempt auto-repair
648-
toast("Repairing terminal environment...");
659+
// Re-run installing flow to repair packages / config
660+
const repairOk = await Terminal.startAxs(
661+
true,
662+
console.log,
663+
console.error,
664+
);
665+
if (repairOk) {
666+
// Start AXS again after repair
667+
await Terminal.startAxs(false, () => {}, console.error);
668+
}
649669

670+
if (!(await pollAxs(30))) {
671+
// Still broken — clear .configured so next open re-triggers install
650672
try {
651-
await Terminal.stopAxs();
673+
await Terminal.resetConfigured();
652674
} catch (_) {
653675
/* ignore */
654676
}
655-
656-
// Re-run installing flow to repair packages / config
657-
const repairOk = await Terminal.startAxs(
658-
true,
659-
console.log,
660-
console.error,
661-
);
662-
if (repairOk) {
663-
// Start AXS again after repair
664-
await Terminal.startAxs(false, () => {}, console.error);
665-
}
666-
667-
if (!(await pollAxs(30))) {
668-
// Still broken — clear .configured so next open re-triggers install
669-
try {
670-
await Terminal.resetConfigured();
671-
} catch (_) {
672-
/* ignore */
673-
}
674-
throw new Error("Failed to start AXS server after repair attempt");
675-
}
677+
throw new Error("Failed to start AXS server after repair attempt");
676678
}
677679
}
678680

@@ -681,6 +683,24 @@ export default class TerminalComponent {
681683
rows: this.terminal.rows,
682684
};
683685

686+
const parsePtyOpenError = (payload) => {
687+
if (typeof payload !== "string") {
688+
return null;
689+
}
690+
691+
const trimmed = payload.trim();
692+
if (!trimmed.startsWith("{")) {
693+
return null;
694+
}
695+
696+
try {
697+
const parsed = JSON.parse(trimmed);
698+
return typeof parsed?.error === "string" ? parsed.error : null;
699+
} catch {
700+
return null;
701+
}
702+
};
703+
684704
const response = await fetch(
685705
`http://localhost:${this.options.port}/terminals`,
686706
{
@@ -697,9 +717,10 @@ export default class TerminalComponent {
697717
}
698718

699719
const data = await response.text();
720+
const ptyOpenError = parsePtyOpenError(data);
700721

701722
// Detect PTY errors from axs server (e.g. incompatible binary)
702-
if (data.includes('"error"') && data.includes("Failed to open PTY")) {
723+
if (ptyOpenError?.includes("Failed to open PTY")) {
703724
const refreshed = await Terminal.refreshAxsBinary();
704725
if (refreshed) {
705726
// Kill old axs, restart with fresh binary, and retry once
@@ -719,7 +740,7 @@ export default class TerminalComponent {
719740
);
720741
if (retryResp.ok) {
721742
const retryData = await retryResp.text();
722-
if (!retryData.includes('"error"')) {
743+
if (!parsePtyOpenError(retryData)) {
723744
this.pid = retryData.trim();
724745
return this.pid;
725746
}

src/plugins/terminal/scripts/init-alpine.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,10 @@ chmod +x "$PREFIX/alpine/initrc"
270270
# even though axs is already listening, which triggers false repair/reinstall loops.
271271
# axs currently exposes only its default https://localhost policy or a global
272272
# allow-any-origin switch; it does not support an explicit origin allowlist yet.
273-
# Keep this until axs gains per-origin CORS configuration that can express the
274-
# WebView origin without breaking the localhost probe.
273+
# Tightening this inside the shell wrapper is not possible: Origin validation has
274+
# to happen inside axs itself, where the HTTP request is handled. Until axs gains
275+
# per-origin CORS or an equivalent auth gate, keep this stopgap so terminal
276+
# startup and localhost readiness probes remain functional.
275277
"$PREFIX/axs" --allow-any-origin -c "bash --rcfile /initrc -i"
276278

277279
else

0 commit comments

Comments
 (0)