Repository navigation
Description
Summary
When an older background service has an explicit host override but no separate local-network setting, its status output recommends a control that cannot disable network access. Following the advice leaves LAN access available and may needlessly restart the service, although the follow-up command warns about the override. A focused command run with a disposable service fixture reproduced the conflicting output.
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- Save the script below as
repro.shand runsh repro.sh <checkout>against the stated commit, with the checkout's dependencies installed. - The script runs the real
statusCommandusing the existing test layers and a mock installed service. Its control has no host entry and requires the older-install note; the failing case addsT3CODE_HOST=0.0.0.0and requires the note to disappear.
The run reproduced the failure once out of one attempt. The script prints tool versions and one final RESULT line. Exit 1 means the defect was reproduced, 0 means the expected behavior held, and 2 means setup or the control was inconclusive. It removes its unique temporary test file and scratch directory on exit.
Reproduction script: repro.sh
#!/bin/sh
set -eu
if [ "$#" -ne 1 ]; then
printf '%s\n' 'usage: repro.sh CHECKOUT' 'RESULT: inconclusive'
exit 2
fi
checkout=$1
test_file=$checkout/apps/server/src/cli/status.s08-03.test.ts
if [ ! -f "$checkout/apps/server/src/cli/up.ts" ] || [ -e "$test_file" ]; then
printf '%s\n' 'missing checkout or temporary test path already exists' 'RESULT: inconclusive'
exit 2
fi
scratch=$(mktemp -d)
cleanup() { rm -f "$test_file"; rm -rf "$scratch"; }
trap cleanup EXIT HUP INT TERM
mkdir -p "$scratch/home"
export HOME="$scratch/home" TMPDIR="$scratch" T3CODE_HOME="$scratch/t3"
printf 'node: %s\n' "$(node --version)"
printf 'vp: %s\n' "$(vp --version | sed -n '1p')"
printf 'platform: %s\n' "$(uname -s -m)"
cat > "$test_file" <<'TEST'
import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { expect, it } from "@effect/vitest";
import * as HostProcess from "@t3tools/shared/HostProcess";
import * as NetService from "@t3tools/shared/Net";
import * as ConfigProvider from "effect/ConfigProvider";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as TestConsole from "effect/testing/TestConsole";
import { Command } from "effect/cli";
import { afterEach, vi } from "vite-plus/test";
import packageJson from "../../package.json" with { type: "json" };
import * as BootService from "../cloud/bootService.ts";
import { statusCommand } from "./up.ts";
it.layer(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer, NetService.layer))("status with an installed service", (it) => {
afterEach(() => vi.restoreAllMocks());
it.effect("does not offer --no-lan while T3CODE_HOST overrides it", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-status-s08-03-" });
const status = {
supported: true,
installed: true,
current: true,
unitPath: "example/t3code.service",
logPath: "example/boot-service.log",
} as const;
vi.spyOn(BootService, "layer").mockReturnValue(
Layer.succeed(BootService.BootService, BootService.BootService.of({
status: Effect.succeed(status),
withStopped: (operation) => operation,
restart: Effect.succeed("restarted" as const),
install: () => Effect.die("unused"),
uninstall: Effect.succeed(false),
})),
);
const runStatus = () =>
Command.runWith(statusCommand, { version: packageJson.version })(["--base-dir", baseDir]).pipe(
Effect.provideService(HostProcess.Environment, {}),
Effect.andThen(TestConsole.logLines),
Effect.map((lines) => lines.map(String).join("\n")),
Effect.provide(Layer.mergeAll(
ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })),
Layer.fresh(TestConsole.layer),
)),
);
yield* fs.writeFileString(`${baseDir}/service.env`, "");
const control = yield* runStatus();
expect(control).toContain("Older installs keep LAN access on. Turn it off: `t3 up --no-lan`.");
yield* fs.writeFileString(`${baseDir}/service.env`, "T3CODE_HOST=0.0.0.0\n");
const overridden = yield* runStatus();
expect(overridden).toContain("Local network overridden · T3CODE_HOST=0.0.0.0");
process.stdout.write(`STATUS_WITH_HOST: ${overridden.replaceAll("\n", " | ")}\n`);
expect(overridden).not.toContain("Turn it off: `t3 up --no-lan`");
}),
);
});
TEST
cd "$checkout"
set +e
vp test run apps/server/src/cli/status.s08-03.test.ts > "$scratch/test.log" 2>&1
test_status=$?
set -e
sed -n '/^STATUS_WITH_HOST:/p; /^ Test Files/p; /^ Tests/p' "$scratch/test.log"
if [ "$test_status" -eq 0 ]; then
printf '%s\n' 'RESULT: expected behavior'
exit 0
fi
if grep -q 'AssertionError.*Turn it off: `t3 up --no-lan`' "$scratch/test.log" && grep -q 'STATUS_WITH_HOST:' "$scratch/test.log"; then
printf '%s\n' 'RESULT: defect reproduced'
exit 1
fi
printf '%s\n' 'RESULT: inconclusive'
exit 2Expected behavior
With T3CODE_HOST set, status should not claim the inherited LAN setting determines reachability or recommend t3 up --no-lan as a way to close LAN access. The docs/user/background-service.md#L45-L58 says any host value replaces that setting; the docs/user/managed-policy.md#L227-L232 permits t3 up --no-lan for an old service only when service.env has no host value.
Actual behavior
With a mocked installed service for the selected home and service.env containing T3CODE_HOST=0.0.0.0 but no T3CODE_LAN, a run of t3 status --base-dir <temporary-home> printed both Local network overridden · T3CODE_HOST=0.0.0.0 and the note “Older installs keep LAN access on. Turn it off: t3 up --no-lan.” The command handler reaches the formatter through apps/server/src/cli/up.ts#L514-L545; the formatter shows the override in its row but adds the older-install note based only on the missing LAN entry (apps/server/src/cli/up.ts#L159-L180).
Impact and scope
Impact: P2 - Following the actionable status hint can leave the service bound to 0.0.0.0 while the user expects LAN access to be off; t3 up --no-lan can also restart an installed service and interrupt running agent turns without changing this reachability.
The tested input is an installed service whose environment file has a host override and no LAN entry. The displayed contradiction was deterministic in the focused run. The actual binding and the restart effect were traced in source rather than exercised against a live service: explicit host takes precedence over LAN in apps/server/src/cli/config.ts#L423-L443, while t3 up writes the LAN choice, warns about the host override, and may restart the service (apps/server/src/cli/up.ts#L288-L295, apps/server/src/cli/up.ts#L321-L347, apps/server/src/cli/up.ts#L396-L425). To close LAN access, set the host to a loopback address in service.env and restart, as the managed-policy guide describes. Reach across other service managers was not run.
Earliest failure stage
Output or presentation
Analysis or suspected cause
Source trace: readSetup sets lanFromOldDefault when the installed service has no T3CODE_LAN entry, independently of the T3CODE_HOST entry (apps/server/src/cli/up.ts#L63-L92). The status row checks the host first, but the note checks only lanFromOldDefault (apps/server/src/cli/up.ts#L159-L177).
Additional context
Open PR #31878 changes the printed command to preserve --base-dir; its described scope and patch do not change when this older-install note appears. The same conditional remains on upstream main after the stated commit, based on the current apps/server/src/cli/up.ts#L159-L180 contents and file commit history.
Evidence provenance: observed focused command output and static source traces. Local verification: reproduced once in one attempt. Reproduction completeness: complete for the status output; a live service bind and restart were not exercised. Restoration check: the reproduction script exits 0 when the host-override row remains and the --no-lan note is absent.
Impact
Minor bug or occasional failure
Version or commit
main @ c00a373
Environment
Node.js v26.12.1, vite-plus CLI v1.5.1, macOS arm64; in-process service fixture without a running background service.
Logs or stack traces
node: v26.12.1
vp: vp v1.5.1
platform: Darwin arm64
STATUS_WITH_HOST: This machine | Service installed · t3@0.1.9 | Local network overridden · T3CODE_HOST=0.0.0.0 | | Older installs keep LAN access on. Turn it off: `t3 up --no-lan`. | | Change access: `t3 up`.
Test Files 1 failed (1)
Tests 1 failed (1)
RESULT: defect reproducedScreenshots, recordings, or supporting files
No response
Workaround
Set the host to a loopback address in service.env and restart, as described in docs/user/managed-policy.md#L227-L232.
References
Merged PR #30900 corrected the managed-policy documentation to say t3 up --no-lan applies to old background services only when service.env has no host value. The status hint still appears with an explicit host override.
Activity