Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions backend/src/Taskdeck.Api/FirstRun/DesktopRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,20 @@ internal static void WriteRetiredProviderConfigurationIgnored()

internal static IReadOnlyList<string> FormatFatalStartup(Exception? exception)
{
if (exception is InvalidOperationException validationFailure
&& validationFailure.Message.StartsWith(
FirstRunBootstrapper.MissingConnectorEncryptionKeyMessagePrefix,
StringComparison.Ordinal))
{
return
[
"TASKDECK_DESKTOP_FATAL code=connector_encryption_key_missing",
"Taskdeck could not start because Connectors__EncryptionKey is missing. Set a stable " +
"base64-encoded 256-bit key (or reuse the existing key for this data) and restart. " +
"No settings were printed."
];
}

if (exception is RetiredLlmProviderConfigurationException)
{
return
Expand Down
9 changes: 8 additions & 1 deletion backend/src/Taskdeck.Api/FirstRun/FirstRunBootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ internal readonly record struct BootstrapIdentityLifecycle(
/// </summary>
public static class FirstRunBootstrapper
{
/// <summary>
/// Ordinal prefix of the Production missing-connector-key failure. <see cref="DesktopRuntime.FormatFatalStartup"/>
/// classifies on this exact prefix, so the throw site and the classifier must share it.
/// </summary>
internal const string MissingConnectorEncryptionKeyMessagePrefix =
"SECURITY: The Connectors:EncryptionKey is not configured.";

private const string LocalConfigFileName = "appsettings.local.json";

// Placeholder values that indicate "not configured".
Expand Down Expand Up @@ -648,7 +655,7 @@ public static WebApplicationBuilder ValidateProductionSecrets(
if (string.IsNullOrWhiteSpace(connectorKey))
{
throw new InvalidOperationException(
"SECURITY: The Connectors:EncryptionKey is not configured. " +
MissingConnectorEncryptionKeyMessagePrefix + " " +
"Generate a base64-encoded 256-bit key with 'openssl rand -base64 32' and set it via the " +
"Connectors__EncryptionKey environment variable. The application cannot start without a " +
$"real encryption key in Production. (If this used to run as a desktop install, an existing " +
Expand Down
20 changes: 20 additions & 0 deletions backend/tests/Taskdeck.Api.Tests/FirstRun/DesktopRuntimeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,26 @@ public void FormatFatalStartup_MapsTypedRetiredProviderFailureToStaticActionable
Assert.DoesNotContain(exception.Message, output);
}

[Fact]
public void FormatFatalStartup_MapsMissingConnectorKeyToStaticActionableOutput()
{
const string secretLikeContent = "synthetic-secret-never-print";
var exception = new InvalidOperationException(
FirstRunBootstrapper.MissingConnectorEncryptionKeyMessagePrefix + " " + secretLikeContent);

var output = DesktopRuntime.FormatFatalStartup(exception);

Assert.Equal(
[
"TASKDECK_DESKTOP_FATAL code=connector_encryption_key_missing",
"Taskdeck could not start because Connectors__EncryptionKey is missing. Set a stable " +
"base64-encoded 256-bit key (or reuse the existing key for this data) and restart. " +
"No settings were printed."
],
output);
Assert.All(output, line => Assert.DoesNotContain(secretLikeContent, line));
}

[Fact]
public void FormatFatalStartup_MapsUnrelatedExceptionToGenericOutputWithoutContentLeak()
{
Expand Down
5 changes: 5 additions & 0 deletions docs/releases/WINDOWS_QUICK_START.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ created inside Taskdeck, and authenticate local API/MCP clients; never put a `td
[UPGRADING.md](../../UPGRADING.md#version-notes). v0.1.2 and later print the accurate
diagnostic below instead.
- **From v0.3.0-rc.1 onward** — `TASKDECK_DESKTOP_WARNING code=retired_provider_configuration_ignored`: Taskdeck started normally after ignoring retired Gemini settings inherited from this machine's environment (environment variables only — a retired selector passed on the command line still fails closed); no retired value was kept, logged, or printed, and the provider actually in use is the one shown in Taskdeck's provider status. Clear those leftover variables with the commands below when convenient. **The published v0.2.0 archive does not have this behaviour**: there the same leftover variables produce the fatal below, and the commands are a required workaround rather than optional tidying.
- **Unreleased (`main` after 2026-09-05, the build after `v0.3.0-rc.1`)** `TASKDECK_DESKTOP_FATAL
code=connector_encryption_key_missing`: you started the packaged exe with `TASKDECK_HEADLESS` set (the
supply-your-own-key contract) and no `Connectors__EncryptionKey` was configured. Set a stable
base64-encoded 256-bit key, or reuse the key already stored for this data folder, and start again. The
message prints no settings. Earlier builds report this case as the generic `code=startup_failed`.
- `TASKDECK_DESKTOP_FATAL code=retired_provider_configuration`: Taskdeck found configuration for the
retired Gemini provider and refused to switch providers silently. **In v0.2.0 and earlier this
fires for retired Gemini settings from any source, including leftover `Llm__Gemini__*` variables
Expand Down
Loading