instar 1.3.1201 → 1.3.1202

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-08-25T23:10:03.683Z",
5
- "instarVersion": "1.3.1201",
4
+ "generatedAt": "2026-08-25T23:24:38.827Z",
5
+ "instarVersion": "1.3.1202",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -778,7 +778,7 @@
778
778
  "type": "cli-command",
779
779
  "domain": "setup",
780
780
  "sourcePath": "src/cli.ts",
781
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
781
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
782
782
  "since": "2025-01-01"
783
783
  },
784
784
  "cli:setup": {
@@ -786,7 +786,7 @@
786
786
  "type": "cli-command",
787
787
  "domain": "setup",
788
788
  "sourcePath": "src/cli.ts",
789
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
789
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
790
790
  "since": "2025-01-01"
791
791
  },
792
792
  "cli:add": {
@@ -794,7 +794,7 @@
794
794
  "type": "cli-command",
795
795
  "domain": "setup",
796
796
  "sourcePath": "src/cli.ts",
797
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
797
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
798
798
  "since": "2025-01-01"
799
799
  },
800
800
  "cli:backup": {
@@ -802,7 +802,7 @@
802
802
  "type": "cli-command",
803
803
  "domain": "operations",
804
804
  "sourcePath": "src/cli.ts",
805
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
805
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
806
806
  "since": "2025-01-01"
807
807
  },
808
808
  "cli:git": {
@@ -810,7 +810,7 @@
810
810
  "type": "cli-command",
811
811
  "domain": "coordination",
812
812
  "sourcePath": "src/cli.ts",
813
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
813
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
814
814
  "since": "2025-01-01"
815
815
  },
816
816
  "cli:memory": {
@@ -818,7 +818,7 @@
818
818
  "type": "cli-command",
819
819
  "domain": "memory",
820
820
  "sourcePath": "src/cli.ts",
821
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
821
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
822
822
  "since": "2025-01-01"
823
823
  },
824
824
  "cli:knowledge": {
@@ -826,7 +826,7 @@
826
826
  "type": "cli-command",
827
827
  "domain": "memory",
828
828
  "sourcePath": "src/cli.ts",
829
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
829
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
830
830
  "since": "2025-01-01"
831
831
  },
832
832
  "cli:semantic": {
@@ -834,7 +834,7 @@
834
834
  "type": "cli-command",
835
835
  "domain": "memory",
836
836
  "sourcePath": "src/cli.ts",
837
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
837
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
838
838
  "since": "2025-01-01"
839
839
  },
840
840
  "cli:intent": {
@@ -842,7 +842,7 @@
842
842
  "type": "cli-command",
843
843
  "domain": "intent",
844
844
  "sourcePath": "src/cli.ts",
845
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
845
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
846
846
  "since": "2025-01-01"
847
847
  },
848
848
  "cli:feedback": {
@@ -850,7 +850,7 @@
850
850
  "type": "cli-command",
851
851
  "domain": "feedback",
852
852
  "sourcePath": "src/cli.ts",
853
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
853
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
854
854
  "since": "2025-01-01"
855
855
  },
856
856
  "cli:server": {
@@ -858,7 +858,7 @@
858
858
  "type": "cli-command",
859
859
  "domain": "server",
860
860
  "sourcePath": "src/cli.ts",
861
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
861
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
862
862
  "since": "2025-01-01"
863
863
  },
864
864
  "cli:status": {
@@ -866,7 +866,7 @@
866
866
  "type": "cli-command",
867
867
  "domain": "monitoring",
868
868
  "sourcePath": "src/cli.ts",
869
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
869
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
870
870
  "since": "2025-01-01"
871
871
  },
872
872
  "cli:user": {
@@ -874,7 +874,7 @@
874
874
  "type": "cli-command",
875
875
  "domain": "users",
876
876
  "sourcePath": "src/cli.ts",
877
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
877
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
878
878
  "since": "2025-01-01"
879
879
  },
880
880
  "cli:relationship": {
@@ -882,7 +882,7 @@
882
882
  "type": "cli-command",
883
883
  "domain": "relationships",
884
884
  "sourcePath": "src/cli.ts",
885
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
885
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
886
886
  "since": "2025-01-01"
887
887
  },
888
888
  "cli:job": {
@@ -890,7 +890,7 @@
890
890
  "type": "cli-command",
891
891
  "domain": "scheduling",
892
892
  "sourcePath": "src/cli.ts",
893
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
893
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
894
894
  "since": "2025-01-01"
895
895
  },
896
896
  "cli:lifeline": {
@@ -898,7 +898,7 @@
898
898
  "type": "cli-command",
899
899
  "domain": "communication",
900
900
  "sourcePath": "src/cli.ts",
901
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
901
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
902
902
  "since": "2025-01-01"
903
903
  },
904
904
  "cli:list": {
@@ -906,7 +906,7 @@
906
906
  "type": "cli-command",
907
907
  "domain": "monitoring",
908
908
  "sourcePath": "src/cli.ts",
909
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
909
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
910
910
  "since": "2025-01-01"
911
911
  },
912
912
  "cli:autostart": {
@@ -914,7 +914,7 @@
914
914
  "type": "cli-command",
915
915
  "domain": "operations",
916
916
  "sourcePath": "src/cli.ts",
917
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
917
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
918
918
  "since": "2025-01-01"
919
919
  },
920
920
  "cli:migrate": {
@@ -922,7 +922,7 @@
922
922
  "type": "cli-command",
923
923
  "domain": "updates",
924
924
  "sourcePath": "src/cli.ts",
925
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
925
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
926
926
  "since": "2025-01-01"
927
927
  },
928
928
  "cli:machines": {
@@ -930,7 +930,7 @@
930
930
  "type": "cli-command",
931
931
  "domain": "coordination",
932
932
  "sourcePath": "src/cli.ts",
933
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
933
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
934
934
  "since": "2025-01-01"
935
935
  },
936
936
  "cli:whoami": {
@@ -938,7 +938,7 @@
938
938
  "type": "cli-command",
939
939
  "domain": "identity",
940
940
  "sourcePath": "src/cli.ts",
941
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
941
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
942
942
  "since": "2025-01-01"
943
943
  },
944
944
  "cli:pair": {
@@ -946,7 +946,7 @@
946
946
  "type": "cli-command",
947
947
  "domain": "coordination",
948
948
  "sourcePath": "src/cli.ts",
949
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
949
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
950
950
  "since": "2025-01-01"
951
951
  },
952
952
  "cli:join": {
@@ -954,7 +954,7 @@
954
954
  "type": "cli-command",
955
955
  "domain": "coordination",
956
956
  "sourcePath": "src/cli.ts",
957
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
957
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
958
958
  "since": "2025-01-01"
959
959
  },
960
960
  "cli:wakeup": {
@@ -962,7 +962,7 @@
962
962
  "type": "cli-command",
963
963
  "domain": "coordination",
964
964
  "sourcePath": "src/cli.ts",
965
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
965
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
966
966
  "since": "2025-01-01"
967
967
  },
968
968
  "cli:leave": {
@@ -970,7 +970,7 @@
970
970
  "type": "cli-command",
971
971
  "domain": "coordination",
972
972
  "sourcePath": "src/cli.ts",
973
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
973
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
974
974
  "since": "2025-01-01"
975
975
  },
976
976
  "cli:doctor": {
@@ -978,7 +978,7 @@
978
978
  "type": "cli-command",
979
979
  "domain": "monitoring",
980
980
  "sourcePath": "src/cli.ts",
981
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
981
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
982
982
  "since": "2025-01-01"
983
983
  },
984
984
  "cli:review": {
@@ -986,7 +986,7 @@
986
986
  "type": "cli-command",
987
987
  "domain": "monitoring",
988
988
  "sourcePath": "src/cli.ts",
989
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
989
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
990
990
  "since": "2025-01-01"
991
991
  },
992
992
  "cli:nuke": {
@@ -994,7 +994,7 @@
994
994
  "type": "cli-command",
995
995
  "domain": "operations",
996
996
  "sourcePath": "src/cli.ts",
997
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
997
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
998
998
  "since": "2025-01-01"
999
999
  },
1000
1000
  "cli:playbook": {
@@ -1002,7 +1002,7 @@
1002
1002
  "type": "cli-command",
1003
1003
  "domain": "context",
1004
1004
  "sourcePath": "src/cli.ts",
1005
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
1005
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
1006
1006
  "since": "2025-01-01"
1007
1007
  },
1008
1008
  "cli:dev-preflight": {
@@ -1010,7 +1010,7 @@
1010
1010
  "type": "cli-command",
1011
1011
  "domain": "development",
1012
1012
  "sourcePath": "src/cli.ts",
1013
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
1013
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
1014
1014
  "since": "2025-01-01"
1015
1015
  },
1016
1016
  "cli:dev-ci-failures": {
@@ -1018,7 +1018,7 @@
1018
1018
  "type": "cli-command",
1019
1019
  "domain": "development",
1020
1020
  "sourcePath": "src/cli.ts",
1021
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
1021
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
1022
1022
  "since": "2025-01-01"
1023
1023
  },
1024
1024
  "cli:dev-profile-node": {
@@ -1026,7 +1026,7 @@
1026
1026
  "type": "cli-command",
1027
1027
  "domain": "development",
1028
1028
  "sourcePath": "src/cli.ts",
1029
- "contentHash": "3dfaa8d2178096f872f81fd270169cdc15e253ed9de28b994359268815ded5fa",
1029
+ "contentHash": "0982533d99db0f9f2ddf7bfba23f59b5a30627b7c4b15601e5ebebca2294f855",
1030
1030
  "since": "2025-01-01"
1031
1031
  },
1032
1032
  "template:build-stop-hook.sh": {
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "generatedFrom": "source-tree",
4
4
  "registrySha256": "8e6606a6722433c5ffc15b3870402283c5406083a14cdbd0e18e735731e2aae2",
5
- "packageVersion": "1.3.1201",
5
+ "packageVersion": "1.3.1202",
6
6
  "guards": [
7
7
  {
8
8
  "ref": "docs/audits/phase-b/f10-triage.md",
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha256": "7cc7eace9958f0daae3b9c37eadf2541b2b5a6bd3b16bf0f7f1cf281a86dadca",
2
+ "sha256": "83f0ffdca366864eaa3886d09c6b1061f7b2b4af5ff2226359aeab20bba7fcd9",
3
3
  "registrySha256": "8e6606a6722433c5ffc15b3870402283c5406083a14cdbd0e18e735731e2aae2",
4
- "packageVersion": "1.3.1201"
4
+ "packageVersion": "1.3.1202"
5
5
  }
@@ -2,5 +2,5 @@
2
2
  "sha256": "8e6606a6722433c5ffc15b3870402283c5406083a14cdbd0e18e735731e2aae2",
3
3
  "articleCount": 90,
4
4
  "generatedFrom": "docs/STANDARDS-REGISTRY.md",
5
- "packageVersion": "1.3.1201"
5
+ "packageVersion": "1.3.1202"
6
6
  }
@@ -0,0 +1,72 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ `instar autostart install` on Linux wrote a systemd **user** service, enabled it, started it, and
9
+ reported *"Your agent will start automatically when you log in."* Every one of those steps genuinely
10
+ succeeded — and the sentence was true in a way that meant **never** on a machine nobody logs into.
11
+
12
+ A per-user systemd service only runs while that user holds a login session. It does **not** start at
13
+ boot unless **lingering** is enabled for the account, and nothing enabled it or said so. On a headless
14
+ host the agent came up when someone connected over SSH and died the moment they disconnected.
15
+
16
+ `installLinuxSystemdService` now calls a new `ensureLinuxLingering()` after enabling the unit. It
17
+ reads the current linger state, attempts `loginctl enable-linger <user>` when it is off, and then
18
+ **re-reads the state to verify the effect** rather than trusting the exit code — a command that
19
+ returns success while changing nothing is reported as not done, because that is the exact defect
20
+ class being fixed. The outcome reaches the CLI, which prints what actually happened:
21
+
22
+ - **starts at boot** — lingering is on, with nobody logged in.
23
+ - **starts at login, NOT at boot** — the attempt was refused (it usually needs root). The message says
24
+ plainly that on an unattended machine this means never, and names the exact one-line fix.
25
+ - **starts at login; boot behaviour undetermined** — no `loginctl` on this system (a container, a
26
+ non-systemd distro). No fix instruction is printed, because it would not work there.
27
+
28
+ There is deliberately **no `sudo` attempt**. A setup step that silently escalates privilege takes an
29
+ authority the operator did not grant it; naming the command keeps that decision with the human. The
30
+ install still returns success in every case — a login-only service is a working install, it is just
31
+ not the thing the old message claimed.
32
+
33
+ macOS is untouched, and Windows already returns false.
34
+
35
+ ## What to Tell Your User
36
+
37
+ If you run instar on Linux, "auto-start installed" has not meant "starts when the machine boots" — it
38
+ meant "starts when you log in", and on a machine you never log into that is never. That is why an
39
+ agent on a headless Linux box could come up when you connected and vanish when you disconnected.
40
+
41
+ Instar now turns on the missing setting where it can, and where it cannot (it usually needs
42
+ administrator rights) it tells you so plainly and gives you the one command to run. Nothing changes
43
+ on macOS.
44
+
45
+ ## Summary of New Capabilities
46
+
47
+ No new capabilities. A Linux auto-start install now completes the step it was missing, and reports
48
+ which of "starts at boot" or "starts at login only" is actually true.
49
+
50
+ ## Evidence
51
+
52
+ Found on the first headless Linux host — WSL2 / Ubuntu 26.04. The reproduction is the contradiction
53
+ itself: `instar autostart install` reported success and `systemctl --user is-enabled` returned
54
+ `enabled`, while `loginctl show-user echo -p Linger` returned `Linger=no` — an enabled service that
55
+ would never start at boot.
56
+
57
+ Both live paths were exercised against a real `loginctl` on that host: an account with lingering on
58
+ returns `enabled-already`, and an unknown account returns `unavailable` rather than being
59
+ misreported as "off". That second case also surfaced a smaller leak now fixed — `loginctl` writes
60
+ `Failed to look up user <x>` to stderr, which was reaching the operator's terminal on a path the
61
+ code handles cleanly; stderr is now piped.
62
+
63
+ 12 unit tests. Both parse directions; the unanswerable case kept distinct from "no" (they need
64
+ different answers, and merging them would print a fix instruction that cannot work where it does not
65
+ apply); the already-on path asserting the privileged command is **not** re-run; verification-over-exit
66
+ -code, where a fake enable that exits 0 and changes nothing yields `needs-privilege`; a polkit
67
+ refusal; a blind-enable guard asserting the command is never run when the state is unreadable; and
68
+ four message assertions — including a regression guard that the old unconditional sentence is never
69
+ rendered for an outcome that does not deserve it.
70
+
71
+ The two failure paths are covered by injected dependencies rather than by breaking a live machine's
72
+ configuration, which is stated here rather than implied.
@@ -0,0 +1,45 @@
1
+ # "Starts automatically" didn't mean at boot on Linux — Plain-English Overview
2
+
3
+ > The one-line version: on Linux, instar set itself to start automatically and then told you it would start when you log in — which on a machine nobody logs into means never; this turns on the missing setting, checks it actually worked, and says which of the two is really true.
4
+
5
+ ## The problem in one breath
6
+
7
+ `instar autostart install` on Linux writes a correct startup service, switches it on, starts it, and reports "your agent will start automatically when you log in." That sentence is true and useless: on Linux a per-user service only runs while that user has a login session open, and it does not start when the machine boots unless a separate setting is turned on. It wasn't. On a headless machine the agent came up when someone connected over SSH and died the moment they disconnected.
8
+
9
+ ## What already exists
10
+
11
+ - **`instar autostart install`** — sets your agent to start on its own, so you don't have to launch it by hand after a restart.
12
+ - **The macOS path** — writes a login item. Works, and has always worked, because on a Mac somebody is logged in.
13
+ - **The Linux path** — writes a systemd user service, enables it, and starts it. Every one of those steps genuinely succeeds. The gap is the step nobody wrote.
14
+
15
+ ## What this adds
16
+
17
+ After enabling the service on Linux, instar now also tries to turn on "lingering" — the setting that lets your account's services keep running when you're not logged in, which is what makes them start at boot. Then it checks whether that actually worked, and prints the truth accordingly rather than an unconditional promise.
18
+
19
+ - If lingering is on, it says the agent will start when the machine boots with nobody logged in.
20
+ - If turning it on was refused (it usually needs administrator rights), it says plainly that the agent starts at login but **not** at boot, that on an unattended machine this means never, and gives you the exact one-line command to fix it.
21
+ - If the system can't answer the question at all — a container, or a Linux without this component — it says the agent starts at login and that boot behaviour couldn't be determined, rather than guessing.
22
+
23
+ ## The new pieces
24
+
25
+ - **A lingering check** — asks the system whether your account's services survive logout. It deliberately distinguishes "no" from "couldn't ask", because those need different answers and merging them would print a fix instruction that cannot work where it doesn't apply.
26
+ - **A lingering enabler** — tries to turn it on, then *re-reads the state to confirm it took effect*. It does not trust the command's exit code, because a step reporting success while producing no effect is the exact defect being fixed here.
27
+ - **An honest message** — one sentence per real outcome, replacing one sentence used for all of them.
28
+
29
+ ## The safeguards
30
+
31
+ **It never escalates privilege behind your back.** Turning on lingering usually requires administrator rights. Instar tries the plain command; if the system refuses, it tells you the command to run rather than quietly reaching for `sudo`. A setup step that silently escalates is worse than one that asks.
32
+
33
+ **It cannot claim success it didn't achieve.** The outcome is established by reading the state back afterwards, not by the command exiting cleanly. A command that returns success and changes nothing is reported as "not done", which is the honest answer and the one that gets the agent actually running.
34
+
35
+ **It doesn't make the install fail.** A service that works at login is still a working install — it's just not the thing the old message claimed. The install still succeeds; only the description of what you got becomes accurate.
36
+
37
+ **macOS is untouched.** Not a line of the Mac path changes, and lingering is a Linux-only concept.
38
+
39
+ ## What ships when
40
+
41
+ One change, one PR: the lingering attempt, the verification, the honest message, and twelve tests — including a regression guard asserting the old unconditional sentence is never printed for an outcome that doesn't deserve it.
42
+
43
+ ## What you actually need to decide
44
+
45
+ Whether instar should attempt to enable lingering during a Linux auto-start install and report which of "starts at boot" or "starts at login only" is actually true — rather than always claiming the second — yes or no.
@@ -0,0 +1,144 @@
1
+ # Side-Effects Review — Linux auto-start lingering (boot vs login)
2
+
3
+ **Version / slug:** `linux-autostart-lingering`
4
+ **Date:** `2026-08-25`
5
+ **Author:** `Echo`
6
+ **Second-pass reviewer:** `not run — see the Second-pass section`
7
+
8
+ ## Summary of the change
9
+
10
+ `instar autostart install` on Linux writes a systemd **user** service, enables it, starts it, and the CLI reports *"Your agent will start automatically when you log in."* A per-user systemd service only runs while that user holds a login session, and does not start at boot unless **lingering** is enabled for the account. It was not, and nothing said so — on a headless host the agent came up on SSH connect and died on disconnect.
11
+
12
+ This change: after enabling the unit, `installLinuxSystemdService` calls a new `ensureLinuxLingering()`, which reads the current linger state, attempts `loginctl enable-linger <user>` when it is off, and then **re-reads the state to verify the effect** rather than trusting the exit code. The outcome (`enabled-already` / `enabled-now` / `needs-privilege` / `unavailable`) is carried to the CLI, which prints a message matching what actually happened instead of one sentence for all cases.
13
+
14
+ Files touched: `src/commands/setup.ts` (`readLingerState`, `ensureLinuxLingering`, `describeLingerOutcome`, `currentSystemdUser`, outcome carrier; one call added inside `installLinuxSystemdService`), `src/cli.ts` (the `autostart install` report), `tests/unit/linux-autostart-lingering.test.ts` (+12 tests).
15
+
16
+ ## Decision-point inventory
17
+
18
+ - `installLinuxSystemdService` — **modify** — one added call after the existing enable/start. Its boolean contract is unchanged; a login-only service is still a successful install.
19
+ - `autostart install` CLI output — **modify** — the success message becomes outcome-dependent. The failure branch is untouched.
20
+ - `installAutoStart` — **pass-through** — signature and boolean semantics unchanged (four callers depend on it: `cli.ts`, `PostUpdateMigrator`, `TelegramLifeline`, and the migrator's boot-wrapper regeneration). The outcome travels via a module-level carrier read with `takeLastLingerOutcome()`, deliberately so those callers need no change.
21
+
22
+ ---
23
+
24
+ ## 1. Over-block
25
+
26
+ No block/allow surface — over-block not applicable. Nothing is refused, gated, or filtered; the change adds one system call and changes what a message says.
27
+
28
+ ---
29
+
30
+ ## 2. Under-block
31
+
32
+ No block/allow surface — under-block not applicable. The nearest analogue is what the change still cannot fix, stated plainly:
33
+
34
+ - **A refusal is reported, not resolved.** When polkit denies `enable-linger` (the common case when not root), the agent still starts only at login until the operator runs the named command. This is deliberate — see §4.
35
+ - **`unavailable` is genuinely unknown, not assumed good.** A container without `loginctl` gets "couldn't be determined", and no fix instruction is printed, because the instruction would not work there.
36
+ - **Nothing re-checks later.** If lingering is turned off after install, nothing notices. Out of scope for an install-time step; the guard-posture surfaces are where a standing check would belong.
37
+
38
+ ---
39
+
40
+ ## 3. Level-of-abstraction fit
41
+
42
+ Correct layer: the defect is that the Linux install path performs an incomplete install, so the missing step belongs in the Linux install path. No new layer, no new caller, no new subsystem.
43
+
44
+ The CLI split is deliberate. Reporting is the CLI's job and the install function's job is to install — so the install function establishes the fact and the CLI renders it. Folding lingering into `installAutoStart`'s boolean was rejected: a login-only service is a working install, and returning `false` for it would make `PostUpdateMigrator` log a spurious error and could make a migration look failed when it succeeded.
45
+
46
+ ---
47
+
48
+ ## 4. Signal vs authority compliance
49
+
50
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
51
+
52
+ - [x] No — this change has no block/allow surface.
53
+
54
+ It performs one idempotent system configuration step and reports the result. It holds no authority over any agent behaviour and gates nothing.
55
+
56
+ The one authority-adjacent decision is what it does **not** do: there is no `sudo` attempt. A setup step that silently escalates privilege takes an authority the operator did not grant it; naming the command instead keeps the decision with the human. That is a deliberate design choice, not an omission.
57
+
58
+ ---
59
+
60
+ ## 4b. Judgment-point check (Judgment Within Floors standard)
61
+
62
+ No new static heuristic at a competing-signals decision point. The domain is enumerable and binary: lingering is on, off, or unreadable. There are no competing live signals to weigh — one query, one answer, and the "unreadable" case is carried explicitly rather than collapsed into either verdict.
63
+
64
+ ---
65
+
66
+ ## 5. Interactions
67
+
68
+ - **Shadowing:** none. The call runs after the existing `daemon-reload` / `enable` / `start` sequence and changes none of them.
69
+ - **Double-fire:** `enable-linger` is idempotent, and the `enabled-already` branch skips the call entirely, so a repeat install runs no privileged command needlessly. `PostUpdateMigrator` regenerates auto-start on update and will therefore re-check lingering on every update — a read, then a no-op.
70
+ - **Races:** none introduced. Two concurrent installs would both read, possibly both enable (idempotent), and both verify.
71
+ - **Feedback loops:** none.
72
+ - **Carrier lifetime:** `takeLastLingerOutcome()` clears on read, so a later caller cannot pick up a stale outcome from an earlier install. Null on macOS and before any install, where the CLI falls back to the original message.
73
+ - **PostUpdateMigrator:** calls `installAutoStart` but not the carrier, so it is unaffected and prints nothing new. Intentional — an update is not the moment to surface an install-time instruction.
74
+
75
+ ---
76
+
77
+ ## 6. External surfaces
78
+
79
+ - **Other agents on the same machine:** `enable-linger` is per-user, so enabling it for the account running instar affects every service that account owns. On a dedicated agent account this is the intent; on a shared login it means that user's other services also survive logout. Worth naming, but it is the setting the operator would run by hand anyway.
80
+ - **Install base:** Linux only. macOS is untouched and Windows already returns false.
81
+ - **External systems:** none.
82
+ - **Persistent state:** `enable-linger` writes `/var/lib/systemd/linger/<user>`, owned by systemd. Removing it is `loginctl disable-linger <user>`.
83
+ - **Timing:** one or two bounded `loginctl` calls (5s timeout) at install time only. Not on any hot path.
84
+ - **Operator surface (Mobile-Complete Operator Actions):** the change adds no operator-facing *action*. It changes an existing CLI message and, in the refusal case, hands the operator a one-line command — which is the correct surface, because the action requires root on that machine's shell and no dashboard control could perform it. No PIN-gated or approval-class route is added.
85
+
86
+ ---
87
+
88
+ ## 6b. Operator-surface quality (Operator-Surface Quality standard)
89
+
90
+ No operator surface — not applicable. No dashboard renderer, approval page, or grant/revoke/secret-drop form is touched. (`src/cli.ts` is a terminal message, not a dashboard surface.)
91
+
92
+ ---
93
+
94
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
95
+
96
+ **Machine-local BY DESIGN.** Lingering is a property of one account on one machine's init system; whether machine A's agent survives logout says nothing about machine B, and each machine installs its own auto-start. Replicating it would be meaningless.
97
+
98
+ - **User-facing notices:** none — the change emits no Telegram/Slack/attention output. It prints to the terminal of the person running the command. One-voice gating not applicable.
99
+ - **Durable state:** none instar-owned. The systemd linger marker is machine-local by definition and cannot strand on topic transfer.
100
+ - **Generated URLs:** none.
101
+
102
+ ---
103
+
104
+ ## 8. Rollback cost
105
+
106
+ - **Hot-fix release:** revert both files, ship as a patch. Pure code.
107
+ - **Data migration:** none instar-owned. Any `linger` markers already set stay set — which is the desired state anyway, and is reversible with `loginctl disable-linger <user>`.
108
+ - **Agent state repair:** none. Reverting restores the previous message; already-installed services keep working exactly as they do now.
109
+ - **User visibility:** the message reverts to the old unconditional sentence. No functional regression.
110
+
111
+ ---
112
+
113
+ ## Conclusion
114
+
115
+ The review produced one design change: the outcome travels to the CLI through a read-and-clear carrier rather than by widening `installAutoStart`'s return type. Widening it would have touched four callers, and `PostUpdateMigrator` treats `false` as an error — so a login-only-but-working install would have started logging spurious migration failures. The carrier keeps the blast radius to the two files that actually care.
116
+
117
+ The second design point worth recording is the refusal to call `sudo`. It makes the fix incomplete in the common non-root case, and that is the right trade: a setup step that silently escalates privilege is a worse defect than the one being fixed.
118
+
119
+ Verification is honest about its own limits: the `enabled-already` and `unavailable` paths were exercised against a real `loginctl` on the Linux host, and the two failure paths (`needs-privilege` via refusal, and via a command that exits 0 while changing nothing) are covered by injected dependencies rather than by breaking a live machine's configuration.
120
+
121
+ ---
122
+
123
+ ## Second-pass review (if required)
124
+
125
+ **Reviewer:** none — not spawned.
126
+ **Independent read of the artifact: NOT PERFORMED.**
127
+
128
+ Recorded rather than glossed. This change is not in the Phase-5 trigger list — no messaging block/allow, no session lifecycle, no coherence/idempotency/trust surface, and nothing named sentinel/guard/gate/watchdog. It is an install-time configuration step with no authority over agent behaviour.
129
+
130
+ A reviewer subagent was additionally not available: a standing operator instruction in the authoring session forbids delegating to subagents unless explicitly asked. The PR is the review surface. If an independent read is wanted before merge, it can be run on request.
131
+
132
+ ---
133
+
134
+ ## Evidence pointers
135
+
136
+ - Live reproduction (WSL2 / Ubuntu 26.04, headless): `instar autostart install` reported success and `systemctl --user is-enabled` returned `enabled`, while `loginctl show-user echo -p Linger` returned `Linger=no` — an enabled service that would not start at boot.
137
+ - 12 unit tests. Both parse directions; the unanswerable case kept distinct from "no"; the no-op-when-already-on path asserting the privileged command is *not* re-run; verification-over-exit-code (a fake enable that exits 0 and changes nothing yields `needs-privilege`); a polkit refusal; the blind-enable guard (`unavailable` never runs the command); and four message assertions including a regression guard that the old unconditional sentence is never rendered for a non-booting outcome.
138
+ - The fix was authored on macOS and the live linger paths were exercised on the Linux host that exposed the defect.
139
+
140
+ ---
141
+
142
+ ## Class-Closure Declaration (display-only mirror)
143
+
144
+ No agent-authored-artifact defect — not applicable. The defect is in hand-written TypeScript, not an LLM prompt, hook, config, skill, or standards text. No self-triggered controller is added or modified: the change adds no loop, monitor, sentinel, reaper, scheduler, or recovery path — it is a one-shot install-time step with no timer and no re-drive.