Priority: high · Unlocks reproducible installs across team members and across agent sessions.
Problem
`allagents` has no way to pin a skill or plugin to a specific Git ref. `src/utils/plugin-path.ts::parseGitHubUrl` recognizes `branch` (via `/tree/` in a URL) but doesn't accept an `owner/repo@ref` shorthand, and `@` in install args is already taken by the marketplace shorthand (`my-plugin@official`). Consequences:
- Two team members running `allagents update` on different days can get different skill bodies.
- An agent running in CI cannot guarantee reproducible session-startup state.
- There is no audit trail for which version of a skill a deployed agent was running.
By contrast `gh skill install` supports both inline `@v1.2.0` and `--pin ` (mutually exclusive). Implementation reference: `pkg/cmd/skills/install/install.go::parseSkillFromOpts` and `resolveVersion` in `cli/cli`.
Current behavior
```bash
$ allagents plugin install --help | grep -E 'pin|version|@'
$ allagents plugin install my-plugin@official # @ means marketplace, NOT version
$ allagents plugin install my-plugin@official --scope user
No --pin flag exists. Passing a tag-style @ref does not work:
$ allagents plugin install owner/repo@v1.2.0
Treats v1.2.0 as a marketplace name and fails to resolve
workspace.yaml has no pin field; sync-state records no resolved ref:
$ cat .allagents/sync-state.json
{
"version": 1,
"lastSync": "2026-05-12T10:54:56.311Z",
"files": { "claude": [] },
"mcpServers": { "claude": [] }
}
```
Expected behavior
```bash
Inline pin
$ allagents skills add foo --from owner/repo@v1.2.0
✓ Added foo from owner/repo (pinned to v1.2.0)
Explicit flag (equivalent)
$ allagents skills add foo --from owner/repo --pin v1.2.0
✓ Added foo from owner/repo (pinned to v1.2.0)
Inline + --pin → conflict
$ allagents skills add foo --from owner/repo@v1.2.0 --pin abc1234
Error: cannot combine inline @Version with --pin
workspace.yaml form
plugins:
- source: owner/repo
pin: v1.2.0 # new optional field
sync-state.json records the resolved ref + SHA so subsequent updates can detect drift
$ cat .allagents/sync-state.json | jq .sources
{
"owner/repo": {
"pluginSpec": "owner/repo",
"resolvedRef": "v1.2.0",
"resolvedSha": "abc1234...",
"pinnedRef": "v1.2.0"
}
}
```
Verification gate (must pass before closing)
```bash
set -euo pipefail
bun run build
WS=$(mktemp -d)
cd "$WS"
allagents workspace init --client claude
(1) Inline @Version is accepted (use any small public skills repo with a tagged release)
allagents skills add brainstorming --from anthropics/superpowers@v0.1.0
test -d .agents/skills/brainstorming/
(2) --pin flag is accepted and equivalent
rm -rf .agents
allagents skills add brainstorming --from anthropics/superpowers --pin v0.1.0
test -d .agents/skills/brainstorming/
(3) Inline + --pin together is rejected
! allagents skills add brainstorming --from anthropics/superpowers@v0.1.0 --pin v0.1.0
(4) Resolved ref is recorded in sync-state
jq -e '.sources["anthropics/superpowers"].pinnedRef == "v0.1.0"' .allagents/sync-state.json
jq -e '.sources["anthropics/superpowers"].resolvedSha | length >= 7' .allagents/sync-state.json
(5) workspace.yaml round-trips a pin field
cat > .allagents/workspace.yaml <<YAML
clients: [claude]
plugins:
- source: anthropics/superpowers
pin: v0.1.0
YAML
allagents update
jq -e '.sources["anthropics/superpowers"].pinnedRef == "v0.1.0"' .allagents/sync-state.json
cd / && rm -rf "$WS"
```
All five checks must pass. (Substitute the example repo/tag with a stable public alternative if needed; the integration test should use a known stable source.)
Implementation notes
- `src/utils/plugin-path.ts::parseGitHubUrl`: extend to recognize `owner/repo@` shorthand. Distinguish from `name@marketplace` by checking whether the left side looks like `owner/repo` (contains a slash) — `@` after a slash is a Git ref; `@` without a slash is a marketplace.
- `src/cli/commands/plugin-skills.ts::addCmd`: add `--pin ` option. Validate mutual exclusivity with inline `@version` in the positional.
- `src/cli/commands/plugin.ts::installCmd` (`plugin install`): same `--pin` flag.
- `src/core/plugin.ts::fetchPlugin`: extend signature to accept `{ ref?: string }` and return the resolved ref + SHA. The shallow-clone path needs to clone the specific ref (or fetch + checkout).
- `src/models/workspace-config.ts::PluginEntrySchema`: add an optional `pin: z.string().optional()` field to the object form.
- `src/models/sync-state.ts::SyncStateSchema`: add an optional `sources` record keyed by plugin spec with `{ pluginSpec, resolvedRef, resolvedSha, pinnedRef? }`. Don't break the existing `files` field — additive only.
- This issue partially overlaps with content-hash tracking; the `sources` block above is designed to be a strict subset of the schema proposed in the companion lockfile-hashes issue, so the two can land together cleanly.
Refs
- Reference impl: `cli/cli` `pkg/cmd/skills/install/install.go::parseSkillFromOpts`, `resolveVersion`.
- Companion wiki page: `concepts/allagents-vs-gh-skill.md` § "Pinning vs declarative re-sync".
- Related issue: content-hash tracking in sync-state (filed separately).
Problem
`allagents` has no way to pin a skill or plugin to a specific Git ref. `src/utils/plugin-path.ts::parseGitHubUrl` recognizes `branch` (via `/tree/` in a URL) but doesn't accept an `owner/repo@ref` shorthand, and `@` in install args is already taken by the marketplace shorthand (`my-plugin@official`). Consequences:
By contrast `gh skill install` supports both inline `@v1.2.0` and `--pin ` (mutually exclusive). Implementation reference: `pkg/cmd/skills/install/install.go::parseSkillFromOpts` and `resolveVersion` in `cli/cli`.
Current behavior
```bash
$ allagents plugin install --help | grep -E 'pin|version|@'
$ allagents plugin install my-plugin@official # @ means marketplace, NOT version
$ allagents plugin install my-plugin@official --scope user
No --pin flag exists. Passing a tag-style @ref does not work:
$ allagents plugin install owner/repo@v1.2.0
Treats v1.2.0 as a marketplace name and fails to resolve
workspace.yaml has no pin field; sync-state records no resolved ref:
$ cat .allagents/sync-state.json
{
"version": 1,
"lastSync": "2026-05-12T10:54:56.311Z",
"files": { "claude": [] },
"mcpServers": { "claude": [] }
}
```
Expected behavior
```bash
Inline pin
$ allagents skills add foo --from owner/repo@v1.2.0
✓ Added foo from owner/repo (pinned to v1.2.0)
Explicit flag (equivalent)
$ allagents skills add foo --from owner/repo --pin v1.2.0
✓ Added foo from owner/repo (pinned to v1.2.0)
Inline + --pin → conflict
$ allagents skills add foo --from owner/repo@v1.2.0 --pin abc1234
Error: cannot combine inline @Version with --pin
workspace.yaml form
plugins:
pin: v1.2.0 # new optional field
sync-state.json records the resolved ref + SHA so subsequent updates can detect drift
$ cat .allagents/sync-state.json | jq .sources
{
"owner/repo": {
"pluginSpec": "owner/repo",
"resolvedRef": "v1.2.0",
"resolvedSha": "abc1234...",
"pinnedRef": "v1.2.0"
}
}
```
Verification gate (must pass before closing)
```bash
set -euo pipefail
bun run build
WS=$(mktemp -d)
cd "$WS"
allagents workspace init --client claude
(1) Inline @Version is accepted (use any small public skills repo with a tagged release)
allagents skills add brainstorming --from anthropics/superpowers@v0.1.0
test -d .agents/skills/brainstorming/
(2) --pin flag is accepted and equivalent
rm -rf .agents
allagents skills add brainstorming --from anthropics/superpowers --pin v0.1.0
test -d .agents/skills/brainstorming/
(3) Inline + --pin together is rejected
! allagents skills add brainstorming --from anthropics/superpowers@v0.1.0 --pin v0.1.0
(4) Resolved ref is recorded in sync-state
jq -e '.sources["anthropics/superpowers"].pinnedRef == "v0.1.0"' .allagents/sync-state.json
jq -e '.sources["anthropics/superpowers"].resolvedSha | length >= 7' .allagents/sync-state.json
(5) workspace.yaml round-trips a pin field
cat > .allagents/workspace.yaml <<YAML
clients: [claude]
plugins:
pin: v0.1.0
YAML
allagents update
jq -e '.sources["anthropics/superpowers"].pinnedRef == "v0.1.0"' .allagents/sync-state.json
cd / && rm -rf "$WS"
```
All five checks must pass. (Substitute the example repo/tag with a stable public alternative if needed; the integration test should use a known stable source.)
Implementation notes
Refs