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
11 changes: 7 additions & 4 deletions cmd/engram/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -812,14 +812,17 @@ func tryStartAutosync(ctx context.Context, s *store.Store, cfg store.Config) (au
token := strings.TrimSpace(cc.Token)
serverURL := strings.TrimSpace(cc.ServerURL)

// REQ-211: token required.
// REQ-211: token required. The token is resolved from cloud.json first and
// overridden by ENGRAM_CLOUD_TOKEN when set, so both sources are tried.
// On Windows (Task Scheduler), the env var is often absent — the file path
// is the expected source (issue #421).
if token == "" {
log.Printf("[autosync] ERROR: ENGRAM_CLOUD_TOKEN is required when ENGRAM_CLOUD_AUTOSYNC=1; autosync disabled")
log.Printf("[autosync] ERROR: cloud token is not configured (set ENGRAM_CLOUD_TOKEN or store token in cloud.json via `engram cloud config`); autosync disabled")
return nil, nil
}
// REQ-211: server URL required.
// REQ-211: server URL required. Resolved from cloud.json or ENGRAM_CLOUD_SERVER.
if serverURL == "" {
log.Printf("[autosync] ERROR: ENGRAM_CLOUD_SERVER is required when ENGRAM_CLOUD_AUTOSYNC=1; autosync disabled")
log.Printf("[autosync] ERROR: cloud server URL is not configured (set ENGRAM_CLOUD_SERVER or run `engram cloud config --server <url>`); autosync disabled")
return nil, nil
}

Expand Down
55 changes: 55 additions & 0 deletions cmd/engram/sync_cloud_auth_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package main

import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/Gentleman-Programming/engram/internal/cloud/autosync"
"github.com/Gentleman-Programming/engram/internal/store"
)

Expand Down Expand Up @@ -119,3 +121,56 @@ func TestSyncCloudSendsAuthorizationHeaderFromFileToken(t *testing.T) {
t.Fatalf("expected Authorization header %q, got %q (file token not forwarded)", wantAuth, gotAuth)
}
}

// TestTryStartAutosyncUsesFileToken asserts that tryStartAutosync picks up the
// cloud token from cloud.json when ENGRAM_CLOUD_TOKEN env var is absent (issue #421).
// This is the Windows Task Scheduler scenario: the background process runs in a
// separate session context without the env var, so the token must come from the
// persisted config file.
func TestTryStartAutosyncUsesFileToken(t *testing.T) {
cfg := testConfig(t)
t.Setenv("ENGRAM_CLOUD_AUTOSYNC", "1")
t.Setenv("ENGRAM_CLOUD_TOKEN", "") // env var absent — must fall back to file
t.Setenv("ENGRAM_CLOUD_SERVER", "") // env var absent — server from file too

const fileToken = "file-only-token-421"
if err := saveCloudConfig(cfg, &cloudConfig{
ServerURL: "http://127.0.0.1:19998",
Token: fileToken,
}); err != nil {
t.Fatalf("save cloud config: %v", err)
}

s, err := store.New(cfg)
if err != nil {
t.Fatalf("store.New: %v", err)
}
defer s.Close()

// Track whether the manager factory was reached.
managerCreated := false
old := newAutosyncManager
newAutosyncManager = func(_ *store.Store, _ autosync.CloudTransport, _ autosync.Config) startableAutosyncManager {
managerCreated = true
return &fakeStartableManager{}
}
defer func() { newAutosyncManager = old }()

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

mgr, stopFn := tryStartAutosync(ctx, s, cfg)

// If the file-token fallback is missing, tryStartAutosync returns (nil, nil)
// because cc.Token is empty after resolveCloudRuntimeConfig ignores the file.
if mgr == nil {
t.Fatal("tryStartAutosync returned nil manager when token is only in cloud.json — file token fallback not working for autosync startup (issue #421)")
}
if stopFn == nil {
t.Fatal("expected non-nil stop function when manager starts successfully")
}
if !managerCreated {
t.Fatal("newAutosyncManager factory was never reached — tryStartAutosync aborted before creating manager")
}
stopFn()
}
7 changes: 7 additions & 0 deletions plugin/claude-code/scripts/user-prompt-submit.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
# fork emulation is slowed or blocked by Defender/EDR. Keep this script small
# and dependency-free; it must never block prompt submission.

# Ensure UTF-8 output so JSON payloads with non-ASCII characters are not
# mangled when Claude Code reads this hook's stdout. Without this, Windows
# defaults to the system codepage (e.g. CP1252/CP850) which corrupts
# multi-byte characters in the systemMessage JSON (issue #421).
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8

$ErrorActionPreference = 'SilentlyContinue'

function Write-EmptyHookResponse {
Expand Down