Files
ClawdDotNet/docs/Instanzmanager-Konzept.md
T
RichardandClaude Opus 5 8686d13999 Instanzmanager Stufe 2: Kestrel auf dem Socket, Instanz kopflos (#528)
Eine Instanz ist jetzt ohne Port ansprechbar. InstanceEndpoint liefert die
Adresse: unter Linux <Instanz>/manager.sock, unter Windows eine Named Pipe,
deren Name aus einem SHA-256-Fingerabdruck des Instanzpfades entsteht. Zwei
Instanzen bekommen damit zwangslaeufig zwei Adressen, ohne dass jemand
etwas vergeben muss - genau der Unterschied zur Vorbelegung 7001, um die
sich zwei Instanzen stillschweigend streiten.

Der Socket ist immer da, auch ohne Weboberflaeche. Er gehoert nicht zu
#494, sondern ist die Steuerflaeche der Instanz: webUiEnabled schaltet den
TCP-Zugang, nicht den Socket. WebUiHost heisst deshalb jetzt
InstanceHttpHost - ein Kestrel, zwei Zugaenge.

Ueber TCP gilt Token-Pflicht, ueber den Socket nicht: Dort steht die Grenze
am Dateisystem (0600 unter Linux, CurrentUserOnly bei der Named Pipe,
explizit gesetzt statt auf die Vorgabe vertraut). Wer die Socketdatei
oeffnen darf, sieht ohnehin in das Verzeichnis, in dem alles liegt, was ein
Token schuetzen wuerde.

Woran die beiden Zugaenge auseinandergehalten werden, ist der Punkt, an dem
ein Irrtum teuer waere. Bei Socket-Verbindungen stehen in
HttpContext.Connection keine IP-Adressen - daraus zu schliessen "keine IP,
also Socket" waere eine Vermutung an der Stelle, an der ein Torwaechter
steht, und sie irrt in die falsche Richtung: Im Zweifel liesse sie eine
TCP-Anfrage ohne Token durch. Der Socket-Zuhoerer haengt deshalb jeder
Verbindung selbst eine Marke an (LocalTransport, Verbindungs-
Zwischenschicht ueber ListenOptions.Use); wo sie fehlt, gilt Token-Pflicht.

Ein belegter Port kostet nur noch die Weboberflaeche. Vorher gab TryStart
in dem Fall null zurueck und die ganze HTTP-Flaeche war weg. Seit dem
Socket ist das die falsche Rangfolge: Ihn zu verlieren, weil eine zweite
Instanz laeuft, hiesse die Instanz unerreichbar zu machen - in genau dem
Fall, fuer den es den Manager gibt.

Kopfloser Betrieb ueber --headless --instance <Pfad>. Die Verzweigung sitzt
in Program.Main vor BuildAvaloniaApp, damit auf einem Server ohne
Bildschirmsitzung kein Fenstersystem angefasst wird. Gefragt wird nichts:
Instanz aus dem Argument, Lizenz ueber das bereits vorhandene
NonInteractiveLicensePrompt, Telegram-Anmeldung aus.

Nachgemessen, nicht angenommen: Kopflose Instanz auf Instance-Test
gestartet, kein Fenster, und eine rohe HTTP/1.1-Anfrage aus einem fremden
Prozess (PowerShell, direkt auf die Pipe) bekam 200 OK auf /api/info, ohne
Token. Das ist die Abnahmebedingung fuer Stufe 2.

Korrektur zur eigenen Ankuendigung: webServerPort entfaellt nicht mit
Stufe 2, sondern mit Stufe 4. Solange kein Manager die Weboberflaeche
traegt, hiesse Wegnehmen, sie ersatzlos abzuschalten.

Zwei Luecken bleiben offen und gehoeren auf die Linux-Testmaschine: Die
Socketdatei-Faelle sind unter Windows gegenstandslos und werden dort mit
sichtbarer Begruendung uebersprungen - der Unix-Domain-Socket selbst ist
auf der Entwicklungsmaschine also ungeprueft, obwohl er die Zielplattform
ist. Und das geordnete Ende auf SIGTERM liess sich hier nicht ausloesen:
Windows kennt das Signal fuer einen fensterlosen Prozess nicht.

Build ohne Warnungen, 714 Tests gruen, 8 uebersprungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 22:08:23 +02:00

20 KiB

Instanzmanager — Befund und Konzept

Warum mehrere Instanzen heute nicht unabhängig voneinander laufen, und wie es stattdessen aussehen soll.

Stand: 2026-09-08. Anlass: Beim Instanzwechsel hat sich die Anwendung aufgehängt und wurde von Windows beendet. Vorhaben: #528, zugehöriger Fehler: #529.

Die Entscheidungen in Abschnitt 2 sind getroffen. Was danach kommt, folgt aus ihnen.


1 — Der Befund

1.1 Was passiert ist

Am 2026-09-08 wurde aus der laufenden Anwendung heraus eine zweite Instanz angelegt und auf sie gewechselt:

Zeit Quelle Eintrag
17:26:48 Dateisystem Instance-Test2 angelegt
17:26:50 Lifetime.log Application is shutting down...
17:26:55 Diagnostics.log Zwei Ereignisströme beendet, 33 s und 776 s Laufzeit
17:27:45 Windows-Ereignisprotokoll AppHangB1 — „hat aufgehört mit Windows zu interagieren und wurde geschlossen"

Die Anwendung wurde nicht beendet, sie wurde erschlagen. Bemerkenswert ist, was dabei nicht geschah: Zu diesem Prozess steht keine einzige Protokollzeile in den Instanzprotokollen — der Schreibpuffer wurde nie geleert. Keine Fehlermeldung, kein Bugtracker-Eintrag, kein Watchdog-Alarm. Der Vorfall ist ausschließlich rekonstruierbar, weil das Betriebssystem ihn festgehalten hat.

Das ist der Kern: Meldeweg, Watchdog und Fehler-Stream leben in dem Prozess, über dessen Zustand sie berichten sollen. Fällt er hart, fällt der Meldeweg mit — genau in dem Fall, der gemeldet werden müsste.

1.2 Was heute geteilt ist, obwohl es getrennt sein müsste

Geteilt Wirkung Beleg
Ein Protokollverzeichnis Alle Instanzen schreiben in dieselben Dateien, ohne Kennzeichnung, welche spricht Startup.log enthält abwechselnd Zeilen von Test und Test2
Eine Settings.json Vier Stellen schreiben sie im laufenden Betrieb. Zwei Instanzen: die letzte gewinnt, stillschweigend LicenseGate, MainWindowViewModel, AgentsPageViewModel, SettingsPageViewModel
Ein Sicherungsverzeichnis Ein Ordner für alle Archive. Die Aufbewahrung räumt dort nach dem Präfix backup_<Instanzname>_ auf — und ein Präfix ist keine Grenze: backup_Test_2_….zip fängt mit backup_Test_ an, Test wirft es also weg AppSettings.BackupDirectory
Ein Sicherungs-Schalter Wer die tägliche Sicherung für eine Instanz einschaltet, schaltet sie für alle ein AppSettings.AutoBackupEnabled
Ein Web-Port als Vorgabe Jede Instanz bringt webServerPort: 7001 mit; die zweite kann nicht binden InstanceConfig.WebServerPort

1.3 Der Wechsel kann gar nicht anders enden

Der Instanzmanager ist heute ein Fenster innerhalb einer laufenden Instanz. Wechseln heißt deshalb: Der Prozess fährt sich selbst herunter, startet sich mit --instance neu und beendet sich. Wer die Instanz wechselt, beendet zwangsläufig die, aus der heraus er es tut.


2 — Die Entscheidungen

Getroffen am 2026-09-08.

E-M1 — Der Weg zwischen Manager und Instanz

Entschieden: HTTP über einen Unix-Domain-Socket je Instanz, unter Windows über eine Named Pipe. Der Socket liegt im Instanzverzeichnis (<Instanz>/manager.sock), Rechte 0600.

Warum nicht TCP: Ports müssten verwaltet, vergeben und gegen Kollisionen gesichert werden — genau das Problem, an dem die heutige Vorbelegung 7001 scheitert. Ein Socket im Instanzverzeichnis braucht keine Vergabe, und die Zugriffsrechte stehen schon am Dateisystem.

Warum trotzdem HTTP darüber: Weil der Manager Anfragen an die Instanzen weiterleitet (E-M5). Bleibt das Protokoll HTTP, ist das Weiterleiten eine Weiterleitung und keine Übersetzung — und die bestehenden Endpunkte, DTOs und der Ereignisstrom aus #494 gelten unverändert weiter. Kestrel kann auf beidem hören, SocketsHttpHandler kann auf beidem verbinden.

E-M2 — Die Oberflächen

Entschieden: Jede Instanz behält ihr eigenes Desktop-Fenster, der Manager bekommt ein eigenes. Die Instanzfenster laufen im Hintergrund und werden beim Wechsel nach vorn geholt. Ein Wechsel ist damit kein Neustart mehr, sondern ein Fensterwechsel.

E-M3 — Lizenz

Entschieden: Die Lizenz liegt im Manager und wird dort geprüft. Instanzprozesse starten nicht ohne aktiven Manager. Eine Lizenz erlaubt beliebig viele Instanzen.

Folge: LicenseGate und LicenseWatch wandern aus dem Instanzprozess in den Manager. AppHost.Callbacks.License entfällt für Instanzen.

E-M4 — Einstellungen

Entschieden: Der Manager kennt nur das Nötigste, alles andere gehört der Instanz. Die Aufteilung steht in Abschnitt 6.

E-M5 — Die Weboberfläche

Entschieden: Eine Adresse, ein Zugang, alle Instanzen. Der Manager besitzt Port, Zugang und Rahmen. Anfragen zu Chat, Agenten, Aufgaben und Freigaben reicht er über E-M1 an die betroffene Instanz weiter.

Er leitet weiter, er implementiert nicht. Keine Fachlogik im Manager — die Weiterleitung kennt Kennungen und Pfade, sonst nichts.

E-M6 — Wenn der Manager ausfällt

Entschieden: Laufende Instanzen laufen weiter. Der neu gestartete Manager findet sie über eine Datei im Instanzverzeichnis wieder und übernimmt die Aufsicht.

Damit wird der Manager nicht zu dem Einzelpunkt, gegen den dieses ganze Vorhaben gebaut wird. Die Prüfung „gibt es einen Manager?" findet beim Start einer Instanz statt, nicht fortlaufend.

Ausnahme: Ein Lizenzwiderruf ist kein Ausfall, sondern ein Urteil. Erfährt der Manager davon, hält er die Instanzen an.


3 — Der Schnitt

┌──────────────────────────────────────────────────────────────┐
│  Instanzmanager  (ein Prozess je Maschine)                   │
│  · eigenes Fenster + die eine Weboberfläche                  │
│  · Instanzen anlegen, starten, anhalten, entfernen           │
│  · Aufsicht: lebt sie? stürzt sie wiederholt ab?             │
│  · Lizenz · Update · DC-Master-Token → Sub-Token je Instanz  │
│  · KEINE Datenbank. KEINE Fachlogik. KEINE Werkzeuge.        │
└──────┬───────────────────────────────────────────────────────┘
       │ manager.sock (HTTP über UDS / Named Pipe)
   ┌───┴────────┬────────────────┬───────────────┐
   ▼            ▼                ▼               ▼
┌────────┐  ┌────────┐      ┌────────┐    Fenster je Instanz
│Instanz │  │Instanz │      │Instanz │    (laufen im Hinter-
│  A     │  │  B     │      │  C     │     grund, kommen beim
│        │  │        │      │        │     Wechsel nach vorn)
│ Engine · Werkzeuge · Taskboard · Staging · state.db          │
│ eigenes Protokoll · eigenes Sub-Token · eigener Meldeweg     │
│ Telegram · Rocket.Chat · Mail · FTP — ausschliesslich hier   │
└────────┘  └────────┘      └────────┘

3.1 Der Manager hat keine Datenbank

Alles, was er braucht, liegt als JSON im Projektverzeichnis: die Instanzliste, seine drei Einstellungen, je Instanz eine kleine Zustandsdatei (läuft sie? seit wann? mit welcher Prozesskennung? wie oft zuletzt abgestürzt?).

state.db einer Instanz fasst er nicht an — die Datei gehört genau einem Prozess, und prozessübergreifend gilt die Schreibsperre von SqliteStorage nicht.

3.2 Werkzeuge laufen ausschließlich in der Instanz

Telegram, Rocket.Chat, Mail, FTP, Datenbank — alle Werkzeuge gehören der Instanz und nur ihr. Der Manager kennt sie nicht und spricht nie mit ihnen.

Das gilt auch für die Telegram-Anmeldung. Sie ist ein Rückruf in AppHost.Callbacks; mit E-M2 beantwortet ihn das eigene Fenster der Instanz. Über den Manager läuft davon nichts. (Eine frühere Fassung dieses Papiers hat den Rückruf als Beleg für Kopflosigkeit angeführt — das war missverständlich: Der Rückruf zeigt nur, dass die Anwendungsschicht ihre Umgebung nicht festschreibt.)


4 — Was der Manager protokolliert und meldet

Nur, was aus seiner Sicht relevant ist:

Ereignis Protokoll Ans Deploymentcenter
Instanz gestartet ja Zustandswechsel
Instanz geordnet beendet ja Zustandswechsel
Instanz abgestürzt ja Fehlermeldung stellvertretend
Instanz startet wiederholt neu ja Fehlermeldung
Instanz antwortet nicht mehr auf dem Socket ja Fehlermeldung
Lizenz ungültig, Update fehlgeschlagen ja Fehlermeldung

Was die Instanz betrifft, bleibt in der Instanz. Ein fehlgeschlagener Werkzeugaufruf, ein abgebrochener Agentenlauf, eine überschrittene Budgetgrenze — das protokolliert und meldet die Instanz selbst, mit ihrem eigenen Sub-Token. Der Manager sieht davon nichts und soll davon nichts sehen.

Der Fall aus 1.1 ist genau der, für den es den Manager gibt: Die Instanz kann nichts mehr sagen, also sagt er es an ihrer Stelle — mit Prozesskennung, Beendigungsgrund und dem Ausschnitt aus ihrem Protokoll, der noch auf der Platte liegt.

4.1 Jede Instanz meldet weiterhin selbst

Zwei Wege, mit Absicht:

  • Die Instanz meldet, solange sie lebt — eigener Fehler-Stream, eigener Heartbeat, eigener Watchdog-Monitor. Das Deploymentcenter führt Instanzen bereits über source + instance als eigene Monitore.
  • Der Manager meldet, was die Instanz nicht mehr melden kann.

Jeder Weg deckt den Ausfall des anderen ab. Ein Manager, der jede Meldung durchreicht, wäre der neue Einzelpunkt.


5 — Lizenz und Token

Der Manager hält das Master-Token, die Instanzen halten Sub-Token.

Das Verfahren ist gebaut: TokenProvisioner tauscht über POST /api/tokens/v1/provision das Master-Token gegen ein Sub-Token je Instanz, mit den Rechten watchdog:ping und bugtracker:report. Rechte lassen sich dabei nur einschränken, nie erweitern; jedes Sub-Token ist einzeln widerrufbar.

Zu tun bleibt der Umzug: Heute wird bei der Einrichtung provisioniert (setup.json, "source": "provision"), künftig beim Anlegen einer Instanz durch den Manager.

Achtung bei der Projektbindung. Seit SDK 2.8.1 erben Watchdog-Monitore das Projekt ihres Ping-Tokens; ein Monitor mit project_slug = NULL erzeugt Befunde im Scheinprojekt „default", und dc_findings(project_slug=…) liefert für eine laufende, meldende Instanz dann null Befunde. Die Sub-Token müssen also projektgebunden ausgestellt werden — sonst sieht die Aufsicht aus wie „nichts auffällig".

Wir stehen auf SDK 2.6.0, aktuell ist 2.8.2. #218 ist damit Voraussetzung, nicht Nacharbeit.


6 — Einstellungen: die Aufteilung

Der Manager (manager.json)

Feld Warum es nicht in die Instanz kann
webUiPort, webUiBinding, webUiToken Eine Weboberfläche für alle
licenseKey E-M3: die Lizenz gilt der Installation
deploymentcenterUrl, deploymentcenterMasterToken Aus ihm entstehen die Sub-Token
instancesDirectory Wo die Instanzen liegen — das muss er wissen, bevor es eine gibt
updateChannel Aktualisiert wird das Programm, nicht die Instanz

Mehr nicht. Farbschema, Teilerstellungen und Protokollstufe sind Werte einer Oberfläche und gehören zu der, die sie anzeigt.

Die Instanz (InstanceSettings.json)

Alles Übrige, und drei Felder ziehen aus den Anwendungseinstellungen dorthin um — sie waren nie anwendungsweit, sie sahen nur so aus:

Feld Heute Künftig
logDirectory geteilt, alle Instanzen in denselben Dateien <Instanz>/Logs, Vorbelegung
backupDirectory + backupKeepCount geteilt, Präfixkollision bei ineinanderliegenden Namen je Instanz
autoBackupEnabled + autoBackupTime geteilt, ein Schalter für alle je Instanz
webServerPort je Instanz, Vorbelegung 7001, Kollision entfällt — es gibt nur noch den Socket

autoBackupEnabled und autoBackupTime standen in der ersten Fassung dieser Tabelle nicht. Sie sind mitgegangen, weil die Hälfte eines Blocks zu verschieben eine Einstellungsseite hinterlässt, auf der zwei von vier Sicherungsfeldern für alle Instanzen gelten und zwei nur für eine — und weil ein Schalter, der die Sicherung für alle einschaltet, derselbe Fehler ist wie ein Ordner für alle.

deploymentcenterEnvironment bleibt bei der Instanz: Eine Maschine kann eine Produktions- und eine Testinstanz nebeneinander betreiben.


7 — Noch zu klären

Sicherungen im Manager? Dafür spricht viel: Die gemeinsame Rotation aus 1.2 verschwände, und eine Sicherung ließe sich auch von einer Instanz ziehen, die gerade nicht läuft. Dagegen spricht eine Falle — eine Sicherung zurückzuspielen heißt, in ein Instanzverzeichnis zu schreiben, während die Instanz womöglich läuft. Das muss den Prozess erst anhalten, sonst schreibt man unter einer offenen state.db das Dateisystem um. Wenn es kommt, dann mit dieser Reihenfolge: anhalten, zurückspielen, starten — und nie ohne Rückfrage.

Wie stellt eine Instanz fest, dass ein Manager da ist? Vorschlag: Der Manager startet sie und gibt ihr den Socketpfad mit; die Instanz verbindet sich einmal zurück und beendet sich, wenn das nicht gelingt. Kein Selbstzweck-Handschlag, sondern die Prüfung aus E-M3.

Der Aufhänger aus 1.1 ist nicht geklärt (#529). Er verschwindet mit dem Umbau vermutlich von selbst, weil es den Selbst-Neustart nicht mehr gibt — aber „vermutlich" ist kein Befund. Zwei unbelegte Verdachtsmomente: BackupScheduler hat sechs await und kein einziges ConfigureAwait(false) und fängt sich damit den Oberflächenfaden ein; ShutdownRequested ist ein async void-Behandler.


8 — Was aus #494 wird

Die Weboberfläche ist nicht umsonst gebaut. Sie zerfällt in zwei Teile, die beide bleiben:

Teil Wohin
Endpunkte, DTOs, Maskierung, Ereignisstrom In die Instanz. Statt auf einem TCP-Port hört Kestrel auf dem Socket. Der Code bleibt, die Bindung ändert sich
wwwroot (Gemini), Anmeldung, Token, Rahmen In den Manager. Dazu die Instanzauswahl und die Verwaltungsansichten
Weiterleitung /api/instanzen/{id}/… → Instanz Neu im Manager

Der Ereignisstrom ist die eine Stelle, die wirklich neu gedacht werden muss: N Instanzen, ein Strom zum Browser. Der Manager hält je laufender Instanz eine Verbindung und führt sie zusammen, mit der Instanzkennung an jedem Ereignis.


9 — Reihenfolge

Stufe Inhalt Woran man es misst
1 ✔ Einstellungen und Protokolle je Instanz trennen (Abschnitt 6) Zwei Instanzen gleichzeitig, keine schreibt der anderen ins Protokoll
2 ✔ Instanzprozess kopflos lauffähig, Kestrel auf dem Socket Eine Instanz läuft ohne Fenster, der Socket antwortet
3 Manager: anlegen, starten, anhalten, Prozessaufsicht, Lizenz, Sub-Token Eine Instanz abschießen — der Manager merkt es und meldet es
4 Weboberfläche zieht um, Weiterleitung, ein Ereignisstrom Eine Adresse, ein Zugang, Wechsel ohne Neustart
5 Instanzfenster als eigener Prozess, Wechsel holt das Fenster nach vorn Kein Neustart mehr beim Wechseln
6 Sicherungen im Manager (falls Abschnitt 7 dafür entscheidet) Zurückspielen hält die Instanz an und startet sie wieder

Stufe 1 ist die, die man überspringen möchte und nicht überspringen darf. Solange alle Instanzen in dieselben Protokolldateien schreiben, ist jede Fehlersuche im Mehrinstanzbetrieb Ratearbeit — und genau in dieser Lage ist der Vorfall aus 1.1 entstanden.

9.1 Was Stufe 1 gebracht hat

Fünf Schlüssel sind aus AppSettings in InstanceSettings.json gewandert: logDirectory, backupDirectory, backupKeepCount, autoBackupEnabled, autoBackupTime. Leer heißt jetzt <Instanz>/Logs und <Instanz>/Backups; ein relativer Wert zählt weiterhin gegen das Instanzverzeichnis (#375).

Keine Übernahme der Altwerte, aber eine Ansage. Ein anwendungsweiter Pfad, in alle Instanzen kopiert, wäre wieder derselbe Ordner für alle. Stehen die Schlüssel noch in einer alten Settings.json, meldet SettingsManager.AbandonedKeys sie, und der Start protokolliert einmal, was nicht mehr gilt und wohin diese Instanz stattdessen schreibt. Beim nächsten Speichern verschwinden sie aus der Datei.

webServerPort bleibt vorerst stehen. Es entfällt erst mit Stufe 4, wenn der Manager den Port übernimmt — nicht schon, sobald Kestrel den Socket hat. Eine frühere Fassung dieses Absatzes sagte „mit Stufe 2"; das war falsch gerechnet: Solange es keinen Manager gibt, der die Weboberfläche trägt, hieße Wegnehmen, sie ersatzlos abzuschalten.


10 — Was das für Vorhandenes bedeutet

„Kern als Dienst, Oberfläche als Client" aus dem Roadmap-Rückstau wird überholt: Dort ging es um einen Kern und eine Oberfläche. Der dort notierte Auslöser — „lohnt sich, sobald der erste UI-Absturz eine Nacht Agentenarbeit kostet" — ist mit 1.1 eingetreten.

#214 (24/7-Betriebsform) sagt heute „Kein kopfloser Betrieb: Die Oberfläche läuft laut Zielbetrieb immer mit". Das gilt nach E-M2 in anderer Form: Der Manager läuft immer mit, die Instanzfenster laufen mit, aber eine Instanz kann ohne Fenster laufen.

#218 (SDK 2.6.0 → aktuell) wird Voraussetzung — siehe Abschnitt 5.

9.2 Was Stufe 2 gebracht hat

Kestrel hört auf dem Socket — <Instanz>/manager.sock unter Linux, unter Windows eine Named Pipe, deren Name aus einem Fingerabdruck des Instanzpfades entsteht (InstanceEndpoint). Zwei Instanzen bekommen zwangsläufig zwei Adressen, ohne dass jemand etwas vergeben müsste.

Der Socket ist immer da, auch ohne Weboberfläche. Er ist nicht Teil von #494, sondern die Steuerfläche der Instanz: webUiEnabled schaltet den TCP-Zugang, nicht den Socket. Was aus WebUiHost wurde, heißt deshalb jetzt InstanceHttpHost — ein Kestrel, zwei Zugänge.

Der Torwächter unterscheidet die Zugänge. Über TCP gilt Token-Pflicht, über den Socket nicht: Dort steht die Grenze am Dateisystem (0600 beziehungsweise CurrentUserOnly), und wer die Socketdatei öffnen darf, sieht ohnehin in das Verzeichnis, in dem alles liegt, was ein Token schützen würde. Woran die beiden auseinandergehalten werden, ist der Punkt, an dem ein Irrtum teuer wäre: Bei Socket-Verbindungen stehen in HttpContext.Connection keine IP-Adressen — daraus zu schließen „keine IP, also Socket" wäre eine Vermutung an der Stelle, an der ein Torwächter steht, und ließe im Zweifel eine TCP-Anfrage ohne Token durch. Der Socket-Zuhörer hängt deshalb jeder Verbindung selbst eine Marke an (LocalTransport); wo sie fehlt, gilt Token-Pflicht.

Ein belegter Port kostet nur noch die Weboberfläche. Vorher gab TryStart in dem Fall null zurück und die ganze HTTP-Fläche war weg. Das ist seit dem Socket die falsche Rangfolge: Ihn zu verlieren, weil eine zweite Instanz schon läuft, hieße die Instanz unerreichbar zu machen — in genau dem Fall, für den es den Manager gibt.

Kopfloser Betrieb über --headless --instance <Pfad>. Die Verzweigung sitzt in Program.Main vor BuildAvaloniaApp, damit auf einem Server ohne Bildschirmsitzung kein Fenstersystem angefasst wird. Gefragt wird nichts: Instanz aus dem Argument, Lizenz über NonInteractiveLicensePrompt, Telegram-Anmeldung aus.

Nachgemessen, nicht angenommen: Eine kopflose Instanz auf Instance-Test lief ohne Fenster, und eine rohe HTTP-Anfrage aus einem fremden Prozess (PowerShell, direkt auf die Pipe) bekam 200 OK auf /api/info — ohne Token.

Zwei Lücken, die offen bleiben. Die Socketdatei-Fälle (liegengebliebene Leiche, Aufräumen beim Beenden) sind unter Windows gegenstandslos und werden dort mit Begründung übersprungen — der Unix-Domain-Socket selbst ist auf der Entwicklungsmaschine also ungeprüft, obwohl er die Zielplattform ist. Und das geordnete Ende auf SIGTERM ließ sich hier nicht auslösen: Windows kennt das Signal für einen fensterlosen Prozess nicht. Beides gehört auf die Linux-Testmaschine.