Repository navigation
Conversation
Baseline tests for specs/PR-5.md against the interface contract exactly as written: DaemonStartupConfig.reconcileInterval, ReconcilePlan::apply and json_merge no-op/mode-tightening, Reconciler.apply_lock serialization, should_log, and a new tick.rs (CurrentConfig, TickStatus, run_tick_with, run_tick) plus remote::tick_config_status. New behaviour is stubbed with todo!() where the contract names a function that does not exist yet; modifications to existing functions (parse_local_daemon, ReconcilePlan::apply, json_merge::plan_merge_with) are left unimplemented and their tests fail on plain assertions instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The daemon applied its configuration only at startup and on a controller push (a managed device also on every reconnect, since the controller re-sends the configuration). A managed file deleted or edited by hand, a fixed conflict or a loosened mode stayed until then; in standalone mode until the next restart. - `daemon.reconcileInterval` (local startup setting, opt-in: unset means no tick; zero rejected). When set, the daemon re-applies the current configuration every interval, first one interval after start. The current configuration is the startup one, replaced by each pushed configuration that passes hash, UTF-8 and parse, whether or not its apply succeeds. - Applies are serialized: `Reconciler` holds an apply lock (shared by clones, poisoning ignored, never held across an await) around plan and apply, so a tick, a push and the startup apply never overlap. - `ReconcilePlan::apply` skips a write whose file already holds the planned bytes and grants no permission bit outside the planned mode; a looser file is rewritten with the same bytes. `json_merge` records an unchanged but looser file as `update`. So the `json_merge` sidecars and unchanged files are no longer rewritten on every apply (local issue 006), and a loosened `providers.json` or `chatLanguageModels.json` is tightened. - Reporting: a tick's outcome goes to the connection loop through a watch channel; a `ConfigStatus` is sent only when the tick's revision is the stream's current one and the state, error or a program's (program, state, detail) differs from the last status sent on this stream (a program settling from applied or removed to unchanged is not a difference). A tick's outcome lines are logged only on the same kind of change. - README: "Periodic re-apply (opt-in)" (what it repairs, what it cannot, no opt-out by deleting a file); three passages updated. Schema regenerated. Tests: baseline 2f330a0 (test author, 28 tests), unchanged. Writer: `only_a_push_that_parses_replaces_the_current_configuration`, `the_reconcile_tick_is_off_unless_an_interval_is_set`, Docker `reconcile_tick_recreates_a_deleted_managed_file` (the file is back within 10 s at 600, and later ticks change no inode or mtime). Agent lib 296, core 21; VS Code Docker 5/5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…quiet failures, interval cap) Review round 1 on pr5-v1 (conformance conforms; maintainer and adversarial each two majors, several minors). - A pushed configuration whose apply failed (a conflict) is the current one but was never saved; a tick that later applied it reported APPLIED while `remote-config.yaml` kept the previous revision, and a restart went back to it. The connection loop now saves it when a tick of that revision succeeds; a failed save keeps the tick FAILED with the error and is retried (maintainer M1, adversarial M1 and M2). - Logout clears the current configuration, so the tick stops enforcing the previous organization's configuration; the managed files stay until the next configuration (maintainer M2, adversarial m4). - The tick reads the configuration after taking the apply lock (`Reconciler::apply_read_under_lock`), so a push that took the lock first is not followed by a tick re-applying the older revision (adversarial m2). - A tick's failure warning follows the same only-on-change rule as its outcome lines; a persisting conflict no longer warns every interval (adversarial m1). - `daemon.reconcileInterval` is capped at 30 days (a huge value made the tick panic, adversarial n2). - The push is parsed once (`apply_parsed`), the status builder is shared by push and tick (`config_status`, `program_statuses`), `last_apply` names what the lock holds, a misplaced doc comment moved back (maintainer m2-m5, adversarial n1). - README: any apply rewrites a file whose mode grants more than the daemon writes, with the same bytes (a Claude Code user `settings.json` at 0664 becomes 0644); the 30-day cap (maintainer m1, adversarial m6). Writer tests: `a_program_that_disappears_is_a_difference_unless_it_was_removed` (conformance), `daemon_reconcile_interval_rejects_more_than_thirty_days`. Not covered by a unit test: the connection loop's save-after-tick and the logout clear (checked in the lab); a panic inside a tick ends the tick task (a panicking provider would also fail pushes). Agent lib 297, core 22. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The Copilot CLI and VS Code providers log "local LLM proxy is not available" at debug level: they plan on every apply, so with a tick the warning repeated every interval (lab v1 deviation 2). The `inactive` outcome line carries the same reason and is now a warning, logged only when the outcome changes. - A save retried after a tick that fails again is logged as a warning (delta 1 d3). - README: after a logout the tick stops re-applying; a pending save is lost if the connection drops and the device restarts offline before a tick applies it (the previous saved configuration is restored until the controller pushes again) (delta 1 d1, d2). Left as is, with the reason: `status_keys` maps an unspecified program state to failed; the connection loop only compares statuses this agent produced, which never carry it. A mode-only rewrite through `json_merge` is recorded as `update` and counts as a change; the other writers record their own action. A tick status that arrives just after the push's status can send one extra status for the same revision; it is correct, only redundant. Agent lib 297. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…isten, README on repeating conflict warnings, log doc) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
Author
|
Superseded: landed upstream in agentdesktop-dev#146 (opt-in reconcile tick). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on this fork so the diff is this PR only (base:
copilot/pr4-program-status). Ninth change in the series; James gets one integration PR fromcopilot/integrationwhen the series is ready.Why. The daemon applied its configuration at startup and on a controller push; a controller-managed device also re-applies on every reconnect (the controller re-sends the configuration, and the agent reconnects before each OIDC access token expires). A managed file deleted or edited by hand, a fixed conflict or a loosened mode stayed until then, in standalone mode until the next restart. And every apply rewrote each
json_mergesidecar with identical bytes.What it does.
daemon.reconcileInterval(local startup setting; unset means no tick, zero or more than 30 days rejected). Opt-in because a tick overrides the user (a deleted managed file comes back), which is a fleet-policy decision; a controller-delivered interval is a follow-up (local issue 015). This differs frominventoryInterval, which sits at the top level.providers.jsonat 0644 becomes 0600, a Claude Code usersettings.jsonat 0664 becomes 0644). Sidecars and unchanged files are no longer rewritten on every apply (local issue 006).ConfigStatusonly when its revision is the stream's current one and the state, error or a program's (program, state, detail) differs from what was last sent on the stream (settling from applied to unchanged is not news). Its outcome lines and failure warning are logged only on the same kind of change; the proxy-absent cause is logged by the providers at debug level and theinactiveoutcome is a warning. A pushed configuration whose apply failed is saved as the one to restore once a tick applies it; a failed save keeps the tick FAILED and is retried.Process. Spec reviewed (Opus, 19 findings, rev 2), then made opt-in after a design discussion (rev 3); baseline tests first (2f330a0, 28 tests), unchanged. Three review lenses: conformance conforms; maintainer and adversarial each two majors (a tick-applied revision never saved, so an offline restart went back; logout kept enforcing; a save failure masked by the next tick) and minors, fixed in the second commit. Delta 1 and delta 2 passed with minors (quiet proxy warnings, a warning on a failed save retry, README on logout and offline restart, wording). The test guard now also counts
#[tokio::test(...)]tests. Gate: agent 297, core 22, clippy, fmt, schema.Lab (Linux VM, user mode, controller-pushed Claude Code, Copilot CLI and VS Code,
reconcileInterval: 20s). First run by an independent agent on v1, all seven items pass: two ticks change no file (inode, mtime, mode of all six managed files and sidecars); a deletedproviders.jsonis back in 11 s at 600 with the same bytes and an untouched sidecar, one tick report;chmod 664is back to 600 with the same bytes; a comment inchatLanguageModels.jsongives FAILED andvscodeconflict within one interval, one outcome warning in 45 s, and APPLIED after the comment is removed; VS Codesettings.jsonat 664 undergithubModelsis back to 600; without the proxy both programs areinactiveand later ticks stay quiet; standalone, a hand-deleted Claude Code key is back in 16 s. Repeat on v3 withlab/checks/pr5-tick.sh: 15 of 15. VS Code Docker tests 5 of 5, including a new one that deletes a managed file and checks that later ticks write nothing.Not covered: a unit test for the connection loop's save-after-tick and logout paths (covered by review and the lab); a panic inside a tick ends the tick task (a panicking provider would fail pushes too); macOS and Windows.
Follow-ups (local issue tracker): 015 controller-delivered interval; 014 startup report.
🤖 Generated with Claude Code