fix: remove premature version check during tachyon self-update

The version-based early exit in waitComponentActionJob fired while
the state file still showed running: true and RPC was healthy. This
caused the frontend to declare install complete while the backend
was still restarting services, triggering a page reload before the
install finished.

Now version detection only fires when RPC fails (backend unreachable)
or when state is done with success=false. Normal installs always wait
for the state file to confirm completion.
This commit is contained in:
Dushnilin
2026-08-20 17:39:36 +03:00
parent 364b79ee27
commit a9790f5638
2 changed files with 30 additions and 37 deletions
@@ -819,21 +819,6 @@ export const TachyonShellMethods = {
return jobDoneResult(stateResponse);
}
// The backend may be stuck reporting the job as running (worker pid
// recycled by the service restart): confirm by version on each tick.
if (
stateResponse &&
stateResponse.running &&
isSelfUpdate &&
Date.now() - lastStatusRefreshAt >=
COMPONENT_ACTION_STATUS_REFRESH_INTERVAL_MS
) {
const version = await confirmedByVersion();
if (version && (await settleVersion(version))) {
return selfUpdateResult(version);
}
}
lastStatusRefreshAt = Date.now();
const statusResponse = await executeShellCommand({
command: '/usr/bin/tachyon',
@@ -843,11 +828,6 @@ export const TachyonShellMethods = {
const parsedResponse = parseComponentActionResult(statusResponse);
if ((statusResponse.code ?? 0) !== 0 || !parsedResponse) {
if (stateResponse?.running) {
transientRpc.reset();
continue;
}
if (isSelfUpdate) {
const version =
(await confirmedByVersion()) ||
@@ -861,6 +841,15 @@ export const TachyonShellMethods = {
// A self-update replaces the package mid-flight: RPC failures are
// expected during the swap window and must not fail the operation.
// The version confirmation above is the only exit that matters.
if (!stateResponse?.running) {
continue;
}
transientRpc.reset();
continue;
}
if (stateResponse?.running) {
transientRpc.reset();
continue;
}
@@ -880,12 +869,6 @@ export const TachyonShellMethods = {
transientRpc.reset();
if (parsedResponse.running) {
if (isSelfUpdate) {
const version = await confirmedByVersion();
if (version && (await settleVersion(version))) {
return selfUpdateResult(version);
}
}
continue;
}
@@ -345,7 +345,9 @@ describe('TachyonShellMethods.componentAction', () => {
});
});
it('completes a Tachyon self-update when the backend is stuck reporting the job running (worker pid recycled)', async () => {
it('completes a Tachyon self-update when the backend is stuck reporting the job running (worker pid recycled, RPC eventually fails)', async () => {
let rpcCalls = 0;
mocks.fsRead.mockResolvedValue(
JSON.stringify({
success: true,
@@ -359,17 +361,25 @@ describe('TachyonShellMethods.componentAction', () => {
mocks.executeShellCommand.mockImplementation(({ args }) => {
if (args[0] === 'component_action_status') {
rpcCalls++;
if (rpcCalls <= 4) {
return Promise.resolve({
stdout: JSON.stringify({
success: true,
running: true,
component: 'tachyon',
action: 'reinstall',
message: 'Component action is running',
job_id: 'job-1',
}),
stderr: '',
code: 0,
});
}
return Promise.resolve({
stdout: JSON.stringify({
success: true,
running: true,
component: 'tachyon',
action: 'reinstall',
message: 'Component action is running',
job_id: 'job-1',
}),
stderr: '',
code: 0,
stdout: '',
stderr: 'Connection refused',
code: 1,
});
}