You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Terminal conversation recovery with HerdR for Claude, Codex, Antigravity, Muse, and Grok. When you start one of these clients with al claude, al codex, al agy, al muse, or al grok inside a HerdR pane, Agent Layer reports that conversation's resume command to HerdR. When HerdR restores the pane, it runs that command, such as al claude --resume <session-id> or al codex resume <session-id>, from the project directory, so the same conversation resumes under the project's current Agent Layer configuration and environment. Per-launch flags are not replayed, and prompts, credentials, and environment snapshots are never saved. A conversation registers at its first client event that carries a session ID, so Muse resumed by hand registers at its first prompt and Antigravity at its first invocation. Agent Layer reports success only after HerdR's saved session file contains the resume command, which normally takes a few seconds; a rejected, stale, or unsaved report fails with a diagnostic. If HERDR_ENV=1 is set without HERDR_SOCKET_PATH and HERDR_PANE_ID, the launch command stops with an error. VS Code, Claude VS Code, and Copilot CLI sessions are not covered, and dispatched agents and subagents never register.
HerdR recovery hooks: al sync (and the sync before each launch command) installs a recovery hook for every enabled terminal client whether or not you use HerdR: a SessionStart hook in .claude/settings.json when agents.claude is enabled, a marked [[hooks.SessionStart]] block in .codex/config.toml when agents.codex is enabled, a PreInvocation hook in .agy/config/hooks.json for Antigravity, SessionStart and UserPromptSubmit hooks in .muse/hooks.json for Muse, and .grok/hooks/agent-layer-herdr.json for Grok. Each hook runs al hook herdr from your PATH on those client events, including when you start the client directly, and does nothing unless an al launch command started that session inside a HerdR pane. Your other hooks are kept, including hooks.SessionStart entries you set through agents.codex.agent_specific, and repeated syncs keep exactly one managed hook per client. Disabling a client removes its hook on the next sync. Recovery still depends on each client's own hook trust and sign-in.
The built-in Agent Dispatch MCP server now records its lifecycle on disk, so you can investigate a dropped connection after the session ends. Each server instance creates .agent-layer/state/dispatch-mcp/<connection-id>.jsonl in an owner-only directory (0700) with an owner-only file (0600). It records start before registering its tools, error for a startup failure or an observed read or write failure, and stop with a stop condition such as client_eof, transport_error, context_cancelled, or completion_unknown when serving returns. Records contain only the time, connection ID, Agent Layer version, process and parent process IDs, and fixed categories; prompts, tool requests and responses, raw error text, credentials, and environment values are never written. Read the files with any text editor or JSON Lines tool; no al command displays them. They are never deleted automatically, so remove them when you no longer need them. An abrupt kill, such as SIGKILL or an out-of-memory kill, leaves no stop record. On Unix, a broken stdout pipe is now recorded as a write failure instead of killing the server immediately. If a record cannot be written, the server prints a warning on stderr and keeps serving. docs/AGENT-DISPATCH.md describes each field and stop condition under "MCP server lifecycle diagnostics".
Changed
AL_DOWNLOAD_TIMEOUT (default 30s) now limits how long a download of a pinned al release may go without receiving data, instead of limiting the whole download. Each download is capped at 10 minutes (longer when AL_DOWNLOAD_TIMEOUT is 5 minutes or more), and a stalled download reports timeout remediation. A second al process waiting for another process to download the same version now waits up to about 20 minutes, previously about 2, before giving up. This fixes running a command in a repo pinned to a version that is not cached, or running al upgrade prefetch, on a slow connection that needs more than 30 seconds to download the ~20 MB binary. Previously the download was cut off while data was still arriving, retried from the start, and failed with a generic context deadline exceeded (Client.Timeout ... while reading body) error.
al init --version now rejects a version other than the running release CLI, including a --version latest that resolves to a newer release, before checking the release or writing any files. al upgrade --version and al upgrade plan --version now reject a version older than the running release CLI. Previously both commands reported success but wrote the running CLI's templates under the requested pin. With an older pin, every later command, including al wizard, ran that older binary against newer files and could fail on config.toml keys it does not know, and al upgrade also recorded those templates as the older release's managed baseline. With a newer al init pin, a later al upgrade treated the repository as already on that release and could skip migrations it still needed. To pin a different release, run al init or al upgrade with that release's CLI, or restore a snapshot with al upgrade rollback <snapshot-id>. Development builds can still target any version.
al dispatch, al benchmark, al probe, and al upgrade now fail with unknown command and a nonzero exit status when given a mistyped subcommand, such as al dispatch wiat, and al upgrade also rejects a stray argument, such as al upgrade rollbak <id> or al upgrade pln. Previously al dispatch, al benchmark, and al probe printed help and exited 0, so a script read the typo as success. al upgrade ignored the extra word and ran a real upgrade, writing an upgrade snapshot and the updated templates and reporting Upgrade successful., so a mistyped rollback upgraded the repository instead. Running al dispatch, al benchmark, or al probe without a subcommand still prints help.
With approvals.mode set to all or commands, VS Code now auto-approves a commands.allow entry only when the command is the entry alone or the entry followed by a space or tab and arguments. Newlines and other whitespace do not count as argument separators. Previously the generated chat.tools.terminal.autoApprove pattern only required a word boundary after the entry, so the default git, node, and make entries also auto-approved different programs such as git-filter-repo --force, node-gyp rebuild, and make.sh, while an entry ending in punctuation, such as go test ./..., approved only the bare command and prompted for go test ./... -run TestFoo. Run al sync to regenerate .vscode/settings.json; a program that VS Code approved only through a shared prefix now prompts unless you add it to commands.allow.
al sync with Muse enabled now rejects a commands.allow entry where an unquoted # starts an argument, such as git push origin #123 or git log # recent, before writing any files. Previously Muse treated the # and everything after it as a shell comment and auto-approved the shorter prefix, such as every git push origin command, while Claude and Codex kept the whole entry. Quote a literal # argument, as in git push origin '#123'; a # inside an argument, such as a#b, is unaffected. If an earlier sync already wrote the shorter rule, Muse keeps approving it until you fix the entry and run al sync again.
al sync, the sync before al claude, al codex, and the other launch commands, and the al doctor MCP connectivity check now resolve MCP ${VAR} placeholders from your process environment, as documented, with .agent-layer/.env filling only missing or empty values. Previously they read only .agent-layer/.env, so a secret exported in your shell failed with missing environment variables even though al doctor reported it as found. When a key is set in both places, Codex .codex/config.toml and Muse .mcp.json entries now embed the process value instead of the .env value.
The ship-pr skill, and the merge-authorization reference used by auto-skill-loop, now require a ledger of every eligible review request before asking you to authorize a merge, including a request inside an automated approval, review summary, or pre-merge checklist. Each entry lists the request's source link, its disposition with evidence, and a posted reply that begins with Fixed in <full commit SHA>., Deferred., or Disagreed.; the merge gate refuses an incomplete ledger. Previously a request inside an approval that was summarized as having no actionable comments could be skipped. Run al upgrade to update installed copies of these skills.
Security
al doctor no longer prints secrets from .agent-layer/.env or your shell environment when it cannot reach an MCP server. Previously a connection or tool-listing failure echoed the resolved request, so a server URL such as https://mcp.tavily.com/mcp/?tavilyApiKey=${AL_TAVILY_API_KEY}, the shape the wizard's Tavily entry uses, appeared with the real API key in the progress line and the MCP_SERVER_UNREACHABLE warning whenever doctor ran offline, behind a blocking proxy, or against a failing server. Doctor now replaces every value it substituted into a server's URL, headers, command, arguments, or environment with the ${NAME} placeholder from config.toml, as al skills already does for repository credentials. ${AL_REPO_ROOT} paths stay visible. If you shared earlier al doctor output, rotate any key it contained.
al sync (and the sync before al agy), al agy, and Antigravity dispatch now keep the Antigravity home .agy/ owner-only (0700). Previously sync created .agy/ with default permissions, such as 0755, before al agy could restrict it, and an existing directory was never tightened, so other local users could list it and read files agy stores there without owner-only permissions, such as conversation summaries and logs. Sync and launch now create .agy/ as owner-only and tighten an existing real directory that grants group or other access; files inside it are left unchanged. al agy, Antigravity dispatch, and wizard model discovery now also reject a symlinked .agy/, which al sync already rejected.
Fixed
al sync (and the sync before al codex and the other launch commands) no longer fails with generated Codex config is invalid TOML: toml: key SessionStart already exists as a value, but should be an array table when Codex is enabled and [agents.codex.agent_specific.hooks] sets SessionStart = []. The same failure, naming Stop, happened with Stop = [] while notifications.chime is enabled. An empty list adds no hooks of its own, but sync wrote it beside the hook that Agent Layer adds for that event, which TOML does not allow. Sync now leaves the empty list out, so .codex/config.toml holds only Agent Layer's hook for that event. Non-empty lists are kept as before.
al update on a Linux Homebrew (Linuxbrew) installation now reports the version that Homebrew just installed. Previously the completion message checked the binary in the old version's Cellar folder, because Linux reports the running program's resolved path rather than the bin/al link. It printed Warning: could not determine installed CLI version and v1.0.0 -> unknown when Homebrew had removed the old version, or the old version, such as v1.0.0 -> v1.0.0, when Homebrew kept it. The update itself succeeded either way. The check now runs the formula's opt/agent-layer/bin/al link, which the upgrade points at the new version.
al upgrade plan no longer fails with invalid pin version when .agent-layer/al.version holds an invalid pin, such as merge-conflict markers or not-a-version, and a managed file you changed has no recorded baseline, as in a fresh clone where .agent-layer/state/managed-baseline.json is not committed. Previously the dry run stopped with exit status 1, although al upgrade itself repairs such a pin. The plan now treats the invalid pin as no pin: the file is reported with no baseline, and the pin row shows the invalid text that the upgrade replaces. A pin file that cannot be read still fails the plan.
al sync (and the sync before al vscode, al claude, and the other launch commands) no longer writes an invalid .vscode/settings.json when one of your settings sits right before an Agent Layer managed block that held only comments, and the next sync adds a setting to that block. This happens, for example, after switching approvals.mode to yolo or enabling agents.vscode when the block was empty. VS Code can add a new setting to an object with no other settings right after {, which places it before the block without a comma. Previously sync wrote the managed setting without a comma between the two, so VS Code reported the file as invalid, and later syncs left it broken. Sync now adds that comma after your setting's value, before any trailing comment. Run al sync to repair a file that an earlier sync broke this way.
al wizard keeps section-style MCP server sub-tables such as [mcp.servers.headers] and [mcp.servers.env] with the server they belong to, and removes transport-incompatible ones while repairing server entries; previously applying the wizard moved them to the last server and dropped repeated ones, which could send one server's credentials to another server.
al skills remove no longer reports success while the removed selector stays in .agent-layer/config.toml when that file declares its imports in valid TOML other than a plain [[skills.imports]] header, such as imports = [{ repository = "...", selectors = ["skills/a"] }] under [skills] or [[skills."imports"]]. Previously remove deleted the skill and its lock entry but left the config unchanged, so the next al skills pull imported the skill again. Adding a selector to such an import failed with a confusing key imports already exists or duplicate-block error. Both commands now stop before changing anything and ask you to give that import its own [[skills.imports]] header; imports for other repositories or policies are unaffected. If an earlier remove left the selector configured, rewrite the entry that way and run al skills remove again.
al benchmark run no longer records a paid Pier run as succeeded when its verifier failed and Docker cleanup also failed. Previously the cleanup failure took precedence: the run got a succeeded execution receipt and the retained staging directory, including the exact model.patch needed to replay the verifier, was deleted, so every later invocation failed that cell with execution failed and could neither replay the verifier nor resume it. A verifier failure after a proven provider completion now keeps the checkpoint and staging directory for verifier-only replay, which cleans up the leftover Docker resources first; an agent-phase failure gets a failed receipt that a later invocation can resume. The error now reports both the verifier and the cleanup failure.
al upgrade rollback <snapshot-id>, and the automatic rollback after a failed al upgrade, now restore files inside a symlinked .agent-layer/skills/ or .agent-layer/instructions/ directory, such as .agent-layer/skills -> ../shared-skills. Previously, when an upgrade migration changed a file through such a link (for example the rename of skills/agent-dispatch/ to skills/dispatch-agent/), rollback deleted the original files in the link's target, then failed with inspect restored directory ... no such file or directory. Every retry failed the same way, so the files survived only inside the snapshot. Rollback now recreates the link first and restores the files through it. Rollback also now stops with ancestor that resolves outside repo root, before writing, when a restored link would lead a write outside the repository. If an earlier rollback failed this way, run al upgrade rollback <snapshot-id> again; the snapshot still holds the files.
al sync (and the sync before al codex, al claude, and the other launch commands) and al wizard now read a TOML multiline string correctly when it ends with a quote character right before its closing delimiter, such as developer_instructions = """Always end with "Done."""" in .codex/config.toml or .agent-layer/config.toml. TOML treats those extra quotes as part of the string. Previously Agent Layer ended the string at the first """ or ''', which threw off how it read the rest of the file: a later multiline string's text was read as config, and the tables after that string were read as part of it. As a result, sync could keep an [mcp_servers.*] table in .codex/config.toml for a server you had removed, al wizard could fail with table approvals already exists or move sections, and sync could misread .grok-config/config.toml when setting [compat.claude] agents = false. Run al sync to remove stale Codex entries.
al upgrade plan and the "overwrite all managed files" review in al upgrade now list a file that an upgrade migration renames when its content differs from the new template, such as a customized .agent-layer/instructions/02_memory.md that becomes 01_memory.md, or a file under .agent-layer/skills/agent-dispatch/ or .agent-layer/skills/playwright-cli/ that moves to dispatch-agent/ or playwright/. The file is listed at its new path with a diff from your current content. Previously both reviews hid it, but approving "overwrite all", or running al upgrade --yes --apply-managed-updates, still replaced it with the template, so your customizations were lost without being shown. A moved file that already matches the new template is still not listed. If an earlier upgrade already replaced such a file, al upgrade rollback --list shows that upgrade's snapshot, and al upgrade rollback <snapshot-id> restores the files that upgrade changed.
al sync (and the sync before al codex, al claude, and the other launch commands) no longer fails with merged Codex config is invalid TOML: toml: table features already exists as defined by a dotted key when your .codex/config.toml defines a table that Agent Layer writes into using only dotted keys, such as features.multi_agent = true or tui.notifications = true at the top level. Previously sync added a separate [features] or [tui] header for a new managed key, such as features.apps or tui.status_line, which TOML forbids next to those dotted keys. The same applied to agents.codex.agent_specific values, such as model_providers.foo.base_url beside model_providers.foo.name = "Foo". Sync now writes the new key as a dotted key next to yours, such as features.apps = false, and leaves your entries unchanged.
al skills push no longer publishes an imported skill that a ! exclusion in its import block deselected, such as skills/secret under selectors = ["skills/*", "!skills/secret"]. Previously push still sent that skill's local edits to the configured destination, even after al skills pull had reported it as no longer selected with local changes. When the destination was the tracked source ref itself, the pushed lock then marked those edits as clean upstream content, so the next pull deleted the local copy. Push now reports such a skill as skipped and leaves its directory and lock entry unchanged. Run al skills pull to apply retirement rules: a clean skill is removed, and a modified one is kept for you to move into .agent-layer/skills/ or delete. If an earlier push already published excluded content, remove it from the destination yourself.
With approvals.mode set to all or mcp, Claude now auto-approves MCP servers whose ID contains a character outside letters, digits, _, and -, such as docs.internal or team/api. Claude Code replaces each such character with _ in its tool names (for example mcp__docs_internal__search) and matches approval rules against that name. Previously .claude/settings.json and the --allowedTools flags passed by al dispatch used the raw ID (mcp__docs.internal__*), so the rule never matched: interactive sessions prompted for every call, and a dispatched Claude agent that used the server failed with denied tool calls. Run al sync to regenerate .claude/settings.json. Grok rules are unchanged.
al organize-scratch no longer moves a bare Git repository, such as proj.git from git clone --bare, as ordinary data. Previously it was filed under artifacts/evidence and moved even without --move-worktrees, which broke every linked worktree created from it with fatal: not a git repository. A bare repository is now protected like a main checkout: it is left in place by default, and --move-worktrees moves it to review/checkouts/ and repairs its linked worktrees. A non-bare git directory created with git init --separate-git-dir is never moved, because Git cannot reconnect its work tree afterwards; neither is a bare repository that safe.bareRepository=explicit stops Git from opening. A detected git directory is also left in place when moving it would break a symlink inside it, such as objects pointing at an external store, even with --move-worktrees. To fix a worktree that an earlier run broke, run git -C <moved repository> worktree repair <worktree>.
al dispatch no longer fails a finished run with provider did not exit and close output streams within 5s of terminal evidence or shutdown when the provider streams output quickly in many small pieces, as Grok and Claude do token by token. Previously Agent Layer rewrote and synced the run record for every streamed piece, so a fast stream could fall seconds behind; after the provider exited, catching up took longer than the 5-second shutdown window and the completed answer was discarded. Progress-only events now update the recorded last_activity_at at most once per second, so it can trail the latest event by up to a second.
al dispatch to Claude no longer kills work that Claude is still running in the background, such as a backgrounded make ci or a background subagent, five seconds after Claude reports a result. Previously Claude's report for the turn that started the work, for example "make ci is running in the background", ended the dispatch: the background work was killed and the run failed with provider did not exit and close output streams within 5s of terminal evidence or shutdown. Dispatch now waits while Claude keeps the work running and returns Claude's later result. Terminal task updates clear outstanding tasks even when Claude omits the notification. If Claude exits without a later result, the latest successful result is the final answer unless a tracked task reported stopped, killed, or failed after it; dispatch fails in that case. The unlimited Claude wait applies to subagents and workflows; Claude can still stop background Bash about five seconds after its result, so run required commands such as make ci in the foreground.
Pressing Ctrl-C, or sending SIGTERM, while al downloads the version pinned in .agent-layer/al.version (or set by AL_VERSION), or waits for another al process to finish that download, now stops the command with a nonzero exit status. al upgrade prefetch stops the same way. Previously the interrupt was ignored until the download and its retry finished or the lock wait ended, and if the pinned binary was then available, al ran the interrupted command with it. A partial download is discarded, so the next run downloads the version again.
al upgrade now writes the VS Code launchers in .agent-layer/ (open-vscode.command, open-vscode.sh, open-vscode.desktop, and open-vscode.app/) only when agents.vscode is enabled, as al sync already did. Previously every upgrade recreated them with VS Code disabled, and the next al upgrade plan reported them as disabled-agent files to remove, so following that advice never cleared the warning. Launchers already on disk are left in place; delete them if you do not use VS Code.
al claude, al codex, al vscode, and the other launch commands, as well as al dispatch agents, now take a secret from .agent-layer/.env when your shell exports that variable with an empty value, such as export AL_TOKEN=. al sync and al doctor already used the .env value in that case. Previously the agent started with the empty value, so an MCP server header like Authorization: Bearer ${AL_TOKEN} reached the server as Bearer and authentication failed, even though sync succeeded and doctor reported the secret as found. A non-empty shell value still wins.
al doctor now checks secrets for and connects to only the MCP servers that al sync writes for at least one enabled client. Only those servers count toward MCP warning thresholds and the size summary. Previously a server whose clients list named only disabled clients, such as clients = ["codex"] with Codex and VS Code disabled, was still checked: a secret it referenced but you never set failed doctor with Missing secret, and also made the MCP check skip every other server and report the MCP size as unavailable, even though al sync succeeded.
The Agent Dispatch MCP tools dispatch_start and dispatch_continue now check the request context once at the start of publication, after preparation and before writing the worker request or launching the worker. Cancellation or the server's tool timeout observed at that checkpoint fails the call without launching a worker or contacting the provider; dispatch_continue preserves the current invocation for a retry. This covers cancellation during slow preparation, including waiting for the project lock and refreshing generated files, which previously could leave an unsupervised agent running after the caller received an error. Cancellation or timeout after the check, including before the launch call, can still return an error while the agent runs.
al sync (and the sync before al codex) no longer duplicates agents.codex.agent_specific hook groups such as hooks.Stop in .codex/config.toml on every sync when the chime is enabled, and no longer fails with an invalid-TOML error after an agent_specific array-of-tables value is edited.
Concurrent al dispatch start and al dispatch reserve calls no longer fail with dispatch session "<name>" was not found or read dispatch mapping when they prune expired sessions at the same time or one of them is creating a new session. Session mappings are now published only once complete, so a process that dies while reserving a name no longer leaves an empty mapping that fails every later start. Unreadable-mapping errors now name the affected session.
al init and al upgrade now read .agent-layer/al.version as documented, ignoring blank lines and # comments like version dispatch already did. Previously a commented pin was treated as invalid, so al upgrade could not determine the starting version on a fresh clone and skipped version-gated migrations, al upgrade plan could fail with invalid pin version, and an already-matching pin was rewritten with an auto-repair warning.
al sync (and the sync before al claude) no longer deletes .claude/statusline.sh. No Agent Layer release generated that file, so it belongs to you, for example a script you wire in through agents.claude.agent_specific.statusLine. Sync still removes its own .claude/claude-statusline.sh when the managed statusline is disabled.
al doctor now checks imported skills in .agent-layer/skills-imported/ the way al sync does. Previously it ignored them: it reported No skills configured or passed while sync failed on an invalid imported skill, an imported directory with no skills.lock.json entry, a malformed skills.lock.json, or a skill name present in both tiers, and the skill catalog warning and size summary left out imported skills.
al benchmark run no longer blocks a study when Docker cleanup fails after a successful paid Pier run. The next invocation retried the cleanup and recovered the result without a provider call, but left the execution receipt reporting the failed cleanup, so every later progress check, report, and --recover-only run rejected the cell as conflicting evidence. Recovery now records the successful cleanup in the receipt. A study already stuck this way recovers after deleting that cell's result.json and rerunning al benchmark run, which still makes no provider call.
al skills pull and al skills push now report a file on one side and a same-named directory on the other (for example a local scripts file while upstream adds scripts/run.sh) as a file/directory conflict for that skill, with a resolution workspace for al skills resolve. Previously the merge accepted both, so pull failed every skill with not a directory, discarded unrelated updates, and kept failing until the file was removed by hand.
The generated Linux launcher .agent-layer/open-vscode.desktop now opens VS Code when launched from GNOME Files or gio launch. Its Exec line uses specification-compliant quoting and includes an absolute fallback path to the sibling open-vscode.sh for GLib versions that leave %k empty. Run al sync to regenerate the launcher; run it again after moving the repository if your launcher does not supply %k.
al dispatch errors, Agent Dispatch MCP tool errors, and the terminal_reason of a failed dispatch run now include the underlying cause. Previously many failures reported only a label such as read dispatch prompt file, prepare Grok home, or start dispatch worker, without the path or the operating-system error, so a caller could not tell what to fix.
al upgrade repair-gitignore-block now keeps whether /.agent-layer/ and /docs/agent-layer/ are ignored, as al upgrade already did. Previously it reset both to the template defaults without saying so, so private project memory in docs/agent-layer/ could become committable and a committed .agent-layer/ was ignored again. A choice is kept when the old block has exactly one line for that pattern, commented or not; otherwise that entry gets the template default.
al organize-scratch --move-worktrees --apply now repairs a Git checkout and its linked worktree when both are moved in the same run, such as repo and the sibling repo-wt that git worktree add ../repo-wt creates. Previously the command exited nonzero and left the moved worktree broken and marked prunable, so a later git worktree prune or git gc could delete its HEAD and index, and the repair command it printed used the pre-move path. To fix a worktree that an earlier run broke, run git -C <moved checkout> worktree repair <moved worktree>.
al skills diff now shows content changes even when a skill's own .gitattributes, or your global Git attributes file, marks files as binary or non-diffable (for example * -diff). Previously an upstream skill could hide changed instructions or scripts behind Binary files … differ in the diff meant to review them.
al upgrade rollback now also restores .agent-layer/state/managed-baseline.json, or removes it if the upgrade created it. Previously the rollback left the baseline at the newer version, so the next al upgrade of an unpinned repository could skip migrations it still needed, and in a pinned repository restored templates could be reported as local customizations. Snapshots taken before this fix don't include the baseline, so rolling one of them back works as it did before.
al wizard no longer deletes noise_mode and version_update_on_sync when you answer No to the warnings prompt. Previously declining warnings removed the whole [warnings] section, so a configured quiet mode or update-check opt-in was silently lost on every wizard run, even though the prompt only controls the warning thresholds. Declining now removes only the six threshold keys, and the section is removed only when nothing else is left in it.
al sync (and the sync before each launch command) now removes the Agent Layer chime Stop hook from .claude/settings.json when you disable both agents.claude and agents.claude_vscode. Previously the hook stayed, because sync writes > and & in the hook command as \u003e and \u0026 and cleanup looked only for the unescaped text, so running claude directly kept chiming. Your other settings and hooks are unchanged.
al sync, al vscode, and the sync before the other launch commands no longer delete VS Code settings that land inside the Agent Layer block in .vscode/settings.json. VS Code and extensions such as Peacock add a new setting after the last property in the file, which in a file created by al sync is inside the block, so the next sync silently removed it. Sync now moves every setting it does not own, with its comments, to just after the block, and fails instead of discarding block content it can't read as settings. Agent Layer still regenerates its own keys, chat.tools.terminal.autoApprove, chat.agentSkillsLocations, and claudeCode.allowDangerouslySkipPermissions, and still removes the retired chat.tools.global.autoApprove.
al skills push now validates a skill it copies from the destination branch into .agent-layer/skills-imported/ when nothing local needs pushing. Previously, if a reviewer left an invalid skill on the contribution branch, such as a SKILL.md without a description, push reported unchanged and push succeeded. It then wrote that skill locally, so al sync and every launch command failed until the file was fixed by hand. Push now reports that skill as failed with would not be a valid skill, leaves the local copy and its lock entry unchanged, and still updates the other skills in the group.
al wizard now ends .agent-layer/.env with a newline when it adds a secret. Previously the file it wrote had no final newline, so a later echo AL_X=... >> .agent-layer/.env joined the new line onto the last secret: ${AL_X} was reported missing and the earlier secret held the wrong value, with no error. The wizard also adds new secrets in a fixed, sorted order, so the .env diff it previews now matches the file it writes. A file written by an earlier version gains its missing final newline when the wizard adds a new secret or when you add the newline by hand. Replacing existing secrets alone preserves the file's final-newline state.
al skills pull and al skills push now merge a file's content and executable bit separately, as Git does. Previously, if one side changed a file's executable bit and the other changed its content, for example upstream ran chmod +x scripts/run.sh while you edited that script, the skill failed with a mode conflict, or a binary conflict for a non-text file. That blocked the skill's other updates and sent you to a resolution workspace where Git had nothing to resolve. The merge now keeps both changes. A mode conflict is reported only when both sides add the same file with different executable bits.
The managed .gitignore block no longer ignores a root CLAUDE.md. Agent Layer has written its Claude instructions to .claude/CLAUDE.md since v0.17.1, but the block kept ignoring the old root path, so a root CLAUDE.md you wrote yourself, for example with Claude Code's /init, was hidden from git status, skipped by git add ., and never reached your teammates. Run al upgrade (or al upgrade repair-gitignore-block) to update .agent-layer/gitignore.block and the root .gitignore; al sync alone keeps applying the existing block. Your choices to track .agent-layer/ or docs/agent-layer/ are kept. Sync still deletes a root CLAUDE.md that Agent Layer generated.
al upgrade plan now lists, under "Files to review for removal", unknown files and directories under .agent-layer/ and docs/agent-layer/ that al upgrade --apply-deletions would delete, including your own skills and instruction files, at the paths where the upgrade's migrations will leave them. Previously the plan listed only template leftovers and could report nothing to remove while --apply-deletions then deleted those paths. .agent-layer/tmp/ and kept paths are still excluded. To keep a local path, add it to .agent-layer/upgrade-keep-list; to recover a path an earlier upgrade deleted, run al upgrade rollback <snapshot-id>.
al sync, the sync before each launch command, and al dispatch no longer fail when .claude or .codex is a symlink you manage and that client is turned off. Previously, with both agents.claude and agents.claude_vscode disabled, or with agents.codex and agents.vscode disabled, sync stopped with an error ending in .claude must be a real file inside the repository while cleaning Agent Layer chime hooks even when the linked folder had no Agent Layer chime hook to remove, although sync had written through the same link while the client was on. Sync now reads through the link and does nothing when there is no chime hook. A symlinked .grok/hooks folder without the Grok chime hook is likewise left alone. If the linked settings still contain the hook, sync fails with the same error rather than editing a file outside the repository; remove the hook from the linked file by hand, then run al sync.
Codex dispatches survive transient stream retries such as Reconnecting... 2/5 (request timed out). Previously Agent Layer treated every Codex error event as fatal and stopped a run that Codex would have recovered. Codex dispatches still fail on turn.failed, turn.aborted, a nonzero provider exit, or an exit without a completed turn; when no turn failure event reports a reason, the failure message includes the last Codex error.
al sync and al doctor now warn about a literal secret in an MCP server URL whose host or userinfo is a ${AL_*} placeholder, such as https://${AL_MCP_HOST}/mcp?api_key=sk-live-123 or https://user:token@${AL_HOST}/mcp. Previously the critical POLICY_SECRET_IN_URL warning was skipped for any URL Go could not parse, which includes every URL with a placeholder host. A query value that mixes literal text with a placeholder, such as token=abc${AL_X}, now also counts as literal, matching skill import validation. Scheme-relative URLs such as //user:secret@example.com/mcp still warn about literal userinfo, and query-like text after # is ignored by the warning, skill import validation, and benchmark credential checks.
al skills add and al skills remove now report only the skills that failed when they abort, and end with al skills add failed or al skills remove failed. Neither command changes anything when a skill fails, but previously the report also listed the other skills as imported, retired, or unchanged and ended with partially succeeded, then repeated the whole report as the error. For example, removing a skills/* selector while one skill had local edits reported the other skills as retired, yet they were still on disk. The error is now no local state was changed because N skill(s) failed.
al wizard --profile without --yes now writes nothing in a repository where Agent Layer is not installed, as documented for preview mode. It previews the profile against an empty config and notes that --yes will install Agent Layer first. Previously it ran al init before reading the profile, so a preview, or even a missing or invalid profile path, left .agent-layer/ and a root .gitignore behind. With or without --yes, the profile is now read and validated before anything is written.