diff --git a/apps/cli/src/commands/results/remote.ts b/apps/cli/src/commands/results/remote.ts index 43410cb08..e3cf396ec 100644 --- a/apps/cli/src/commands/results/remote.ts +++ b/apps/cli/src/commands/results/remote.ts @@ -159,6 +159,12 @@ export interface ResultsPublishOverrides { readonly push_conflict_policy?: 'block'; } +type RuntimeResultsConfig = Omit & { + readonly sync?: ResultsConfig['sync'] & { + readonly require_push?: boolean; + }; +}; + const REMOTE_RUN_PREFIX = 'remote::'; const SIZE_WARNING_BYTES = 10 * 1024 * 1024; @@ -226,15 +232,11 @@ export async function loadNormalizedResultsConfig( ...(project.results.remote !== undefined && { remote: project.results.remote }), ...(project.results.path !== undefined && { path: project.results.path }), ...((project.results.sync?.autoPush !== undefined || - project.results.sync?.requirePush !== undefined || project.results.sync?.pushConflictPolicy !== undefined) && { sync: { ...(project.results.sync?.autoPush !== undefined && { auto_push: project.results.sync.autoPush, }), - ...(project.results.sync?.requirePush !== undefined && { - require_push: project.results.sync.requirePush, - }), ...(project.results.sync?.pushConflictPolicy !== undefined && { push_conflict_policy: project.results.sync.pushConflictPolicy, }), @@ -260,7 +262,7 @@ export async function loadNormalizedResultsConfig( return baseConfig; } - const merged: ResultsConfig = { + const merged: RuntimeResultsConfig = { mode: 'github', ...(overrides.repo !== undefined ? { repo: overrides.repo } diff --git a/apps/cli/test/commands/results/remote-auto-export.test.ts b/apps/cli/test/commands/results/remote-auto-export.test.ts index c3621b125..4e535172a 100644 --- a/apps/cli/test/commands/results/remote-auto-export.test.ts +++ b/apps/cli/test/commands/results/remote-auto-export.test.ts @@ -145,6 +145,7 @@ describe('maybeAutoExportRunArtifacts', () => { let cloneDir: string; let previousHome: string | undefined; let previousXdgConfigHome: string | undefined; + let previousAgentvHome: string | undefined; beforeEach(() => { rootDir = mkdtempSync(path.join(os.tmpdir(), 'agentv-remote-export-test-')); @@ -157,10 +158,13 @@ describe('maybeAutoExportRunArtifacts', () => { // identity setup rather than the developer machine's ~/.gitconfig. previousHome = process.env.HOME; previousXdgConfigHome = process.env.XDG_CONFIG_HOME; + previousAgentvHome = process.env.AGENTV_HOME; process.env.HOME = path.join(rootDir, 'empty-home'); process.env.XDG_CONFIG_HOME = path.join(rootDir, 'empty-xdg-config'); + process.env.AGENTV_HOME = path.join(rootDir, 'agentv-home'); mkdirSync(process.env.HOME, { recursive: true }); mkdirSync(process.env.XDG_CONFIG_HOME, { recursive: true }); + mkdirSync(process.env.AGENTV_HOME, { recursive: true }); }); afterEach(() => { @@ -174,6 +178,11 @@ describe('maybeAutoExportRunArtifacts', () => { } else { process.env.XDG_CONFIG_HOME = previousXdgConfigHome; } + if (previousAgentvHome === undefined) { + process.env.AGENTV_HOME = undefined; + } else { + process.env.AGENTV_HOME = previousAgentvHome; + } rmSync(rootDir, { recursive: true, force: true }); }); @@ -272,6 +281,27 @@ describe('maybeAutoExportRunArtifacts', () => { } }, 20_000); + it('throws when a CLI-only require-push override cannot push results', async () => { + const runDir = writeRunArtifacts(projectDir); + writeProjectConfig(projectDir, { + repo: `file://${path.join(rootDir, 'missing-remote.git')}`, + path: cloneDir, + autoPush: false, + }); + const warnSpy = spyOn(console, 'warn').mockImplementation(() => {}); + + try { + await expect( + maybeAutoExportRunArtifacts({ + ...payload(projectDir, runDir), + results_overrides: { require_push: true }, + }), + ).rejects.toThrow(); + } finally { + warnSpy.mockRestore(); + } + }, 20_000); + it('publishes locally without pushing when auto-push is disabled', async () => { const remoteDir = initializeRemoteRepo(rootDir); const runDir = writeRunArtifacts(projectDir); diff --git a/apps/web/src/content/docs/docs/tools/dashboard.mdx b/apps/web/src/content/docs/docs/tools/dashboard.mdx index 4f81462ab..8ebdb3ae6 100644 --- a/apps/web/src/content/docs/docs/tools/dashboard.mdx +++ b/apps/web/src/content/docs/docs/tools/dashboard.mdx @@ -291,11 +291,10 @@ projects: branch: agentv/results/v1 sync: auto_push: false - require_push: false push_conflict_policy: block ``` -`results.repo.remote` is the Git remote URL used when AgentV creates a fresh results checkout, and the intended remote URL for portable project config. `results.repo.path: .` stores completed run artifacts on a dedicated branch of the source repository without checking out that branch in the source worktree. AgentV does not add or rewrite remotes inside an existing checkout; the checkout's existing `origin` must already point at the repository you want to fetch and push. When `results.repo.remote` is omitted, `results.repo.path` means an existing local Git checkout whose object database and refs AgentV should write to, and the branch defaults to `agentv/results/v1`. AgentV creates the branch automatically on first publish and commits only AgentV result paths into it. `sync.auto_push: false` keeps the result commit local; set it to `true` to push the branch best-effort after each completed run. `sync.require_push: true` is for CI workflows where a push failure should fail the command after local artifacts are written. `sync.push_conflict_policy` defaults to `block`; the removed `backup_and_force_push` value is rejected with migration guidance because AgentV never force-pushes result branches. Non-fast-forward result branch pushes are auto-merged with artifact-aware Git merge drivers and pushed as a fast-forward, so the canonical results branch is never force-pushed or rewritten. Genuine overlay conflicts route to a timestamped temp branch plus a GitHub compare link for a human merge instead. +`results.repo.remote` is the Git remote URL used when AgentV creates a fresh results checkout, and the intended remote URL for portable project config. `results.repo.path: .` stores completed run artifacts on a dedicated branch of the source repository without checking out that branch in the source worktree. AgentV does not add or rewrite remotes inside an existing checkout; the checkout's existing `origin` must already point at the repository you want to fetch and push. When `results.repo.remote` is omitted, `results.repo.path` means an existing local Git checkout whose object database and refs AgentV should write to, and the branch defaults to `agentv/results/v1`. AgentV creates the branch automatically on first publish and commits only AgentV result paths into it. `sync.auto_push: false` keeps the result commit local; set it to `true` to push the branch best-effort after each completed run. For CI workflows where a push failure should fail the command after local artifacts are written, invoke the run with `agentv eval run --results-require-push`. `sync.push_conflict_policy` defaults to `block`; the removed `backup_and_force_push` value is rejected with migration guidance because AgentV never force-pushes result branches. Non-fast-forward result branch pushes are auto-merged with artifact-aware Git merge drivers and pushed as a fast-forward, so the canonical results branch is never force-pushed or rewritten. Genuine overlay conflicts route to a timestamped temp branch plus a GitHub compare link for a human merge instead. For a separate results repository, use `results.repo.remote` and an optional managed clone `results.repo.path`: @@ -327,7 +326,6 @@ results: branch: agentv/results/v1 sync: auto_push: false - require_push: false push_conflict_policy: block ``` diff --git a/apps/web/src/content/docs/docs/tools/results.mdx b/apps/web/src/content/docs/docs/tools/results.mdx index 920198aa3..7e8cf8f29 100644 --- a/apps/web/src/content/docs/docs/tools/results.mdx +++ b/apps/web/src/content/docs/docs/tools/results.mdx @@ -240,7 +240,7 @@ The CLI contract is deliberately narrow: `agentv results` manages local result a Use these supported remote workflows instead: -- **Automatic publishing:** configure `projects[].results` or top-level `results`; new `agentv eval` and `agentv pipeline bench` runs publish completed artifacts after the run completes. Use `repo.remote` with `repo.path: .` and `repo.branch: agentv/results/v1` to store primary result records on a dedicated branch of the source repo. AgentV never adds or rewrites remotes in an existing checkout; that checkout's `origin` must already point at the repository you want to fetch and push. AgentV reserves `agentv/results/v1` for primary results and `agentv/artifacts/v1` for heavy artifact payloads. When `index.jsonl` rows point trace or transcript payloads at `agentv/artifacts/v1`, automatic publishing stores those bytes on that artifact branch in the same remote and publishes pointer keys such as `runs//`. The configured results branch remains the metadata/control plane (`index.jsonl`, `summary.json`, tags, and pointers) instead of duplicating canonical trace/transcript payload bodies. Local pre-publish run workspaces can still contain those files beside the manifest so local tools keep working. Mutable run tags are stored as `tags.json` with a `tag_revision`; there is no tag event log in the normal results layout. `results.repo.path` without `results.repo.remote` means an existing local Git checkout, distinct from `workspace.repos[].repo`, which is a portable repository identity. Set `sync.auto_push: true` to push after publish, or `sync.require_push: true` in CI to fail when that push fails. Non-fast-forward result branch pushes never force-push: AgentV auto-merges concurrent remote writes with artifact-aware Git merge drivers (a union driver for the append-only `index.jsonl`, a JSON-union driver for tag and feedback overlays) and pushes the merge as a fast-forward, and routes a genuine overlay conflict to a timestamped `agentv/results-sync/...` branch plus a GitHub compare/PR link for a human merge. The removed `sync.push_conflict_policy: backup_and_force_push` value is rejected with migration guidance; remove the field or set it to `block`. While an eval is still running, [WIP checkpoints](/docs/tools/wip-checkpoints/) can keep partial run output durable on `agentv/wip/...` branches when auto-push is enabled. +- **Automatic publishing:** configure `projects[].results` or top-level `results`; new `agentv eval` and `agentv pipeline bench` runs publish completed artifacts after the run completes. Use `repo.remote` with `repo.path: .` and `repo.branch: agentv/results/v1` to store primary result records on a dedicated branch of the source repo. AgentV never adds or rewrites remotes in an existing checkout; that checkout's `origin` must already point at the repository you want to fetch and push. AgentV reserves `agentv/results/v1` for primary results and `agentv/artifacts/v1` for heavy artifact payloads. When `index.jsonl` rows point trace or transcript payloads at `agentv/artifacts/v1`, automatic publishing stores those bytes on that artifact branch in the same remote and publishes pointer keys such as `runs//`. The configured results branch remains the metadata/control plane (`index.jsonl`, `summary.json`, tags, and pointers) instead of duplicating canonical trace/transcript payload bodies. Local pre-publish run workspaces can still contain those files beside the manifest so local tools keep working. Mutable run tags are stored as `tags.json` with a `tag_revision`; there is no tag event log in the normal results layout. `results.repo.path` without `results.repo.remote` means an existing local Git checkout, distinct from `workspace.repos[].repo`, which is a portable repository identity. Set `sync.auto_push: true` to push after publish. In CI, use `agentv eval run --results-require-push` when push failures should fail that invocation after local artifacts are written. Non-fast-forward result branch pushes never force-push: AgentV auto-merges concurrent remote writes with artifact-aware Git merge drivers (a union driver for the append-only `index.jsonl`, a JSON-union driver for tag and feedback overlays) and pushes the merge as a fast-forward, and routes a genuine overlay conflict to a timestamped `agentv/results-sync/...` branch plus a GitHub compare/PR link for a human merge. The removed `sync.push_conflict_policy: backup_and_force_push` value is rejected with migration guidance; remove the field or set it to `block`. While an eval is still running, [WIP checkpoints](/docs/tools/wip-checkpoints/) can keep partial run output durable on `agentv/wip/...` branches when auto-push is enabled. - **Manual Dashboard sync:** run `agentv dashboard`, open the project, and use **Sync Project**. - **Manual API sync:** while Dashboard is running, call `GET /api/projects/:projectId/remote/status` or `POST /api/projects/:projectId/remote/sync` for project-scoped automation. Single-project sessions also expose `GET /api/remote/status` and `POST /api/remote/sync`. - **Git escape hatch:** for advanced recovery, inspect or repair the configured `projects[].results.repo.path` clone with `git` directly, then sync again. diff --git a/docs/plans/git-native-results.md b/docs/plans/git-native-results.md index a73d9be28..bb573409c 100644 --- a/docs/plans/git-native-results.md +++ b/docs/plans/git-native-results.md @@ -52,7 +52,7 @@ Every completed `agentv eval` publish is one atomic operation: 1. Write artifacts into the normal local run workspace at `.agentv/results/runs///`. 2. Resolve the results store: either the `repo_path` checkout or the managed clone for `repo_url`. 3. Build a commit for `runs/**` (and `metadata/**`) on the storage branch using git plumbing and a temporary index, so `repo_path: .` never has to check out `agentv/results/v1`. -4. If `sync.auto_push` or `sync.require_push` is enabled, push the storage branch. Non-fast-forward conflicts fetch the remote branch, rebuild the single run commit on the remote base when safe, and retry. +4. If `sync.auto_push` is enabled or the run was invoked with `--results-require-push`, push the storage branch. Non-fast-forward conflicts fetch the remote branch, rebuild the single run commit on the remote base when safe, and retry. Each run is one commit. Files are unique to that run, so rebases never content-conflict. @@ -89,7 +89,7 @@ Each run is one commit. Files are unique to that run, so rebases never content-c ## Implementation notes - `normalizeResultsConfig()` accepts `repo_url`/legacy `repo` or `repo_path`, but prerelease docs and config examples use `repo_url` or `repo_path`. -- `directPushResults()` resolves the results store, builds one storage-branch commit for the completed run, and pushes when `sync.auto_push` or `sync.require_push` is enabled. +- `directPushResults()` resolves the results store, builds one storage-branch commit for the completed run, and pushes when `sync.auto_push` or the runtime `--results-require-push` override is enabled. - `commitResultsRunWithTemporaryIndex()` writes blobs into the repo object database and updates the storage branch via a temporary index. This is the normal `repo_path: .` path and avoids copying files into a checked-out results branch. - `listGitRuns()` uses `git ls-tree` plus `git cat-file --batch` against `runs/**/summary.json`. A not-yet-created storage branch (ref does not exist) returns `[]` rather than throwing, so the Dashboard's remote-results poll stays quiet before the first push. - `setupWipWorktree()` and `pushWipCheckpoint()` maintain recoverable in-progress branches under `agentv/wip/...`. @@ -99,7 +99,7 @@ Each run is one commit. Files are unique to that run, so rebases never content-c | Change | Impact | |--------|--------| | `results.repo` is legacy | Use `results.repo_url` for a remote clone or `results.repo_path` for an existing local checkout | -| `results.auto_push` moved | Use `results.sync.auto_push`; `results.sync.require_push` is the CI fail-on-push-failure knob | +| `results.auto_push` moved | Use `results.sync.auto_push`; use `agentv eval run --results-require-push` as the per-run CI fail-on-push-failure knob | | `repo_path` configs default to `agentv/results/v1` | Same-repo storage no longer needs an explicit branch in the common case | | WIP branch namespace is `agentv/wip/...` | Interrupted runs are recoverable, but successful runs delete their WIP branch after final publish | @@ -122,7 +122,7 @@ Breaking changes accepted because no production users yet. Document in release n ## Current answers 1. **Branch model**: completed runs use the configured storage branch, defaulting to `agentv/results/v1` for `repo_path`; WIP checkpoints use `agentv/wip//`. -2. **What to do on `git fetch` failures during `agentv eval`**: warn unless `sync.require_push` is true; local eval artifacts are still written first. +2. **What to do on `git fetch` failures during `agentv eval`**: warn unless `--results-require-push` was passed; local eval artifacts are still written first. 3. **`gh` CLI dependency**: the git-native flow uses raw `git`; GitHub-specific tooling stays outside result publishing. ## What this PR does NOT do diff --git a/packages/core/src/evaluation/loaders/config-loader.ts b/packages/core/src/evaluation/loaders/config-loader.ts index 278874ce1..5ec57f9a8 100644 --- a/packages/core/src/evaluation/loaders/config-loader.ts +++ b/packages/core/src/evaluation/loaders/config-loader.ts @@ -63,7 +63,6 @@ export type ResultsConfig = { readonly auto_push?: boolean; readonly sync?: { readonly auto_push?: boolean; - readonly require_push?: boolean; readonly push_conflict_policy?: ResultPushConflictPolicy; }; readonly branch_prefix?: string; @@ -857,8 +856,10 @@ export function parseResultsConfig(raw: unknown, configPath: string): ResultsCon logWarning(`Invalid results.sync.auto_push in ${configPath}, expected boolean`); return undefined; } - if (syncObj.require_push !== undefined && typeof syncObj.require_push !== 'boolean') { - logWarning(`Invalid results.sync.require_push in ${configPath}, expected boolean`); + if (syncObj.require_push !== undefined) { + logWarning( + `results.sync.require_push in ${configPath} is no longer supported in persistent config. Use the per-run --results-require-push CLI flag instead.`, + ); return undefined; } if (syncObj.push_conflict_policy === 'backup_and_force_push') { @@ -873,7 +874,6 @@ export function parseResultsConfig(raw: unknown, configPath: string): ResultsCon } sync = { ...(typeof syncObj.auto_push === 'boolean' && { auto_push: syncObj.auto_push }), - ...(typeof syncObj.require_push === 'boolean' && { require_push: syncObj.require_push }), ...(syncObj.push_conflict_policy === 'block' && { push_conflict_policy: syncObj.push_conflict_policy, }), diff --git a/packages/core/src/evaluation/results-repo.ts b/packages/core/src/evaluation/results-repo.ts index 7057fe7b7..c04ae7a98 100644 --- a/packages/core/src/evaluation/results-repo.ts +++ b/packages/core/src/evaluation/results-repo.ts @@ -292,6 +292,13 @@ export interface NormalizedResultsConfig { readonly storageBranchWorktree: boolean; } +export type RuntimeResultsConfig = Omit & { + readonly sync?: ResultsConfig['sync'] & { + /** Runtime-only override, set by --results-require-push. Not YAML config. */ + readonly require_push?: boolean; + }; +}; + type StorageBranchResultsConfig = NormalizedResultsConfig & { readonly branch: string }; export interface DirectPushResultsResult { @@ -371,10 +378,19 @@ function resolveLocalPath(p: string, baseDir: string): string { return path.isAbsolute(expanded) ? expanded : path.resolve(baseDir, expanded); } +function isNormalizedResultsConfig( + config: RuntimeResultsConfig | NormalizedResultsConfig, +): config is NormalizedResultsConfig { + return typeof (config as NormalizedResultsConfig).storageBranchWorktree === 'boolean'; +} + export function normalizeResultsConfig( - config: ResultsConfig, + config: RuntimeResultsConfig | NormalizedResultsConfig, options?: { baseDir?: string }, ): NormalizedResultsConfig { + if (isNormalizedResultsConfig(config)) { + return config; + } const baseDir = options?.baseDir ?? process.cwd(); const repoUrl = (config.repo_url ?? config.repo)?.trim(); const repoPath = config.repo_path?.trim(); diff --git a/packages/core/src/evaluation/validation/config-validator.ts b/packages/core/src/evaluation/validation/config-validator.ts index 1e9c2a455..fa1dc8d6f 100644 --- a/packages/core/src/evaluation/validation/config-validator.ts +++ b/packages/core/src/evaluation/validation/config-validator.ts @@ -443,12 +443,12 @@ function validateResultsSyncAndBranchPrefix( `Field '${location}.sync.auto_push' must be a boolean`, ); } - if (syncRecord.require_push !== undefined && typeof syncRecord.require_push !== 'boolean') { + if (syncRecord.require_push !== undefined) { addError( errors, filePath, `${location}.sync.require_push`, - `Field '${location}.sync.require_push' must be a boolean`, + `Field '${location}.sync.require_push' was removed from persistent config. Use the per-run --results-require-push CLI flag instead.`, ); } if (syncRecord.push_conflict_policy === 'backup_and_force_push') { diff --git a/packages/core/src/projects.ts b/packages/core/src/projects.ts index 7f802acaf..9cf0f8fca 100644 --- a/packages/core/src/projects.ts +++ b/packages/core/src/projects.ts @@ -28,7 +28,6 @@ * branch: agentv/results/v1 * sync: * auto_push: true - * require_push: false * added_at: "2026-03-20T10:00:00Z" * last_opened_at: "2026-03-30T14:00:00Z" * @@ -70,7 +69,6 @@ import { getAgentvConfigDir } from './paths.js'; export interface ProjectResultsSyncConfig { autoPush?: boolean; - requirePush?: boolean; pushConflictPolicy?: 'block'; } @@ -112,7 +110,6 @@ export function getProjectsRegistryPath(): string { interface ProjectResultsSyncYaml { auto_push?: boolean; - require_push?: boolean; push_conflict_policy?: 'block' | string; } @@ -159,6 +156,7 @@ function readTrimmedString(value: unknown): string | undefined { } let warnedRemovedBackupAndForcePushPolicy = false; +let warnedRemovedRequirePushConfig = false; function warnRemovedBackupAndForcePushPolicy(): void { if (warnedRemovedBackupAndForcePushPolicy) { @@ -170,6 +168,16 @@ function warnRemovedBackupAndForcePushPolicy(): void { ); } +function warnRemovedRequirePushConfig(): void { + if (warnedRemovedRequirePushConfig) { + return; + } + warnedRemovedRequirePushConfig = true; + console.warn( + '[agentv] projects[].results.sync.require_push is no longer supported in persistent config and was ignored while loading the project registry. Use the per-run --results-require-push CLI flag instead.', + ); +} + function fromYaml(raw: unknown): ProjectEntry | null { if (!raw || typeof raw !== 'object') return null; const e = raw as Partial; @@ -219,15 +227,11 @@ function fromYaml(raw: unknown): ProjectEntry | null { ...(clonePath ? { path: clonePath } : {}), ...(sync && (typeof sync.auto_push === 'boolean' || - typeof sync.require_push === 'boolean' || sync.push_conflict_policy === 'block' || sync.push_conflict_policy === 'backup_and_force_push') ? { sync: { ...(typeof sync.auto_push === 'boolean' ? { autoPush: sync.auto_push } : {}), - ...(typeof sync.require_push === 'boolean' - ? { requirePush: sync.require_push } - : {}), ...(sync.push_conflict_policy === 'block' ? { pushConflictPolicy: sync.push_conflict_policy } : {}), @@ -238,6 +242,9 @@ function fromYaml(raw: unknown): ProjectEntry | null { ? { branchPrefix: r.branch_prefix.trim() } : {}), }; + if (sync && 'require_push' in sync) { + warnRemovedRequirePushConfig(); + } if (sync?.push_conflict_policy === 'backup_and_force_push') { warnRemovedBackupAndForcePushPolicy(); } @@ -261,16 +268,12 @@ function toYaml(entry: ProjectEntry): ProjectEntryYaml { if (entry.results) { const resultsSync = entry.results.sync?.autoPush !== undefined || - entry.results.sync?.requirePush !== undefined || entry.results.sync?.pushConflictPolicy !== undefined ? { sync: { ...(entry.results.sync?.autoPush !== undefined && { auto_push: entry.results.sync.autoPush, }), - ...(entry.results.sync?.requirePush !== undefined && { - require_push: entry.results.sync.requirePush, - }), ...(entry.results.sync?.pushConflictPolicy !== undefined && { push_conflict_policy: entry.results.sync.pushConflictPolicy, }), diff --git a/packages/core/test/evaluation/loaders/config-loader.test.ts b/packages/core/test/evaluation/loaders/config-loader.test.ts index dbf5cc615..436cedeb3 100644 --- a/packages/core/test/evaluation/loaders/config-loader.test.ts +++ b/packages/core/test/evaluation/loaders/config-loader.test.ts @@ -384,7 +384,6 @@ describe('parseResultsConfig', () => { remote: 'upstream', sync: { auto_push: false, - require_push: true, push_conflict_policy: 'block', }, }, @@ -398,12 +397,32 @@ describe('parseResultsConfig', () => { remote: 'upstream', sync: { auto_push: false, - require_push: true, push_conflict_policy: 'block', }, }); }); + it('rejects require_push in persistent results sync config', () => { + const warn = spyOn(console, 'warn').mockImplementation(() => undefined); + try { + const result = parseResultsConfig( + { + repo_path: '.', + sync: { + require_push: true, + }, + }, + '/tmp/.agentv/config.yaml', + ); + + expect(result).toBeUndefined(); + expect(warn).toHaveBeenCalledWith(expect.stringContaining('results.sync.require_push')); + expect(warn).toHaveBeenCalledWith(expect.stringContaining('--results-require-push')); + } finally { + warn.mockRestore(); + } + }); + it('rejects removed backup_and_force_push sync policy with migration guidance', () => { const warn = spyOn(console, 'warn').mockImplementation(() => undefined); try { @@ -488,7 +507,7 @@ describe('parseResultsConfig', () => { branch: 'agentv/results/v1', }, sync: { - require_push: true, + auto_push: true, }, }, '/tmp/.agentv/config.yaml', @@ -499,7 +518,7 @@ describe('parseResultsConfig', () => { repo_path: '.', branch: 'agentv/results/v1', sync: { - require_push: true, + auto_push: true, }, }); }); diff --git a/packages/core/test/evaluation/validation/config-validator.test.ts b/packages/core/test/evaluation/validation/config-validator.test.ts index 6699d528d..5b8e0ed2f 100644 --- a/packages/core/test/evaluation/validation/config-validator.test.ts +++ b/packages/core/test/evaluation/validation/config-validator.test.ts @@ -155,7 +155,6 @@ describe('validateConfigFile', () => { branch: agentv/results/v1 sync: auto_push: false - require_push: true `, ); @@ -165,6 +164,32 @@ describe('validateConfigFile', () => { expect(result.errors).toHaveLength(0); }); + it('errors on removed require_push persistent results sync config', async () => { + const filePath = path.join(tempDir, 'removed-require-push.yaml'); + await writeFile( + filePath, + `results: + repo: + path: . + sync: + require_push: true +`, + ); + + const result = await validateConfigFile(filePath); + + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + severity: 'error', + location: 'results.sync.require_push', + message: expect.stringContaining('--results-require-push'), + }), + ]), + ); + }); + it('keeps flat project repo fields compatible with migration warnings', async () => { const filePath = path.join(tempDir, 'global-config-flat-project.yaml'); await writeFile( diff --git a/packages/core/test/projects.test.ts b/packages/core/test/projects.test.ts index 9314ca510..378a653df 100644 --- a/packages/core/test/projects.test.ts +++ b/packages/core/test/projects.test.ts @@ -236,7 +236,6 @@ describe('projects registry', () => { branch: agentv/results/v1 sync: auto_push: false - require_push: true push_conflict_policy: block added_at: "2026-01-01T00:00:00Z" last_opened_at: "2026-01-01T00:00:00Z" @@ -249,7 +248,7 @@ describe('projects registry', () => { repoUrl: 'git@github.com:example/source.git', path: '.', branch: 'agentv/results/v1', - sync: { autoPush: false, requirePush: true, pushConflictPolicy: 'block' }, + sync: { autoPush: false, pushConflictPolicy: 'block' }, }); saveProjectRegistry(registry); @@ -259,11 +258,10 @@ describe('projects registry', () => { expect(yamlOnDisk).toContain('path: .'); expect(yamlOnDisk).toContain('branch: agentv/results/v1'); expect(yamlOnDisk).toContain('auto_push: false'); - expect(yamlOnDisk).toContain('require_push: true'); expect(yamlOnDisk).toContain('push_conflict_policy: block'); expect(yamlOnDisk).not.toContain('repo_path:'); expect(yamlOnDisk).not.toContain('repoPath:'); - expect(yamlOnDisk).not.toContain('requirePush:'); + expect(yamlOnDisk).not.toContain('require_push:'); }); it('preserves legacy flat results remote aliases through YAML', () => { @@ -323,10 +321,11 @@ dashboard: expect(yamlOnDisk).toContain('projects:'); }); - it('loads project registry entries from AGENTV_HOME config.local.yaml', () => { + it('warns and ignores require_push in project registry entries', () => { const registryPath = getProjectsRegistryPath(); const localRegistryPath = path.join(path.dirname(registryPath), 'config.local.yaml'); mkdirSync(path.dirname(registryPath), { recursive: true }); + const warnSpy = spyOn(console, 'warn').mockImplementation(() => undefined); writeFileSync( localRegistryPath, `projects: @@ -346,18 +345,23 @@ dashboard: 'utf-8', ); - const registry = loadProjectRegistry(); + try { + const registry = loadProjectRegistry(); - expect(registry.projects).toHaveLength(1); - expect(registry.projects[0]).toMatchObject({ - id: 'local-results', - path: '/srv/agentv/source', - results: { - repoPath: '/srv/agentv/results/local-results', - branch: 'agentv/results/v1', - sync: { requirePush: true }, - }, - }); + expect(registry.projects).toHaveLength(1); + expect(registry.projects[0]).toMatchObject({ + id: 'local-results', + path: '/srv/agentv/source', + results: { + repoPath: '/srv/agentv/results/local-results', + branch: 'agentv/results/v1', + }, + }); + expect(registry.projects[0].results?.sync).toBeUndefined(); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('results.sync.require_push')); + } finally { + warnSpy.mockRestore(); + } }); it('keeps registry mutations in config.local.yaml when the overlay owns projects', () => { diff --git a/scripts/setup-dashboard-deployment.sh b/scripts/setup-dashboard-deployment.sh index 280e05ad1..72c4d920d 100755 --- a/scripts/setup-dashboard-deployment.sh +++ b/scripts/setup-dashboard-deployment.sh @@ -135,7 +135,6 @@ results: path: /data/results/agentv-evalresults sync: auto_push: false - require_push: false dashboard: project_dashboard: true @@ -174,7 +173,6 @@ const nextEntry = { }, sync: { auto_push: false, - require_push: false, }, }, added_at: diff --git a/skills-data/agentv-eval-writer/references/config-schema.json b/skills-data/agentv-eval-writer/references/config-schema.json index 6633383be..2a2415b38 100644 --- a/skills-data/agentv-eval-writer/references/config-schema.json +++ b/skills-data/agentv-eval-writer/references/config-schema.json @@ -155,10 +155,6 @@ "auto_push": { "type": "boolean", "description": "Push result commits best-effort after each completed run." - }, - "require_push": { - "type": "boolean", - "description": "Fail the command if a configured push fails after writing local artifacts." } }, "additionalProperties": false