Repository navigation
Conversation
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.38.2 to 4.38.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@v4.38.2...v4.38.3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.38.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
…lop/github/codeql-action-4.38.3 chore(deps): bump github/codeql-action from 4.38.2 to 4.38.3
capa upgrade runs install.ps1 with powershell.exe, which is always Windows PowerShell 5.1. 5.1 reads a BOM-less script as Windows-1252, so the UTF-8 check mark and box-drawing glyphs decoded into stray smart quotes and the script failed to parse. It never ran a single line. The failure was invisible because the installer was spawned detached with stdout/stderr ignored. - install.ps1 is ASCII-only now; `e (a 7.0-only escape) becomes [char]27 - run the Windows installer in the foreground with inherited stdio and a real exit code, same as install.sh; the installer already renames the running capa.exe aside, so capa doesn't need to exit first - test that install.ps1 stays ASCII-only
fix(upgrade): make capa upgrade work on Windows
The builder kept the generator from the existing lockfile, so adding a skill after upgrading capa left an old version in capabilities.lock. Restamp generator together with generatedAt when the pins change; a no-op install still leaves the file untouched. Closes #310
Two bugs kept plugins from gitlab-org/ai/skills from installing: - "./.claude-plugin/plugins/glab" lost its leading dot. The parser stripped "./" and the path join stripped a leading "." again, so capa looked for "claude-plugin/plugins/glab". Only a leading "./" (or a bare ".") is stripped now, in the generic marketplace parser and the bundled claude-plugins adapter (pin updated). - Each plugin's skills/<name> is a symlink to the repo's top-level skills/. Skill discovery skipped symlinked dirs, and the stable plugin copy kept the relative link, which broke once copied. Discovery now accepts symlinked skill dirs, and the plugin copy goes through copySkillTree, which dereferences symlinks that resolve inside the repo snapshot and still rejects ones that point outside it. copySkillTree also stops silently dropping symlinks and guards against link cycles. Closes #312
The at-rest migration re-encrypted any enc:v1 value it couldn't decrypt. A process started with the wrong master key (restored or swapped master.key, different HOME, key-store bug) re-wrapped every stored secret under that key, so restoring the right key later couldn't recover them. Values that don't decrypt are now left byte-identical. The count is reported: a warning when the server starts, secretStorage.undecryptable in /health, and a line in capa status. Only plaintext is migrated. Closes #305
Hook entries were upserted by event and matcher, so changing a hook's `on` or `matcher` added a new capa:<id> entry and left the old one running. The database locator then pointed only at the new entry, so neither install nor capa clean removed the old one. Before placing an entry, capa now drops entries tagged with the same capa:<id> in other events (and, for Claude-like shapes, other matcher groups). An entry already in the target group stays where it is, so re-installs don't reorder the file. Antigravity is keyed by hook name and was not affected. resolveMatcher also returns "" instead of undefined when a mapping carries an explicit undefined matcherPrefix. Closes #306
Skill, plugin and hook entries pass def.version, def.ref (and repo
suffixes) to `git rev-parse --verify <ref>^{commit}`. Nothing checked
them first, so rev-parse's revision syntax (HEAD~2, main@{1}, :/text,
a..b) and option-like values reached git.
resolveRef, the only place these values reach git, now accepts only
plain tag/branch names and SHAs (letters, digits, . _ / + -, no leading
"-", no "..") and fails with a clear error otherwise. Lockfile pins
are checked the same way.
Closes #308
The capabilities-manager reference still described ${PLUGIN_DATA} as
~/.capa/plugin-data/<projectId>/<pluginId>/ that survives plugin updates.
Since #286 the folder is <slug>-<hash>, keyed by the declared source, and
it is removed when the plugin is no longer declared and by capa clean.
Match docs/README.md.
Closes #309
Review follow-ups for the symlink support in this PR: - copySkillTree walks the real tree first, then follows symlinks, and enters each real directory once per walk. The per-chain cycle guard still allowed links pointing at each other to copy 2^N trees (a hang and a full disk for capa install and the server's re-resolve); a link to a directory already copied is now skipped, and a real directory always lands at its own path. - The plugin copy skips broken links and links leaving the repository with an install warning instead of failing the whole plugin, as cpSync used to copy them. Other copySkillTree callers still throw. - The stable-copy stamp is versioned (copy-v2) so plugins copied by the old cpSync path, with broken relative links, are copied again once. - The marketplace plugin preview checks the subpath with assertSafeRepoPath, like install does.
removeTaggedElsewhere deleted every empty event array in the hooks object, including ones it never touched (a user's "Stop": [] or a Gemini disabled: []). Only delete an array this call emptied.
The allowlist rejected tags git accepts, such as changesets/lerna
monorepo tags (@scope/pkg@1.2.3), "feature#12" or unicode names, which
installed fine before. Reject what git check-ref-format rejects instead
(control characters, space, ~ ^ : ? * [ \, "..", "@{", a leading
"-", "/" or ".", and similar), which still rules out rev-parse's
revision syntax and option-like values.
The offline snapshot fast path only checked that a lockfile pin was 40
characters long before using it as a path segment, so a tampered
resolvedRef could point at another local directory. It now requires 40
hex characters; anything else falls through to resolveRef, which
rejects it.
In search mode agents gave up on capa when a call failed:
- An unknown call_tool name said "Make sure you've called setup_tools to
activate the required skills", which doesn't exist in search mode and
reads as misconfiguration. Unknown names now get one message on both
call paths: not in the catalog, hidden by a server's expose
except/exactly policy when that's the reason, the closest names (a bare
remote name like get_issue maps to github.get_issue, which is how
server descriptions name tools), and the next step for the exposure
mode. It still starts with "Tool not found".
- A tool that exists but isn't activated now says to call
search("<exact name>"), which activates it.
- Searching a tool's exact name (qualified, local id or remote name) ranks
it first instead of among every tool sharing a word; a name-like query
with no exact match says the tool isn't exposed by this project.
Closes #321
The variables UI, capa configure and install --env scanned every string
in the effective capabilities for ${…}. Tools synthesized from a
server's expose policy carry the remote server's own descriptions, so
prose such as Grafana's ${var:regex} showed up as a required project
variable. Inline skill bodies and descriptions had the same problem.
extractRequiredVariables reads only the fields capa substitutes: MCP
server defs (resolveMcpServerDef) and command tool init/run cmd, dir
and env (tool-executor). Plugin-sourced entries stay excluded.
Closes #320
tool-executor resolves the whole command spec, so a ${...} in an
init/run argument default is substituted at runtime. Include defaults
when collecting required variables; argument descriptions stay prose.
Qodo review follow-ups for unknownToolMessage: - Servers with hand-written tools: entries have their expose policy turned off (serversWithExposePolicy semantics), so it is never named as the cause. - A bare name that is callable on another server (public.shared) is suggested instead of being reported as blocked by one server's except list. - Only a name listed under an active expose: except is reported as hidden; the list proves the tool exists. Under exactly, and now none, a qualified name might just be a typo, so the policy is added as a note next to the normal not-found answer and suggestions.
fix(lockfile): stamp the current capa version when pins change
…-312 fix(registries): resolve GitLab marketplace plugins
…only fix(variables): only config fields can make a variable required
fix(mcp): clearer search-mode errors and exact-name search
docs(skills): correct the PLUGIN_DATA path and lifetime
fix(cache): validate git version/ref values before rev-parse
fix(hooks): remove the old entry when a hook event or matcher changes
fix(secrets): never re-encrypt secrets that fail to decrypt
After a passthrough install no providers are on record, so capa clean fell back to every provider, including OpenClaw, whose skills dir is the project's own skills/. It then removed skills/<id> for every declared skill, deleting committed type: local sources. - Skill dirs are only removed for providers the project resolved; with none on record, only providers whose skills dir is their own dot directory (.claude/skills, .cursor/skills, ...) are swept, which still cleans up after a passthrough install. - A skill dir that is, contains or sits inside a type: local skill's source is never removed, whatever the provider. Closes #324
Claude Code plugin hooks can use exec form: { command: "node", args: [...] },
spawned without a shell. parseHookAction never read args, so capa
installed a bare `node`, which Claude Code fed the hook JSON on stdin and
which failed with a SyntaxError on every matching event.
- args are parsed, carried on UnifiedHookEntry and Hook, kept by
validateHooks, and ${CLAUDE_PLUGIN_ROOT} is expanded in each one.
- Claude Code (supportsExecArgs) gets the args array as is.
- Providers that only take a command string get the args appended
POSIX-quoted, so each stays one verbatim argument.
- The plugin hook merge key includes args, so `node a.js` and
`node b.js` are not coalesced.
Closes #325
Some plugin hooks read the CLAUDE_PLUGIN_ROOT environment variable at
runtime (ponytail: node -e "...process.env.CLAUDE_PLUGIN_ROOT...").
Claude Code only sets it for hooks of an installed plugin, not for hooks
in project settings, so once capa wrote them there they threw a
TypeError on every prompt. This affected server mode and --passthrough
alike; the report saw a difference only because an older ponytail used
the ${CLAUDE_PLUGIN_ROOT} placeholder, which capa already expands.
${CLAUDE_PLUGIN_ROOT} placeholders are expanded first, so any name left
is a runtime read: capa now prefixes CLAUDE_PLUGIN_ROOT=<plugin copy> to
the command. An exec-form hook that reads it becomes one shell command,
since exec form can't set environment variables.
Closes #326
The project API blanked every MCP server env value, so the UI showed empty
fields even though spawn resolved them. Stored defs keep `${VAR}`
placeholders (resolved only at spawn), so those are returned as-is, along
with plain literals. A literal is still blanked when its name reads like a
credential or it embeds URL credentials.
Fixes #349
Address Qodo review on #350: the name check now covers PASS and PAT as a segment (DB_PASS, GITHUB_PAT_VALUE), and the value itself is checked for Bearer/Basic, well-known token prefixes and long token-shaped strings, so a secret under a generic name stays blank. Paths and plain config still show.
fix: show MCP env placeholders and plain literals in the project API
Code Review by Qodo
1.
|
| // A lockfile pin becomes a path segment here, so it must be a real SHA. | ||
| const fullSha = /^[0-9a-f]{40}$/i.test(opts.pinnedSha) ? opts.pinnedSha : null; |
There was a problem hiding this comment.
2. Invalid pins can reach git 📎 Requirement gap ⛨ Security
resolveRef validates pinnedSha only as a generic Git ref, while the snapshot fast path merely skips non-40-character values instead of rejecting them. A short SHA or another valid ref therefore reaches Git and can be resolved and used in snapshot handling instead of failing as an invalid lockfile pin.
Agent Prompt
## Issue description
Lockfile pins are required to be real commit SHAs, but the new snapshot check only recognizes full SHAs for the offline fast path and does not reject other values before `resolveRef` passes them to Git.
## Fix Focus Areas
- src/shared/cache/snapshot.ts[131-132]
- src/shared/cache/mirror.ts[303-305]
## Recommended Fix
Validate every lockfile `pinnedSha` against the full hexadecimal commit SHA format before ref resolution or path construction, and raise a clear error for short SHAs, branch names, or other non-SHA values while retaining the broader Git ref validation for `version` and `ref`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
PR Summary by QodoRelease v2.4.0: project status, plugin fixes, and safety improvements
AI Description
Diagram
High-Level Assessment
Files changed (74)
|
Qodo review of #351: - Plugin agents keep skills that were installed under a prefix after a name clash (they referenced the old id and dropped the skill). - A plugin that fails part-way releases the skill names it claimed, so a later plugin keeps the plain name. - A lockfile pin must be a hex commit SHA (7 to 40 characters); a branch or tag there would float. - A legacy plaintext secret that merely starts with enc:v1: is encrypted on open; only a well-formed envelope is treated as ciphertext and left untouched when it doesn't decrypt. - Search explains a missing tool name with the server's expose policy, sharing exposurePolicyReason with the unknown-tool error. - MCP env redaction also blanks token-only URL userinfo, credential parameters in query and connection strings, and base64-style secrets with / or +.
Qodo review of #352: - An encrypted empty secret is only IV + tag (28 bytes); requiring 29 made the migration encrypt it again and reads return ciphertext. - Redaction: URL userinfo is a credential only as user:pass@ or a long mixed token (so ssh://git@github.com stays visible), and a base64-style value needs + or = or the 40 characters of an AWS secret key (so relative paths stay visible). - A failed plugin restores the skill-claim snapshot taken before it instead of deleting claims by install id, which another entry may share. - Sub-agent search judges "callable elsewhere" by the tools the caller can see. - buildSearchPayload takes hidden and note separately instead of checking a message prefix. - Doc comments sit above their own functions again.
Token-only URL userinfo counts at 16+ characters, letters only included. Base64-style values are told apart from relative paths by Shannon entropy instead of requiring + / = or exactly 40 characters.
The entropy check alone let low-entropy AWS-style keys through. The fixtures named *_TOKEN / *_SECRET were blanked by name before the value rules ran, so the tests did not cover them.
Redaction: keep the 40-character key rule, test value rules directly
Redaction: letter-only URL tokens and unpadded base64 secrets
fix: second round of v2.4.0 release review follow-ups
fix: follow-ups from the v2.4.0 release review
The running server's capabilities carry the tools synthesized from each server's expose policy, so serverIdsWithDeclaredTools treated every policy server as hand-curated and exposurePolicyReason never explained a tool hidden by expose: except.
explainGitError rewrote every error. Invalid refs and lockfile pins came out as 'Failed to clone ...: Unknown error', and a missing tag on GitHub/GitLab as 'repository not accessible'. Only a failed git process (cmd / code set) is explained now; capa's own messages pass through.
Search mode: explain tools hidden by expose: except on a running server
With other plugins resolving, the failure was only a warning, so capa status showed Installed and --strict exited 0. It is now recorded like a failed skill, and reported once instead of twice when every plugin fails.
explainGitError told git failures from capa's own errors by the cmd / code properties execFile sets. That shape depends on the runtime, so git() now records the errors it rejects with and the wrapper checks that.
The header added servers.length to the authored tools, so 8 servers with 100 tools showed 8. It now counts every exposed tool, as the projects list does.
UI: show the number of tools, not servers, in the Tools header
Install: show invalid ref and pin errors instead of Unknown error
resolvePlugins now returns failures alongside warnings. The install task and passthrough install use that list, so a warning from a plugin that resolved can't count as a failure because of its text.
Plugin resolve failures: structured list instead of text match
…ures Status: count plugins that fail to resolve as install failures
Summary
Release v2.4.0:
capa statusreports the current project as well as the server.CLAUDE_PLUGIN_ROOT, and skills with the same name from two plugins.capa cleanand stored secrets are safer.capabilities.yaml, andcapa upgradeworks on Windows.Closes #305, closes #306, closes #308, closes #309, closes #310, closes #312, closes #320, closes #321, closes #324, closes #325, closes #326, closes #330, closes #332, closes #345, closes #349.
What changed
New
Project state in
capa status(feat(status): report the current project's state #344, closes [Bug]capa statusreports server health only, not the current project's state #332): next to the server,statusreports the project in the current folder, which is one of:capabilities.lockchanged, or capa was upgraded since);--jsonprints it for scripts and--strictexits 1 unless the project is installed, up to date and without problems.capa installnow records what it installed sostatuscan compare.statusitself only reads.JSON Schema for
capabilities.yaml(feat(capabilities): publish a JSON Schema for the capabilities file #346, closes [Feature] capabilities.yaml schema file #345): editors get autocomplete and validation.capa initadds the$schemaline to new files; the README shows how to add it to existing ones.Plugin skills with the same name (fix(plugins): install a clashing plugin skill under a prefix, with a warning #331, closes [Feature] Allow selecting individual skills from plugins #330): when two plugins ship a skill with the same name, nothing is overwritten anymore.
/skill./<plugin id>-<skill>, with itsSKILL.mdname updated to match.Fixed
./.claude-plugin/plugins/glabkeep their leading dot.argsarray, installing a barenodecommand that fails on every matching event #325):command+argshooks keep theirargs. Claude Code gets the array as is; other providers get a quoted command string.CLAUDE_PLUGIN_ROOT(fix(hooks): set CLAUDE_PLUGIN_ROOT for plugin hooks that read it #329, closes [Bug]install --passthroughwrites plugin hooks that read$CLAUDE_PLUGIN_ROOT, which is unset for project hooks #326): capa exports the variable for hooks that read it at runtime, since Claude Code only sets it for hooks of an installed plugin.capa clean(fix(clean): never delete project skill sources under skills/ #327, closes [Bug]capa cleandeletes project-owned local skill sources underskills/after a passthrough install #324): it no longer deletes project-ownedtype: localskill sources underskills/after a passthrough install, including sources reached through a symlinked folder or recorded by an earlier install.generatorversion not updated when adding new skills #310):generatorshows the capa version that last changed the pins.${...}text in tool descriptions or skill bodies no longer shows up as a required project variable. Only the fields capa substitutes count.expose: exceptsays so, on a running server too (Search mode: explain tools hidden by expose: except on a running server #356).version/ref, an invalid lockfile pin, or a missing tag or commit shows its own message instead ofFailed to clone …: Unknown erroror "repository not accessible".capa statusreports them and--strictexits 1, and reported once instead of twice.${VAR}placeholders and plain values are shown instead of blank. Values that look like credentials stay hidden.capa upgradeon Windows (fix(upgrade): make capa upgrade work on Windows #304): the Windows installer script parses in Windows PowerShell 5.1, and upgrade runs it in the foreground and checks its result.Security
/healthandcapa status.versionandrefvalues follow git's ref-name rules before they reach git. A lockfile pin must be a real SHA before it's used as a path.../ absolute-path check as install.capa status --jsoncan show them again.enc:v1:gets encrypted, and an encrypted empty secret is left as it is.Pwd=,password=) and base64-style keys are hidden, while paths and plain URLs stay visible.Docs
${PLUGIN_DATA}path and lifetime (docs(skills): correct the PLUGIN_DATA path and lifetime #318, closes capabilities-manager reference: PLUGIN_DATA path and lifetime are out of date #309), and the newcapa statusoutput and flags (feat(status): report the current project's state #344).Maintenance
github/codeql-action4.38.3 (chore(deps): bump github/codeql-action from 4.38.2 to 4.38.3 #303).Upgrade notes
capa installonce in each project after upgrading. This:capa statuscan track changes; until then a project shows "Out of date: installed by an older capa that did not record installs";argsandCLAUDE_PLUGIN_ROOT./<plugin id>-<skill>instead of being skipped or overwritten. To make another plugin's version the plain name, list it first incapabilities.yaml.versionorrefthat git itself would reject (for exampleHEAD~1ormain@{1}) now fails with an error naming the field.Screenshots / logs
N/A (see the individual PRs).
Test plan
Every PR above passed CI on Linux, Windows and macOS, plus CodeQL and dependency review, before merging into
develop.developCI is green at the release head (524ba88).developmerges intomaincleanly.mainonly has README-only commits thatdeveloplacks (Add X links for @InfragateAI and @AntonioZaitoun to README #340 and two direct edits).Local release build from
developat524ba88(Windows, isolated HOME, real stdio MCP server). Every item above was checked by running it:--jsonand--strict;CLAUDE_PLUGIN_ROOThooks, a hook event change,clean,initand the schema;install.ps1parse in PowerShell 5.1.This check found the bugs fixed in Search mode: explain tools hidden by expose: except on a running server #356, Install: show invalid ref and pin errors instead of Unknown error #357 and Status: count plugins that fail to resolve as install failures #358. Not run:
capa upgradeitself. The glab plugin still fails on Windows without Developer Mode, because git checks its symlinks out as plain files (see fix(registries): resolve GitLab marketplace plugins #314).After merge: tag
v2.4.0onmainand watchrelease.ymlbuild and publish the binaries.Checklist
bunx tsc --noEmitpassesbun run smellsclean vs base (or CI Qlty Smells job green)🤖 Generated with Claude Code