Skip to content

Commit 178a015

Browse files
fix(cloud): clarify autosync token error log and add UTF-8 PS1 output encoding (#421) (#454)
- Correct misleading log message in tryStartAutosync: token can come from cloud.json (file fallback via resolveCloudRuntimeConfig) or ENGRAM_CLOUD_TOKEN; the old message implied only the env var was checked, causing Windows users running under Task Scheduler to think file-based config was unsupported. - Add TestTryStartAutosyncUsesFileToken to lock the file-token fallback path for autosync startup — the existing behavior was correct but untested. - Set [Console]::OutputEncoding and InputEncoding to UTF-8 in the Windows PowerShell hook so non-ASCII characters in hook JSON payloads are not mangled by the default system codepage (CP1252/CP850).
1 parent b625d77 commit 178a015

3 files changed

Lines changed: 69 additions & 4 deletions

File tree

cmd/engram/main.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -812,14 +812,17 @@ func tryStartAutosync(ctx context.Context, s *store.Store, cfg store.Config) (au
812812
token := strings.TrimSpace(cc.Token)
813813
serverURL := strings.TrimSpace(cc.ServerURL)
814814

815-
// REQ-211: token required.
815+
// REQ-211: token required. The token is resolved from cloud.json first and
816+
// overridden by ENGRAM_CLOUD_TOKEN when set, so both sources are tried.
817+
// On Windows (Task Scheduler), the env var is often absent — the file path
818+
// is the expected source (issue #421).
816819
if token == "" {
817-
log.Printf("[autosync] ERROR: ENGRAM_CLOUD_TOKEN is required when ENGRAM_CLOUD_AUTOSYNC=1; autosync disabled")
820+
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")
818821
return nil, nil
819822
}
820-
// REQ-211: server URL required.
823+
// REQ-211: server URL required. Resolved from cloud.json or ENGRAM_CLOUD_SERVER.
821824
if serverURL == "" {
822-
log.Printf("[autosync] ERROR: ENGRAM_CLOUD_SERVER is required when ENGRAM_CLOUD_AUTOSYNC=1; autosync disabled")
825+
log.Printf("[autosync] ERROR: cloud server URL is not configured (set ENGRAM_CLOUD_SERVER or run `engram cloud config --server <url>`); autosync disabled")
823826
return nil, nil
824827
}
825828

cmd/engram/sync_cloud_auth_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package main
22

33
import (
4+
"context"
45
"net/http"
56
"net/http/httptest"
67
"strings"
78
"testing"
89

10+
"github.com/Gentleman-Programming/engram/internal/cloud/autosync"
911
"github.com/Gentleman-Programming/engram/internal/store"
1012
)
1113

@@ -119,3 +121,56 @@ func TestSyncCloudSendsAuthorizationHeaderFromFileToken(t *testing.T) {
119121
t.Fatalf("expected Authorization header %q, got %q (file token not forwarded)", wantAuth, gotAuth)
120122
}
121123
}
124+
125+
// TestTryStartAutosyncUsesFileToken asserts that tryStartAutosync picks up the
126+
// cloud token from cloud.json when ENGRAM_CLOUD_TOKEN env var is absent (issue #421).
127+
// This is the Windows Task Scheduler scenario: the background process runs in a
128+
// separate session context without the env var, so the token must come from the
129+
// persisted config file.
130+
func TestTryStartAutosyncUsesFileToken(t *testing.T) {
131+
cfg := testConfig(t)
132+
t.Setenv("ENGRAM_CLOUD_AUTOSYNC", "1")
133+
t.Setenv("ENGRAM_CLOUD_TOKEN", "") // env var absent — must fall back to file
134+
t.Setenv("ENGRAM_CLOUD_SERVER", "") // env var absent — server from file too
135+
136+
const fileToken = "file-only-token-421"
137+
if err := saveCloudConfig(cfg, &cloudConfig{
138+
ServerURL: "http://127.0.0.1:19998",
139+
Token: fileToken,
140+
}); err != nil {
141+
t.Fatalf("save cloud config: %v", err)
142+
}
143+
144+
s, err := store.New(cfg)
145+
if err != nil {
146+
t.Fatalf("store.New: %v", err)
147+
}
148+
defer s.Close()
149+
150+
// Track whether the manager factory was reached.
151+
managerCreated := false
152+
old := newAutosyncManager
153+
newAutosyncManager = func(_ *store.Store, _ autosync.CloudTransport, _ autosync.Config) startableAutosyncManager {
154+
managerCreated = true
155+
return &fakeStartableManager{}
156+
}
157+
defer func() { newAutosyncManager = old }()
158+
159+
ctx, cancel := context.WithCancel(context.Background())
160+
defer cancel()
161+
162+
mgr, stopFn := tryStartAutosync(ctx, s, cfg)
163+
164+
// If the file-token fallback is missing, tryStartAutosync returns (nil, nil)
165+
// because cc.Token is empty after resolveCloudRuntimeConfig ignores the file.
166+
if mgr == nil {
167+
t.Fatal("tryStartAutosync returned nil manager when token is only in cloud.json — file token fallback not working for autosync startup (issue #421)")
168+
}
169+
if stopFn == nil {
170+
t.Fatal("expected non-nil stop function when manager starts successfully")
171+
}
172+
if !managerCreated {
173+
t.Fatal("newAutosyncManager factory was never reached — tryStartAutosync aborted before creating manager")
174+
}
175+
stopFn()
176+
}

plugin/claude-code/scripts/user-prompt-submit.ps1

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
# fork emulation is slowed or blocked by Defender/EDR. Keep this script small
66
# and dependency-free; it must never block prompt submission.
77

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

1017
function Write-EmptyHookResponse {

0 commit comments

Comments
 (0)