π Pre-flight Checks
π Bug Description
The engram Claude Code plugin starts the daemon from its SessionStart hook, but that daemon can never perform cloud autosync β and it fails completely silently.
Two independent causes combine:
1. The hook starts the daemon with no cloud environment.
plugin/claude-code/scripts/session-start.sh:26-30:
# Ensure engram server is running
if ! curl -sf "${ENGRAM_URL}/health" --max-time 1 > /dev/null 2>&1; then
engram serve &>/dev/null &
sleep 0.5
fi
The only thing sourced is _helpers.sh (line 17), which carries no cloud environment logic. The daemon therefore inherits the agent's environment, which does not contain ENGRAM_CLOUD_TOKEN or ENGRAM_CLOUD_AUTOSYNC. plugin/codex/scripts/session-start.sh:28 is identical.
2. There is no file-based way to enable autosync at all.
cmd/engram/main.go:804 gates autosync exclusively on an environment variable:
if strings.TrimSpace(os.Getenv("ENGRAM_CLOUD_AUTOSYNC")) != "1" { return nil, nil }
cmd/engram/cloud.go:204-207 β cloudConfig persists only server_url and token:
type cloudConfig struct {
ServerURL string `json:"server_url"`
Token string `json:"token"`
}
So even with a valid cloud.json, a hook-launched daemon can never enable autosync. The documented setup in docs/AGENT-SETUP.md:861-867 assumes an interactive shell (export ENGRAM_CLOUD_AUTOSYNC=1 β¦; engram serve), which is not how the plugin starts it.
3. The failure is invisible.
&>/dev/null on line 28 discards both [autosync] ERROR: cloud token is not configured (main.go:822) and [autosync] started (server=β¦) (main.go:845). Nothing surfaces anywhere. Related: #341.
π Steps to Reproduce
- Configure cloud sync normally: valid
~/.engram/cloud.json (server + token), enrolled projects, reachable cloud server.
- Put
ENGRAM_CLOUD_SERVER / ENGRAM_CLOUD_TOKEN / ENGRAM_CLOUD_AUTOSYNC=1 in a shell env file, as the docs suggest for interactive use.
- Start Claude Code so the plugin's
SessionStart hook launches the daemon.
- Save memories normally for a few days.
- Inspect the daemon's environment:
tr '\0' '\n' < /proc/$(pgrep -f 'engram serve')/environ | grep ENGRAM β no ENGRAM_* variables at all.
curl -s "http://127.0.0.1:7437/sync/status?project=<P>"
β
Expected Behavior
The daemon started by the plugin hook should be able to autosync when cloud is configured β either by loading the cloud configuration from a file, or by the hook passing the environment through. A misconfiguration should be visible somewhere, not swallowed.
β Actual Behavior
last_sync_at stays null indefinitely on every project. phase never leaves pending/idle. No error is written anywhere the user can see.
Observed on a real installation: cloud sync silently did nothing for 27 days. Roughly 84% of 2,859 observations across 28 projects had never reached the server. The only data that ever arrived came from one manual engram sync --cloud --project <P> batch run months earlier from an interactive shell with the env sourced.
Note that mem_doctor reports status: ok throughout β its sync_mutation_required_fields check validates mutation schema, not whether the queue drains, so it does not catch this.
Operating System
Linux (Ubuntu/Debian)
Engram Version
1.20.0 (plugin plugin/claude-code/.claude-plugin/plugin.json β 0.1.1)
Agent / Client
Claude Code
π Relevant Logs
$ curl -s "http://127.0.0.1:7437/sync/status?project=<redacted>"
{
"enabled": false,
"last_sync_at": null,
"phase": "idle",
"consecutive_failures": 0,
"last_error": "",
"reason_code": "blocked_unenrolled"
}
$ tr '\0' '\n' < /proc/<daemon-pid>/environ | grep '^ENGRAM'
(no output)
π‘ Additional Context
Two possible fixes, both reasonable β I did not want to presume which direction the project prefers, so I am not opening a PR yet:
Option A β hook loads an optional env file. Have session-start.sh source ${ENGRAM_CONFIG_DIR:-$HOME/.config/engram}/cloud.env if it exists before launching the daemon. Smallest change, no Go code touched, but it introduces a new file convention the repo does not currently define.
Option B β persist the toggle in cloud.json. Add an autosync field to cloudConfig (cmd/engram/cloud.go:204-207) so main.go:804 can honour it without an environment variable. Configuration then lives where the server URL and token already live.
Either way, not discarding the daemon's stderr on line 28 would have surfaced this on day one (see #341).
Worth flagging for whichever direction is chosen: #464 (token stored in cleartext in agent configs) means Option A should probably not encourage putting the token anywhere new.
I am happy to implement whichever option a maintainer prefers, once this gets status:approved.
Related prior art: #326 and #328 covered autosync not starting under engram mcp, but not the daemon launched by the plugin hook. #279 covered the daemon dying on upgrade β same silent-failure class, different cause.
π Pre-flight Checks
status:approvedbefore a PR can be openedπ Bug Description
The engram Claude Code plugin starts the daemon from its
SessionStarthook, but that daemon can never perform cloud autosync β and it fails completely silently.Two independent causes combine:
1. The hook starts the daemon with no cloud environment.
plugin/claude-code/scripts/session-start.sh:26-30:The only thing sourced is
_helpers.sh(line 17), which carries no cloud environment logic. The daemon therefore inherits the agent's environment, which does not containENGRAM_CLOUD_TOKENorENGRAM_CLOUD_AUTOSYNC.plugin/codex/scripts/session-start.sh:28is identical.2. There is no file-based way to enable autosync at all.
cmd/engram/main.go:804gates autosync exclusively on an environment variable:cmd/engram/cloud.go:204-207βcloudConfigpersists onlyserver_urlandtoken:So even with a valid
cloud.json, a hook-launched daemon can never enable autosync. The documented setup indocs/AGENT-SETUP.md:861-867assumes an interactive shell (export ENGRAM_CLOUD_AUTOSYNC=1 β¦; engram serve), which is not how the plugin starts it.3. The failure is invisible.
&>/dev/nullon line 28 discards both[autosync] ERROR: cloud token is not configured(main.go:822) and[autosync] started (server=β¦)(main.go:845). Nothing surfaces anywhere. Related: #341.π Steps to Reproduce
~/.engram/cloud.json(server + token), enrolled projects, reachable cloud server.ENGRAM_CLOUD_SERVER/ENGRAM_CLOUD_TOKEN/ENGRAM_CLOUD_AUTOSYNC=1in a shell env file, as the docs suggest for interactive use.SessionStarthook launches the daemon.tr '\0' '\n' < /proc/$(pgrep -f 'engram serve')/environ | grep ENGRAMβ noENGRAM_*variables at all.curl -s "http://127.0.0.1:7437/sync/status?project=<P>"β Expected Behavior
The daemon started by the plugin hook should be able to autosync when cloud is configured β either by loading the cloud configuration from a file, or by the hook passing the environment through. A misconfiguration should be visible somewhere, not swallowed.
β Actual Behavior
last_sync_atstaysnullindefinitely on every project.phasenever leavespending/idle. No error is written anywhere the user can see.Observed on a real installation: cloud sync silently did nothing for 27 days. Roughly 84% of 2,859 observations across 28 projects had never reached the server. The only data that ever arrived came from one manual
engram sync --cloud --project <P>batch run months earlier from an interactive shell with the env sourced.Note that
mem_doctorreportsstatus: okthroughout β itssync_mutation_required_fieldscheck validates mutation schema, not whether the queue drains, so it does not catch this.Operating System
Linux (Ubuntu/Debian)
Engram Version
1.20.0 (plugin
plugin/claude-code/.claude-plugin/plugin.jsonβ 0.1.1)Agent / Client
Claude Code
π Relevant Logs
π‘ Additional Context
Two possible fixes, both reasonable β I did not want to presume which direction the project prefers, so I am not opening a PR yet:
Option A β hook loads an optional env file. Have
session-start.shsource${ENGRAM_CONFIG_DIR:-$HOME/.config/engram}/cloud.envif it exists before launching the daemon. Smallest change, no Go code touched, but it introduces a new file convention the repo does not currently define.Option B β persist the toggle in
cloud.json. Add anautosyncfield tocloudConfig(cmd/engram/cloud.go:204-207) somain.go:804can honour it without an environment variable. Configuration then lives where the server URL and token already live.Either way, not discarding the daemon's stderr on line 28 would have surfaced this on day one (see #341).
Worth flagging for whichever direction is chosen: #464 (token stored in cleartext in agent configs) means Option A should probably not encourage putting the token anywhere new.
I am happy to implement whichever option a maintainer prefers, once this gets
status:approved.Related prior art: #326 and #328 covered autosync not starting under
engram mcp, but not the daemon launched by the plugin hook. #279 covered the daemon dying on upgrade β same silent-failure class, different cause.