From 5eadf9dcdd5ed7d6093c2142bbeaf5a1db5d8b58 Mon Sep 17 00:00:00 2001 From: Grivn Date: Tue, 11 Aug 2026 10:19:34 +0800 Subject: [PATCH 1/3] fix: preserve Memory process compatibility Restore default SIGINT and SIGTERM behavior for ordinary Memory commands while keeping graceful signal ownership in the long-running Agency daemon. Keep Memory usage diagnostics on stderr and successful help on stdout. --- cmd/agency/serve.go | 6 +++++- cmd/root.go | 37 +++++++++++++++++++++++++------------ cmd/root_test.go | 17 +++++++++++++++-- main.go | 6 +----- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/cmd/agency/serve.go b/cmd/agency/serve.go index 72f50fb3..08265cf7 100644 --- a/cmd/agency/serve.go +++ b/cmd/agency/serve.go @@ -5,8 +5,10 @@ import ( "errors" "fmt" "os" + "os/signal" "path/filepath" "strings" + "syscall" "time" "github.com/mnemon-dev/mnemon/internal/daemon" @@ -37,7 +39,9 @@ func runServe(command *cobra.Command, _ []string) error { } resolved, err := resolveStateDirectory(stateDirectory) if err == nil { - err = serveDaemon(command.Context(), resolved) + lifetime, stop := signal.NotifyContext(command.Context(), os.Interrupt, syscall.SIGTERM) + defer stop() + err = serveDaemon(lifetime, resolved) } if err != nil { return commandFailure{code: 1, err: fmt.Errorf("mnemon agency serve: %w", err)} diff --git a/cmd/root.go b/cmd/root.go index 51728ed3..6f3bc1a2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,22 +13,25 @@ import ( var version = "dev" -// Execute runs one Mnemon command. Process signal handling and exit remain the -// root main package's responsibility. +// Execute runs one Mnemon command. Process exit remains the root main package's +// responsibility; a long-running command owns any graceful signal handling it +// requires so ordinary Memory commands retain the operating system defaults. func Execute(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) int { if ctx == nil || stdin == nil || stdout == nil || stderr == nil { return 1 } root := productRoot() + agencyRequest := false if command, _, findErr := root.Find(args); findErr == nil { - for current := command; current != nil; current = current.Parent() { - if current.Name() == "agency" { - root.SilenceErrors = true - root.SilenceUsage = true - break - } + agencyRequest = belongsToAgency(command) + if agencyRequest { + root.SilenceErrors = true } } + // Cobra renders automatic usage through the command output writer. Keep + // successful help and version output on stdout, but render Memory's usage + // explicitly to stderr after an execution error, as the established CLI did. + root.SilenceUsage = true root.SetArgs(args) root.SetIn(stdin) root.SetOut(stdout) @@ -37,20 +40,30 @@ func Execute(ctx context.Context, args []string, stdin io.Reader, stdout, stderr if err == nil { return 0 } + if !agencyRequest && !belongsToAgency(executed) && executed != nil { + _, _ = fmt.Fprintln(stderr, executed.UsageString()) + } if err.Error() != "" { _, _ = fmt.Fprintln(stderr, err) } if code, ok := agency.ExitCode(err); ok { return code } - for command := executed; command != nil; command = command.Parent() { - if command.Name() == "agency" { - return 2 - } + if agencyRequest || belongsToAgency(executed) { + return 2 } return 1 } +func belongsToAgency(command *cobra.Command) bool { + for current := command; current != nil; current = current.Parent() { + if current.Name() == "agency" { + return true + } + } + return false +} + func productRoot() *cobra.Command { root := memory.New(version) root.Short = "Memory and durable agency for LLM agents" diff --git a/cmd/root_test.go b/cmd/root_test.go index fc8fc35d..d951870a 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -54,9 +54,22 @@ func TestMemoryKeepsItsExistingCobraErrorOutput(t *testing.T) { var stdout, stderr bytes.Buffer exitCode := Execute(context.Background(), []string{"forget"}, strings.NewReader(""), &stdout, &stderr) - if exitCode != 1 || !strings.Contains(stderr.String(), "Error:") || - !strings.Contains(stdout.String(), "Usage:") { + if exitCode != 1 || stdout.Len() != 0 || + !strings.Contains(stderr.String(), "Error:") || + !strings.Contains(stderr.String(), "Usage:") || + !strings.Contains(stderr.String(), "\n\naccepts 1 arg(s), received 0\n") { t.Fatalf("memory usage error: exit=%d stdout=%q stderr=%q", exitCode, stdout.String(), stderr.String()) } } + +func TestMemoryHelpRemainsSuccessfulStdout(t *testing.T) { + var stdout, stderr bytes.Buffer + exitCode := Execute(context.Background(), []string{"forget", "--help"}, + strings.NewReader(""), &stdout, &stderr) + if exitCode != 0 || !strings.Contains(stdout.String(), "mnemon forget [id]") || + stderr.Len() != 0 { + t.Fatalf("memory help: exit=%d stdout=%q stderr=%q", + exitCode, stdout.String(), stderr.String()) + } +} diff --git a/main.go b/main.go index 16c87f7a..39c66446 100644 --- a/main.go +++ b/main.go @@ -3,16 +3,12 @@ package main import ( "context" "os" - "os/signal" - "syscall" "github.com/mnemon-dev/mnemon/cmd" ) func main() { - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) - exitCode := cmd.Execute(ctx, os.Args[1:], os.Stdin, os.Stdout, os.Stderr) - stop() + exitCode := cmd.Execute(context.Background(), os.Args[1:], os.Stdin, os.Stdout, os.Stderr) if exitCode != 0 { os.Exit(exitCode) } From afb1aca689ebea9ff502c8c37aa16f46f77eb9ee Mon Sep 17 00:00:00 2001 From: Grivn Date: Tue, 11 Aug 2026 10:19:50 +0800 Subject: [PATCH 2/3] fix: prepare Homebrew cask installation Remove macOS quarantine metadata from the unsigned CLI binary after Cask installation, following the GoReleaser migration guidance for prebuilt command-line artifacts. --- .goreleaser.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.goreleaser.yml b/.goreleaser.yml index c7a51398..5b545e4d 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -60,6 +60,12 @@ homebrew_casks: token: "{{ .Env.HOMEBREW_TAP_TOKEN }}" homepage: "https://github.com/mnemon-dev/mnemon" description: "Persistent memory and durable agency for LLM agents" + hooks: + post: + install: | + if OS.mac? + system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/mnemon"] + end release: github: From 7dd6affba47aedd2ef95cb1d8d408118f848ffd3 Mon Sep 17 00:00:00 2001 From: Grivn Date: Tue, 11 Aug 2026 10:22:08 +0800 Subject: [PATCH 3/3] fix: preserve short unknown-command diagnostics Only render the full Memory usage block for errors on commands Cobra resolved successfully. Keep root discovery failures byte-compatible with the established short help hint. --- cmd/root.go | 5 +++-- cmd/root_test.go | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 6f3bc1a2..cd4e22c3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -22,7 +22,8 @@ func Execute(ctx context.Context, args []string, stdin io.Reader, stdout, stderr } root := productRoot() agencyRequest := false - if command, _, findErr := root.Find(args); findErr == nil { + command, _, findErr := root.Find(args) + if findErr == nil { agencyRequest = belongsToAgency(command) if agencyRequest { root.SilenceErrors = true @@ -40,7 +41,7 @@ func Execute(ctx context.Context, args []string, stdin io.Reader, stdout, stderr if err == nil { return 0 } - if !agencyRequest && !belongsToAgency(executed) && executed != nil { + if findErr == nil && !agencyRequest && !belongsToAgency(executed) && executed != nil { _, _ = fmt.Fprintln(stderr, executed.UsageString()) } if err.Error() != "" { diff --git a/cmd/root_test.go b/cmd/root_test.go index d951870a..df5c4b57 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -73,3 +73,15 @@ func TestMemoryHelpRemainsSuccessfulStdout(t *testing.T) { exitCode, stdout.String(), stderr.String()) } } + +func TestUnknownRootCommandKeepsTheShortCobraDiagnostic(t *testing.T) { + var stdout, stderr bytes.Buffer + exitCode := Execute(context.Background(), []string{"unknown"}, + strings.NewReader(""), &stdout, &stderr) + if exitCode != 1 || stdout.Len() != 0 || + !strings.Contains(stderr.String(), "Run 'mnemon --help' for usage.") || + strings.Contains(stderr.String(), "Usage:\n") { + t.Fatalf("unknown command: exit=%d stdout=%q stderr=%q", + exitCode, stdout.String(), stderr.String()) + } +}