Skip to content

Commit d9bfa71

Browse files
committed
refactor(daemon): shrink handleCloseCommand/runSessionCloseTeardown under fallow's complexity gate
CI's fallow code-quality check flagged handleCloseCommand (126 lines, 19 cyclomatic / 16 cognitive) and runSessionCloseTeardown (73 lines) as exceeding the large-function/high-complexity thresholds after the prior commit's fix. Extract runCloseTeardownAndRelease (teardown + lease release + claim clear + delete + ordered error surfacing) and buildCloseSuccessResponse (final response shaping) out of handleCloseCommand, and finalizeOrdinaryCloseScript out of runSessionCloseTeardown. No behavior change — same control flow, split into named, independently-readable steps; fallow now reports 0 complexity findings for this diff.
1 parent 653a590 commit d9bfa71

1 file changed

Lines changed: 126 additions & 61 deletions

File tree

src/daemon/handlers/session-close.ts

Lines changed: 126 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -236,37 +236,53 @@ async function runSessionCloseTeardown(params: {
236236
// succeed. Only an ordinary (non-repair) session records `close` + writes its
237237
// session log here, and — per #1225 — a failed platform close is not recorded
238238
// as `Closed`.
239-
let saveScriptError: AppError | undefined;
240-
if (!repairArmed) {
241-
const actionsBeforeClose = session.actions.length;
242-
if (!platformCloseError) {
243-
recordSessionAction(sessionStore, session, req, 'close', {
244-
session: session.name,
245-
...successText(`Closed: ${session.name}`),
246-
});
247-
}
248-
if (req.flags?.saveScript) {
249-
session.recordSession = true;
250-
}
251-
try {
252-
sessionStore.writeSessionLog(session, {
253-
force: resolveEffectiveSaveScriptForce(req, session),
254-
});
255-
} catch (error) {
256-
// #1391: never let a failed script publish leak the session/device claim
257-
// (item 1) or leave an unrecorded `close` action for a later successful
258-
// write to duplicate (item 2) — roll back exactly like the repair path's
259-
// `commitRepairBeforeClose` does, then let teardown continue below.
260-
session.actions.length = actionsBeforeClose;
261-
saveScriptError = toOrdinaryCloseSaveScriptFailure(error);
262-
}
263-
}
239+
const saveScriptError = repairArmed
240+
? undefined
241+
: finalizeOrdinaryCloseScript({ req, session, sessionStore, platformCloseError });
264242
await attemptCleanup('materialized_paths', () =>
265243
cleanupRetainedMaterializedPathsForSession(sessionName),
266244
);
267245
return { platformCloseError, saveScriptError };
268246
}
269247

248+
/**
249+
* ADR 0012 decision 6 (BLOCKER 2): only an ordinary (non-repair) session
250+
* records its finalize `close` and writes its session log here — see the
251+
* call site's own comment for why a repair-armed session skips this entirely.
252+
*
253+
* #1391: the write can refuse to publish (no-clobber target-exists, or any
254+
* other fs `AppError`). Roll back the just-recorded `close` action on failure
255+
* — mirroring `commitRepairBeforeClose`'s own rollback — so neither the
256+
* session/device claim leaks (item 1) nor an unrecorded `close` survives for a
257+
* later successful write to duplicate (item 2); the caller still completes
258+
* teardown regardless of the outcome returned here.
259+
*/
260+
function finalizeOrdinaryCloseScript(params: {
261+
req: DaemonRequest;
262+
session: SessionState;
263+
sessionStore: SessionStore;
264+
platformCloseError: unknown;
265+
}): AppError | undefined {
266+
const { req, session, sessionStore, platformCloseError } = params;
267+
const actionsBeforeClose = session.actions.length;
268+
if (!platformCloseError) {
269+
recordSessionAction(sessionStore, session, req, 'close', {
270+
session: session.name,
271+
...successText(`Closed: ${session.name}`),
272+
});
273+
}
274+
if (req.flags?.saveScript) {
275+
session.recordSession = true;
276+
}
277+
try {
278+
sessionStore.writeSessionLog(session, { force: resolveEffectiveSaveScriptForce(req, session) });
279+
return undefined;
280+
} catch (error) {
281+
session.actions.length = actionsBeforeClose;
282+
return toOrdinaryCloseSaveScriptFailure(error);
283+
}
284+
}
285+
270286
/**
271287
* #1391: normalizes an ordinary (non-repair) session's close-time script-write
272288
* failure. `SessionScriptWriter.write()` only ever throws a genuine `AppError`
@@ -521,10 +537,68 @@ export async function handleCloseCommand(params: {
521537
// identity it was recorded under, so the platform close runs (again).
522538
const repair = await prepareRepairClose({ req, session, logPath, sessionStore });
523539
if ('response' in repair) return repair.response;
524-
// Resource teardown is failure-isolated: a rejected step is collected instead of
525-
// short-circuiting the rest, so every subsequent resource (and the runner stop)
526-
// is still attempted. The provider lease is committed only after that teardown,
527-
// and a failed provider release keeps the session retryable.
540+
const closed = await runCloseTeardownAndRelease({
541+
req,
542+
session,
543+
sessionName,
544+
logPath,
545+
sessionStore,
546+
leaseRegistry,
547+
leaseLifecycleProvider,
548+
repairArmed: repair.repairArmed,
549+
});
550+
if (closed.kind === 'response') return closed.response;
551+
const shutdownResult = await maybeShutdownSessionTarget({
552+
device: session.device,
553+
shutdownRequested: req.flags?.shutdown,
554+
});
555+
return buildCloseSuccessResponse({
556+
session,
557+
repair,
558+
requestedSaveScript: Boolean(req.flags?.saveScript),
559+
shutdownResult,
560+
providerData: closed.providerData,
561+
});
562+
}
563+
564+
type SessionCloseFinalization =
565+
| { kind: 'response'; response: DaemonResponse }
566+
| { kind: 'closed'; providerData?: Record<string, unknown> };
567+
568+
/**
569+
* Everything between a settled repair decision and a success response: the
570+
* failure-isolated resource teardown, the provider lease release, and — only
571+
* once both have run — the device-claim clear and session delete. A rejected
572+
* cleanup step is collected instead of short-circuiting the rest, so every
573+
* subsequent resource (and the runner stop) is still attempted; the provider
574+
* lease is released only after that teardown, and a failed release keeps the
575+
* session retryable (returned as `{kind:'response'}`, mirroring the
576+
* repair-commit-failure path above it). The platform-close failure is thrown
577+
* as the primary error with its original code/details/hint intact; the
578+
* cleanup aggregate has already been emitted as a diagnostic by this point so
579+
* per-resource failures stay visible; a failed script save (#1391) is thrown
580+
* last since — unlike the two above it — the session has already ended and
581+
* the device is already released by the time it surfaces.
582+
*/
583+
async function runCloseTeardownAndRelease(params: {
584+
req: DaemonRequest;
585+
session: SessionState;
586+
sessionName: string;
587+
logPath: string;
588+
sessionStore: SessionStore;
589+
leaseRegistry: LeaseRegistry;
590+
leaseLifecycleProvider: LeaseLifecycleProvider | undefined;
591+
repairArmed: boolean;
592+
}): Promise<SessionCloseFinalization> {
593+
const {
594+
req,
595+
session,
596+
sessionName,
597+
logPath,
598+
sessionStore,
599+
leaseRegistry,
600+
leaseLifecycleProvider,
601+
} = params;
528602
const cleanupFailures: SessionCleanupFailure[] = [];
529603
const { platformCloseError, saveScriptError } = await runSessionCloseTeardown({
530604
req,
@@ -533,17 +607,17 @@ export async function handleCloseCommand(params: {
533607
logPath,
534608
sessionStore,
535609
cleanupFailures,
536-
repairArmed: repair.repairArmed,
610+
repairArmed: params.repairArmed,
537611
// The platform close for a repair-armed session already ran (and was
538612
// confirmed to succeed) above, before the commit — never dispatch it twice.
539-
skipPlatformClose: repair.repairArmed,
613+
skipPlatformClose: params.repairArmed,
540614
});
541615
const leaseRelease = await releaseProviderLeaseForClose({
542616
session,
543617
leaseRegistry,
544618
leaseLifecycleProvider,
545619
});
546-
if (leaseRelease.response) return leaseRelease.response;
620+
if (leaseRelease.response) return { kind: 'response', response: leaseRelease.response };
547621
const cleanupAggregate = reportSessionCleanupFailures({
548622
sessionName,
549623
phase: 'session_close_cleanup_failed',
@@ -556,25 +630,21 @@ export async function handleCloseCommand(params: {
556630
await clearAdvisoryDeviceClaim(session.deviceClaim);
557631
}
558632
sessionStore.delete(sessionName);
559-
// The platform-close failure is the primary error: rethrow it with its original
560-
// code/details/hint intact. The cleanup aggregate has already been emitted as a
561-
// diagnostic above so per-resource failures stay visible.
562633
if (platformCloseError) throw platformCloseError;
563634
if (cleanupAggregate) throw cleanupAggregate;
564-
// #1391: surfaced last — the session already ended and the device is already
565-
// released above, unlike the two failures above it. Nothing left to retry in
566-
// place; `toOrdinaryCloseSaveScriptFailure`'s hint reflects that.
567635
if (saveScriptError) throw saveScriptError;
568-
const shutdownResult = await maybeShutdownSessionTarget({
569-
device: session.device,
570-
shutdownRequested: req.flags?.shutdown,
571-
});
572-
// ADR 0012 decision 6 (BLOCKER 2a): positively report the committed healed
573-
// artifact path so the agent learns the repair published (and where) without
574-
// an extra round-trip.
575-
const savedScript = repair.healedScriptPath ? { savedScript: repair.healedScriptPath } : {};
576-
const text = `Closed: ${session.name}`;
577-
if (repair.aborted && req.flags?.saveScript) {
636+
return { kind: 'closed', providerData: leaseRelease.providerData };
637+
}
638+
639+
function buildCloseSuccessResponse(params: {
640+
session: SessionState;
641+
repair: Extract<RepairClosePreparation, { repairArmed: boolean }>;
642+
requestedSaveScript: boolean;
643+
shutdownResult: DeviceTargetShutdownResult | undefined;
644+
providerData: Record<string, unknown> | undefined;
645+
}): DaemonResponse {
646+
const { session, repair, requestedSaveScript, shutdownResult, providerData } = params;
647+
if (repair.aborted && requestedSaveScript) {
578648
return {
579649
ok: false,
580650
error: {
@@ -585,29 +655,24 @@ export async function handleCloseCommand(params: {
585655
},
586656
};
587657
}
588-
658+
// ADR 0012 decision 6 (BLOCKER 2a): positively report the committed healed
659+
// artifact path so the agent learns the repair published (and where) without
660+
// an extra round-trip.
661+
const savedScript = repair.healedScriptPath ? { savedScript: repair.healedScriptPath } : {};
662+
const provider = providerData ? { provider: providerData } : {};
663+
const text = `Closed: ${session.name}`;
589664
if (shutdownResult) {
590665
return {
591666
ok: true,
592667
data: withSuccessText(
593-
{
594-
session: session.name,
595-
shutdown: shutdownResult,
596-
...savedScript,
597-
...(leaseRelease.providerData ? { provider: leaseRelease.providerData } : {}),
598-
},
668+
{ session: session.name, shutdown: shutdownResult, ...savedScript, ...provider },
599669
text,
600670
),
601671
};
602672
}
603673
return {
604674
ok: true,
605-
data: {
606-
session: session.name,
607-
...successText(text),
608-
...savedScript,
609-
...(leaseRelease.providerData ? { provider: leaseRelease.providerData } : {}),
610-
},
675+
data: { session: session.name, ...successText(text), ...savedScript, ...provider },
611676
};
612677
}
613678

0 commit comments

Comments
 (0)