26 Commits

Author SHA1 Message Date
cg33
1c92a03fdc feat(daemon): add CC_LOG_MAX_BACKUPS env var support (#1260)
* feat(daemon): add CC_LOG_MAX_BACKUPS env var support (#1222)

PR #1243 only addressed CC_LOG_MAX_SIZE while leaving the backup
count hard-wired to one (.log.1). That still loses any post-mortem
context older than one rotation, which is the same class of failure
users reported on #1222. This change adds the matching knob so the
post-mortem trail is configurable, with the same flag > env >
default priority used for size.

- daemon: add ParseLogBackups(s) (>=1, no unit suffix, error echoes
  input) and DefaultLogMaxBackups = 3.
- daemon: extend RotatingWriter with maxBackups; rotateLocked walks
  the chain (delete .N, shift .(N-1) -> .N .. .1 -> .2, rename
  active -> .1, reopen) and a public Rotate() hook for tests/SIGHUP.
- daemon: Config/Meta gain LogMaxBackups; Resolve() defaults to 3.
- cmd/cc-connect: resolveLogMaxBackups + preScanLogMaxBackupsFlag +
  --log-max-backups flag; startup log now reports max_backups and
  its source. The rotating-writer setup happens before flag.Parse so
  the pre-scan keeps the flag effective there too.
- daemon/launchd.go, daemon/systemd.go, daemon/windows.go: thread
  CC_LOG_MAX_BACKUPS through the service templates so a fresh
  install picks it up.
- tests: TestParseLogBackups (19 subtests + error-echo), three new
  RotatingWriter tests (chain, disabled, fallback), four resolver
  tests + pre-scan tests in cmd/cc-connect. TestIssue1222_BackupRetention
  pins the new env-var behaviour as the regression test for the
  follow-up to #1222.

* fix(daemon): silence errcheck on logrotate_test.go defer Close

QA review (run 27109765660) flagged defer w.Close() in the 4
backup-related tests added by #1260. Wrap each in
defer func() { _ = w.Close() }() so errcheck is satisfied without
changing test semantics (temp-dir cleanup is best-effort).

Verified locally:
- golangci-lint --new-from-rev origin/main ./daemon/... -> 0 issues
- go test -count=1 -tags no_web ./daemon/ ./cmd/cc-connect/ -> ok

---------

Co-authored-by: cc-connect dev-claudecode <dev-claudecode@cc-connect.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-06-16 09:59:09 +08:00
Claude
411687b675 fix(daemon): remove duplicate CheckLinger stub blocking non-linux build
PR #1034 (c53f5450) re-introduced daemon/linger_other.go with
`//go:build !linux`, which overlaps every per-platform stub:
- launchd.go (darwin)
- windows.go (windows)
- unsupported.go (other)

This causes `CheckLinger redeclared in this block` and breaks
`make release-all` on every non-Linux GOOS target.

Per-platform stubs already cover all four cases:
- linux   → daemon/systemd.go
- darwin  → daemon/launchd.go
- windows → daemon/windows.go
- other   → daemon/unsupported.go

Delete the redundant `!linux` stub. Same fix as the never-merged
PR #1311; no runtime behavior change.

Verified:
- GOOS=linux   go build ./daemon/... → ok
- GOOS=darwin  go build ./daemon/... → ok
- GOOS=windows go build ./daemon/... → ok
- GOOS=freebsd go build ./daemon/... → ok

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 10:34:17 +08:00
Shuchao Shao
c53f5450ee feat(daemon): harden service-file env capture + add EnvDiscoverer plugin hook (#1034)
* feat(daemon): harden service-file writes and capture config.toml ${ENV}

Three independent improvements to the service-file install path so
operators can store API keys / tokens in config.toml using ${ENV}
placeholders and have them work with the installed daemon, without
those values being world-readable on disk.

1. captureConfigEnvPlaceholders: during `daemon install`, scan the
   target config.toml for ${VAR_NAME} placeholders. For every match
   that is set in the current process environment, copy the value
   into the EnvExtra map so the rendered launchd plist / systemd unit
   / Windows task script carries it. Without this, the daemon
   process starts with empty strings for placeholder values and
   fails to authenticate to any platform.

2. Tighten service-file permissions to 0600. Both the WriteFile mode
   and an explicit os.Chmod after write — the Chmod is required for
   the reinstall path because WriteFile only applies perm on create,
   so a 0644 file left by a previous cc-connect version would keep
   its old permissions in place. Applies to launchd plist, systemd
   unit, and Windows .ps1 script (the latter as a defense-in-depth
   layer; real Windows access control lives in the ACL).

3. EnvExtra hardening in every renderer:
   - drop entries whose key fails POSIX-identifier validation
   - drop entries whose value is empty
   - launchd: XML-escape both keys and values; reserve PATH /
     CC_LOG_FILE / CC_LOG_MAX_SIZE so EnvExtra can't override the
     template-owned keys
   - systemd: backslash- and quote-escape values for the
     `Environment="K=V"` form per systemd.exec(5)

Add --no-capture-secrets / CC_DAEMON_NO_CAPTURE_SECRETS=1 opt-out for
operators who'd rather inject secrets via keychain, secret-tool, or
systemd EnvironmentFile= and keep the service file token-free.

runSystemctl becomes a var so per-platform tests can stub it without
needing a real systemctl on the host.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(daemon): add EnvDiscoverer plugin hook for install-time env capture

Generalises captureDaemonEnv() with a small plugin registry:

    type EnvDiscoverer func() (map[string]string, error)
    daemon.RegisterEnvDiscoverer(d)
    daemon.ResetEnvDiscoverers()    // tests only

Resolve() invokes every registered discoverer (unless NoCaptureSecrets
is set) and merges the result into EnvExtra after the proxy-key capture
and config.toml ${ENV} placeholder scan. Discoverers run in
registration order; the returned map is filtered for valid POSIX env
names and non-empty values before reaching any renderer.

Discoverer errors are logged at WARN level and never fail install —
matching the rest of the install path's tolerance posture.

Use case: lets a downstream / plugin agent contribute extra env vars
the service file should carry, without daemon needing to know about
that agent. Without the hook, the only ways to extend capture are (a)
pre-populate EnvExtra in the caller — verbose at every install site —
or (b) edit daemon/manager.go to import the new package — which couples
daemon to that package and breaks the no-agent-deps boundary.

The isValidEnvName helper moves out of manager.go into the new
env_extension.go alongside the registry, since the validator is shared
between the renderers and the discoverer-merge code.

Comment tweaks in launchd.go / systemd.go acknowledge that discoverer
output is another source of captured secrets justifying the 0600 perms.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(daemon): add non-linux linger check stub

---------

Co-authored-by: Shuchao Shao <shaoshch@yonyou.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 00:14:23 +08:00
cg33
9957e4bfbe fix(core): honor CC_LOG_MAX_SIZE env var in v1.3.3-beta.4 (#1243)
In v1.3.3-beta.4 the rotating-writer setup at startup called
strconv.ParseInt on CC_LOG_MAX_SIZE, so any value carrying a unit suffix
("10MB", "512K", "1G") silently failed and the env var was ignored. The
default 10MB was used, the user got no warning, and unit-suffix
configurations appeared to take effect but did not.

Introduce daemon.ParseLogSize, which accepts raw bytes plus
case-insensitive K/KB/M/MB/G/GB/T/TB/B suffixes with binary (1024-based)
multipliers, and a resolveLogMaxSize helper that applies the priority
order: --log-max-size flag > CC_LOG_MAX_SIZE env var > built-in default.
A new --log-max-size flag exposes the same parser on the command line.
A startup line ("log: redirecting to ... max_size=... bytes (source:
flag|env|default)") makes the active value auditable without grepping
systemd/launchd definitions. Invalid values are warned to stderr and
skipped, never silently downgraded.

New unit tests cover the parser, the priority order, the pre-scan
helper, and pin a regression for the exact "10MB" scenario in
issue #1222.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 15:22:46 +08:00
Claude
d577db282b fix(daemon): remove duplicate CheckLinger declaration in launchd.go
Two PRs both added CheckLinger to launchd.go, causing a redeclaration
compile error on darwin. Remove the duplicate introduced in the second PR.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-28 00:23:09 +08:00
Claude
9f0466d832 fix: 微信 ilink QR 码主动刷新,防止扫码时过期 (#1103)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 23:33:30 +08:00
cg33
0f45c7df88 Merge pull request #1110 from woaillr-crypto/fix/opencode-yolo-skip-permissions
fix(opencode): skip permissions in yolo mode + macOS CheckLinger stub
2026-05-27 23:29:57 +08:00
Claude
78370812d1 fix(daemon): CheckLinger on macOS should return true (launchd is always persistent)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 23:29:16 +08:00
Alloyee
9b11de3d13 fix(opencode): skip permissions in yolo mode + add macOS CheckLinger stub
1. Add --dangerously-skip-permissions flag when opencode agent is in yolo
   mode. Without this, headless runs auto-reject external-directory
   permission requests (e.g. superpowers /init trying to modify
   ~/.config/opencode/), resulting in empty responses delivered to users.

2. Add CheckLinger() stub to daemon/launchd.go for macOS. This function
   is only referenced from daemon.go but was only defined in
   systemd.go (Linux-only build tag), causing build failure on darwin.
2026-05-24 17:17:36 +08:00
Claude
8e04d66bf6 build: release v1.3.3-beta.3 — fix Windows cross-compile (CheckLinger stub)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 09:49:54 +08:00
kare
9b4e9db27c fix: proactive QR refresh for weixin ilink setup to prevent expiry during scan
微信 ilink QR 码有效期仅约 120 秒,原有实现被动等待服务端返回 expired
状态后才刷新,导致用户扫码时 QR 码经常已过期。

改动:
- 追踪 QR 码生成时间,在预估过期前 30 秒主动刷新(weixinQRProactiveRefreshAt = 80s)
- QR 码刷新时同步更新 --qr-image 图片文件,保持本地文件始终有效
- 新增 daemon/launchd.go CheckLinger() 修复 macOS 编译问题

Closes #1102
2026-05-24 00:13:10 +08:00
cg33
3a9e64ed31 feat(daemon): warn about systemd linger on daemon install (#965)
When cc-connect is installed as a user-level systemd service (non-root),
the service stops when the user's last login session ends (SSH disconnect)
because systemd's default Linger=no behavior.

Changes:
- Add CheckLinger function in daemon/systemd.go to check linger status
- Add warning in daemon install output when linger is disabled
- Document linger setting in INSTALL.md

The warning appears after successful installation:
  ⚠️ Warning: Linger is not enabled for this user.
  cc-connect will stop when your last login session ends (e.g., SSH disconnect).
  To keep it running persistently, run:
    sudo loginctl enable-linger $USER

Fixes #960

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-18 22:08:37 +08:00
Shawn
024e52e9af fix(daemon/launchd): xml-escape user-supplied paths in the plist template (#995)
buildPlist embeds cfg.BinaryPath, cfg.WorkDir, cfg.LogFile, and cfg.EnvPATH
into the launchd .plist via fmt.Sprintf("%s"), without escaping XML-reserved
characters. If any of those values contains '&', '<', '>', '"', or '\'',
the resulting plist is malformed XML and `launchctl bootstrap` rejects it
with a parse error, so `cc-connect daemon install` fails before the daemon
is ever registered.

The Windows daemon manager already escapes via powerShellLiteral; launchd
was the inconsistent path. Pass each user-supplied value through
xml.EscapeText. Label is a hard-coded constant and LogMaxSize is an int,
so neither needs escaping.

Concrete triggers: a workdir under `dev & test`, an iCloud-style path with
spaces and tildes already handled by the path encoder but containing '&'
or quotes, or a home directory containing an apostrophe.
2026-05-18 21:38:43 +08:00
Shawn
519ea9415c fix(daemon/launchd): set SuccessfulExit=false so crashes restart and clean stop does not (#909)
PR #304 wired the LaunchAgent KeepAlive dict with SuccessfulExit=true,
intending (per its commit message) to "match systemd Restart=on-failure
behavior — only unclean exits are restarted". The semantics are
inverted: per launchd.plist(5),

    SuccessfulExit <boolean>
        If true, the job will be restarted as long as the program
        exits with a successful exit status. If false, the job will
        be restarted in the inverse condition.

so SuccessfulExit=true means "restart ONLY after a successful (exit 0)
exit", not the other way around. The shipped plist therefore had two
backwards behaviours:

  1. Graceful SIGTERM shutdown — cmd/cc-connect/main.go falls through
     to a clean return from main(), so the Go runtime exits 0. With
     SuccessfulExit=true, launchd respawns. That is exactly the
     symptom #153 was supposed to fix.

  2. Crash / panic / SIGKILL — process exits non-zero. With
     SuccessfulExit=true, launchd does NOT restart, so the LaunchAgent
     silently goes away on the first crash and never comes back until
     the user re-bootstraps it.

Flip the value to <false/> so it actually matches the stated intent
(and systemd's Restart=on-failure semantics): launchd restarts only
on non-zero exit; clean SIGTERM does not respawn.

The existing TestBuildPlist_KeepAliveDoesNotRestartOnCleanExit test
only checked that the SuccessfulExit *key* was present, not its
value, so it passed with either polarity. Tighten it to assert the
exact <false/> value with a comment explaining the inversion, so a
future edit can't silently flip it back.
2026-05-18 10:13:13 +08:00
yahaa
d8efe17ab2 Windows supports daemon install (#817)
* support windows daemon install

* hide windows daemon launcher window

* use scheduledtasks for windows daemon state

* stop windows task before reinstall

* wait for windows task stop before reinstall

* reuse existing windows task when protected

* register windows task for current user

* invoke schtasks executable explicitly

* address copilot windows task comments

* make windows task powershell strict
2026-05-05 09:56:51 +08:00
xxb
e1b0319b1e fix(daemon): support launchd gui and background sessions (#716) 2026-04-27 14:29:20 +08:00
Claude
bd9dc515f4 fix: address high/medium priority issues from PR review (batch 3)
H1: Prevent workspace path traversal via ../
    Add resolveLocalDirPath() that validates resolved path stays under
    baseDir, expands ~/... via os.UserHomeDir, rejects escaping paths.

M2: Sanitize sender_name in inject_sender header
    Replace quotes with single quotes, strip newlines/CR to prevent
    prompt injection via display names.

M3: Warn on unset env var placeholders in config
    resolveEnvPlaceholders now logs slog.Warn when ${VAR} references
    an environment variable that is not set.

M4: Validate WeCom api_base_url
    Reject non-http(s) or malformed URLs at New() to prevent credential
    exfiltration via misconfigured base URL.

M5: Quote systemd Environment= values
    Wrap values in double quotes to handle spaces, #, and special chars
    in proxy URLs and paths.

M7: Discord table detection now requires separator row
    Only wrap pipe-delimited blocks that contain a markdown separator
    row (| --- | --- |), reducing false positives on decorative pipes.
Made-with: Cursor
2026-04-16 09:21:18 +08:00
zkunzhu
421bd9f05c fix(daemon): preserve proxy env in systemd service 2026-04-15 11:04:48 +08:00
Claude
160acc241f fix: satisfy staticcheck ST1005 for error string formatting
Lowercase fmt.Errorf prefixes in daemon/update CLI paths and collapse
systemd manager error to a single line without trailing punctuation.
</think>

mcp_GitKraken_git_push
2026-03-28 08:37:19 +08:00
cg33
a456ab3bd4 fix(daemon): macOS launchd do not respawn on clean exit (#153) (#304)
LaunchAgent used KeepAlive=true, so launchd restarted cc-connect after
every process exit, including graceful SIGTERM shutdown. That looked like
"stop then immediate restart" and could make tooling that waits for the
Unix API socket to disappear hang if the service respawned quickly.

Use KeepAlive with SuccessfulExit=true so only unclean exits are restarted,
matching systemd Restart=on-failure behavior.

Existing installs: re-run `cc-connect daemon install --force` to refresh
the plist.

Fixes #153

Made-with: Cursor

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-24 14:15:51 +08:00
q107580018
8a75a00df5 feat(telegram): add async startup recovery
* docs: add telegram async recovery design

* feat(telegram): add async startup recovery

* fix(telegram): resolve lint nits

* fix(core): resolve errcheck lint issues

* chore: fix launchd lint issues

* test(telegram): wrap bot api fixtures for interface
2026-03-22 12:03:30 +08:00
Claude
b9af7ea7ba fix: handle errors and add error logging throughout codebase
The changes add error handling for previously ignored operations and introduce logging for critical errors and warnings. This includes:
- Adding error checks for file operations, JSON unmarshaling, system calls
- Logging warnings/errors for failed operations using slog
- Properly handling return values from functions that were previously ignored
- Adding context to error messages for better debugging
- Cleaning up unused code and variables

generated by llmgit

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-20 00:31:00 +08:00
kevinWangSheng
f108656e91 fix(daemon): prevent nil pointer panic in RotatingWriter after failed rotation
When rotate() fails to open the new log file, the original code set
w.file to nil (via the failed retry that returned nil). Subsequent
Write() calls would dereference w.file and panic.

Fix:
- Add nil check in Write() to return os.ErrClosed gracefully
- Remove the useless retry in rotate() (identical call would also fail)
- Explicitly set w.file = nil on failure so the nil guard in Write() and
  Close() handles it correctly
2026-03-13 11:03:01 -07:00
kevinWangSheng
347b7c1b8c fix(daemon): retry launchd bootstrap after bootout to avoid race condition (closes #35) 2026-03-12 01:56:04 -07:00
chenhg5
34c1b7a0cc feat(daemon): support system-level systemd and improve error handling
- Add support for system-level systemd (/etc/systemd/system/) when running as root
- Add requireInstalled check before start/stop/restart operations
- Improve systemd detection and error messages for WSL2, containers, SSH
- Add isWSL2() helper for better platform detection
- Update daemon help text to clarify Linux root vs non-root behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:48:23 +08:00
chenhg5
9a291eb0c3 feat: add daemon mode for running as background service
Add support for running cc-connect as a daemon managed by OS init system:
- Linux: systemd user service
- macOS: launchd LaunchAgent

New CLI commands:
- cc-connect daemon install/uninstall
- cc-connect daemon start/stop/restart/status
- cc-connect daemon logs [-f] [-n N] [--log-file PATH]

Features:
- Auto log rotation with configurable max size
- Cross-platform support (systemd/launchd)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 00:18:55 +08:00