Visitar URL original
Add the 1Password CLI as an Optional Managed Host Tool by ptr727 · Pull Request #4161 · ptr727/ProjectTemplate · GitHub
Skip to content

Add the 1Password CLI as an Optional Managed Host Tool - #4161

Merged
ptr727 merged 13 commits into
developfrom
feature/op-host-tool
Nov 13, 2026
Merged

ptr727 merged 13 commits into
developfrom
feature/op-host-tool

Conversation

@ptr727

@ptr727 ptr727 commented Nov 13, 2026 •

Copy link
Copy Markdown
Owner

Summary

Makes the 1Password CLI (op) an optional managed host tool on both installers.

A repository that reads its secrets from a 1Password vault needs op on its hosts. Its own host-tools.json cannot declare the Linux source, since an overlay's install metadata names only a package, never an apt repository or a signing key. So the hub manages it.

  • spec/host-tools.json gains an op entry: required: false, no floor, probe op --version, with source and remedy for Linux, macOS, and Windows. Its pattern reads a line holding only the version, which is what the probe prints, and tolerates a pre-release suffix, since an unreadable version fails the gate even for an optional tool.
  • host-setup/linux/install-tools.sh gains op_* functions modeled on gh_install. The keyring at downloads.1password.com/linux/keys/1password.asc is proved against the repository's own InRelease rather than pinned. The sources file is deb822 1password.sources naming a keyring under /etc/apt/keyrings. The vendor's one-line 1password.list and its /usr/share/keyrings keyring are removed once the new source is written, so apt never sees two Signed-By values for one repository and a key that fails its proof leaves the vendor's source working. A new register_apt_source helper holds that order for gh, node, docker, and op, so gh and docker now remove their own predecessors after the replacement too. The repository carries amd64, arm64, and armhf, and any other architecture is a named skip.
  • A manual download at /usr/local/bin/op shadows the package's /usr/bin/op. It is removed after a prompt, and only beside an installed package, so a failed fetch or install leaves the working copy. An unowned op anywhere else can be a wrapper or a version manager's shim, so it is reported and left in place.
  • Ripgrep, PowerShell, op, and tool_unshadow's shadow loop share new path_is_unowned, unowned_command_path, and remove_unowned_command helpers. Ripgrep and the shadow loop previously read a failed dpkg-query as "unowned" and offered to remove the file. The shared strict test stops the run instead, whatever errexit state it runs in, and a failed rm warns once rather than looping.
  • install_keyring, write_sources, and microsoft_feed run with errexit off under apply_tool, and the step after each removes a working source or copy. A failed write in any of them now ends the run, found by Copilot on the first head.
  • The one hard-coded optional tool on Linux becomes an OPTIONAL_TOOLS list holding powershell and op.
  • host-setup/windows/install-tools.ps1 gains an AgileBits.1Password.CLI record and a tool-level $OPTIONAL_TOOLS list with the Linux semantics. An optional tool is left out of the default selection unless -Optional is given or the tool is named, and -List always shows it, marked (optional tool).
  • unmanaged is defined by the absent sources file rather than by a distro install, which was false for op, in the README, a comment, and the apply log line.
  • The usage examples on both platforms no longer say a bare run reports or upgrades every tool.
  • Docs: docs/host-setup.md, host-setup/linux/README.md, host-setup/windows/README.md (Differences table). No new cspell.json words, since CI spell-checks only README.md and HISTORY.md.

Measured while building it

  • The 1password-cli package ships only /usr/bin/op. Its install script adds an onepassword-cli group and makes the binary set-group-ID. It writes no apt source or keyring.
  • The 1Password desktop app package writes /etc/apt/sources.list.d/1password.sources on each of its installs, after copying its key to /usr/share/keyrings. No package owns that key as a file. This change uses the same sources file name on purpose. A different name would leave apt with two sources for one repository disagreeing on Signed-By, which apt refuses. On a host carrying both, the desktop app's upgrade rewrites the file and the next run here writes it back, and apt reads one consistent source either way.
  • The vendor's debsig step is skipped because the signed apt repository is the trust path, as for every other repository here. The package's own debsig signature measured as SHA-512, so this change does not claim debsig rejects it.

Awaiting maintainer

bootstrap.sh --dev and the Windows dev stand-up run the installers with --optional / -Optional, so a dev stand-up now also installs op. On Linux that adds 1Password's apt source and keyring, the onepassword-cli group, and a set-group-ID binary. On Windows, -Optional previously selected only the extra .NET SDK lines. This follows the requested powershell semantics, and whether a dev stand-up should carry op is the maintainer's call.

Local review

Every push carried a recorded local pass, three before the opening push. Their findings are fixed above, apart from these declines:

  • remove_stale of /usr/share/keyrings/1password-archive-keyring.gpg could break the desktop app's source. Declined: that package's install script recreates the key before writing its sources file, and no package owns the key.

Findings carried past an edit budget, with their outcomes

Each push carried recorded passes, and where a push's two edit rounds were spent, its remaining findings were fixed in the next push. All are closed on the head, whose final pass recorded no findings.

  • op_install removed /usr/local/bin/op under --install beside an existing package: fixed in 0545b7b.
  • The report note claimed no upstream version with the vendor's 1password.list in place: fixed in 0545b7b.
  • The "still shadowed, --upgrade removes it" line fired for op: fixed in 0545b7b.
  • The Windows README misdescribed the optional-tool test: fixed in 0545b7b.
  • unmanaged wording and the desktop-app write-back claim: fixed in 3628aca, c971f77, 7f1abb4, and 9aac122.
  • A failed keyring or sources write let the predecessor source go (Copilot): fixed in 99548df, and the Microsoft feed sibling in cebc62d.
  • The floor test duplicated the remedy check: fixed in 99548df.
  • Usage examples, comment scope, and the InRelease docs claim: fixed in cebc62d and 263eeb0.
  • The per-site registration order and the run-ending cases in the docs: fixed in 2c36667.

Pre-existing findings, not filed

  • host-setup/linux/install-tools.sh powershell_install, pre-existing correctness: powershell_non_apt_paths runs inside a process substitution, so a failed dpkg-query there ends only that subshell and the install carries on with a partial path list. Not filed from this seat, since this pull request is its only write. It is reported to the dispatching session to file.

Verification

  • spec/validate.py: OK.
  • python3 -m unittest test_install_tools test_host_gate test_bootstrap from tests/, on the tree merged with current develop: OK. New cases drive the Linux functions with stubs and the Windows selection and list through a pwsh harness over the script's own registry. Each new guard was deleted in turn and a test failed each time.
  • scripts/docker_lint.py (shellcheck, shfmt, markdownlint, PSScriptAnalyzer, editorconfig-checker): clean. ruff and mypy: clean.
  • Read-only on a Debian 13 host: install-tools.sh --list marks op optional, and --report op reads it unavailable with the repository-not-configured note.
  • Not verified natively: a real Linux install, its idempotent repeat, and the host gate after it, since each changes the host and waits on the maintainer. No Windows or macOS run.

🤖 Generated with Claude Code

ptr727 and others added 4 commits November 13, 2026 09:10
A repository that reads its secrets from a 1Password vault needs op on
its hosts, and its own host-tools.json cannot declare the Linux source:
an overlay names only a package, never an apt repository or a signing
key. So the hub manages it.

The spec gains an optional, presence-only op entry. The Linux installer
registers 1Password's apt repository with its key proved against the
repository's own InRelease, removes the vendor's one-line .list and old
keyring, and removes an unowned download that would shadow /usr/bin/op.
The Windows installer installs AgileBits.1Password.CLI from winget.

Both installers hold op out of the default selection unless --optional
or -Optional is given or op is named. Linux generalizes its one
hard-coded optional tool into an OPTIONAL_TOOLS list, and Windows gains
the same list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A local review pass found that the downloaded op went before the
repository was proved, so a failed fetch left the host with none, and
that any unowned op on PATH was removed, a wrapper or a shim included.
The download now goes only from /usr/local/bin, the vendor's manual
install path, and only once apt has installed the package.

The ripgrep and op download removals share one helper built on a strict
ownership test, so a failed dpkg query stops the run for both rather
than reading as unowned. The op pattern reads a pre-release suffix,
since an unreadable version fails the gate even for an optional tool.
The unmanaged log line no longer says the distro installed the tool.
The Windows wording drops its count of optional tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A second local review pass found that --install could remove a newer
downloaded op beside an outdated package, downgrading what PATH
resolves to. apply_tool keeps its original unshadow condition, so
--install leaves that copy as it leaves a newer jq.

The predecessor sources file and keyring for gh, docker, and op are
removed once the replacement source is written, so a key that fails its
proof leaves the old source working. The shared download removal ends
the run on a failed ownership query whatever errexit state it runs in,
and stops rather than loops when rm fails. tool_unshadow's own loop and
the pwsh resolver move onto the same strict ownership test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings November 13, 2026 17:41
@ptr727 ptr727 added the comments Permits the comment lines the pull request adds or edits, which the prose gate otherwise refuses label Nov 13, 2026
@coderabbitai

coderabbitai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 47 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 815c5ff and 2c36667.

Files selected for processing: 9

  • docs/host-setup.md
  • host-setup/linux/README.md
  • host-setup/linux/install-tools.sh
  • host-setup/windows/README.md
  • host-setup/windows/install-tools.ps1
  • spec/host-tools.json
  • tests/test_bootstrap.py
  • tests/test_host_gate.py
  • tests/test_install_tools.py
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@ptr727

ptr727 commented Nov 13, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head 4d2844ca1d791b111685952b5f284cc3ad61aba3, the content this pull request carries at that commit against develop, and it recorded 4 findings.

The opening push's third local pass found that op_install removed
/usr/local/bin/op under --install even where the package was already
installed, through the vendor's one-line source or by hand. It now
removes the download under --install only where the run added the
package, and always under --upgrade.

The report names the vendor's 1password.list when that is the source in
place rather than saying apt reads no upstream version. apply_tool's
"still shadowed" line is limited to the binary tools whose shadow
--upgrade removes. The Windows README states what the optional-tool
test actually checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Several active correctness issues remain, including a critical failure path that can remove the only working APT source after a replacement write fails, along with CLI removal and diagnostic inaccuracies.

5 open issues
What changed in this PR

Adds the optional 1Password CLI (op) as a managed host tool across the host contract, Linux and Windows installers, documentation, and regression tests.

Changes:

  • Adds op metadata, probes, platform sources, remedies, and optional selection behavior.
  • Extends Linux installation with repository setup, key verification, architecture handling, and shadow management.
  • Adds Windows package registration and updates installer, host-gate, bootstrap, and documentation coverage.
File Description
tests/​test_install_tools.py Tests Linux and Windows optional-tool selection and Linux op installation behavior.
tests/​test_host_gate.py Tests the optional op host-gate metadata and version parsing.
tests/​test_bootstrap.py Validates installer remedies, registries, and optional-tool declarations.
spec/​host-tools.json Declares the optional op host tool and platform-specific remedies.
host-setup/​windows/​README.md Documents Windows op support, optional-tool behavior, and installer verification coverage.
host-setup/​windows/​install-tools.ps1 Registers the 1Password CLI package and implements optional selection and listing behavior.
host-setup/​linux/​README.md Documents Linux op installation, repository migration, shadow handling, and package behavior.
host-setup/​linux/​install-tools.sh Adds Linux repository management, op installation, shadow handling, and generalized optional-tool logic.
docs/​host-setup.md Documents op in the fleet host-tool contract and platform setup guidance.

Review effort: Lite

Comment thread host-setup/linux/install-tools.sh Resolved
Comment thread host-setup/linux/install-tools.sh Resolved
Comment thread host-setup/linux/install-tools.sh Resolved
Comment thread host-setup/linux/install-tools.sh Resolved
Comment thread host-setup/windows/README.md Resolved
@codecov

codecov Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.75%. Comparing base (b38e084) to head (2c36667).
⚠️ Report is 196 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #4161      +/-   ##
===========================================
- Coverage    81.60%   76.75%   -4.86%     
===========================================
  Files           26       41      +15     
  Lines         9070    14279    +5209     
===========================================
+ Hits          7402    10960    +3558     
- Misses        1668     3319    +1651     
Flag Coverage Δ
python-3.13 76.75% <ø> (-4.86%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

A further local pass found that the desktop app's sources file is
written back only by a run that installs or upgrades op, not by any run,
and that the unmanaged log line still misdescribed a package installed
through the vendor's own one-line source. The report-note test no
longer depends on whether the host carries that source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ptr727

ptr727 commented Nov 13, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head 3628aca5afa5ec5f64fc6385932fd3ec7647753b, the content this pull request carries at that commit against develop, and it recorded 1 finding.

ptr727 and others added 2 commits November 13, 2026 10:00
A local pass found the README's definition of unmanaged, and the
tool_configured comment, still saying the distro installed the tool.
That is false for op, which no distro carries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A local pass found the reworded definition still tied gh and node to a
distro install, which is false for gh from its vendor's one-line source,
and found the apply_tool comment beside the unmanaged log line still
naming the distro. Both now speak of whatever apt already has.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ptr727

ptr727 commented Nov 13, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head 7f1abb422acd5230811262632a04e910986601ca, the content this pull request carries at that commit against develop, and it recorded 1 finding.

ptr727 and others added 2 commits November 13, 2026 10:13
A local pass found the definition read as covering every tool, while
only gh, node, and op have a sources file the report checks. A docker
install from Docker's older one-line source reads current, not
unmanaged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot found that write_sources runs inside an if, where errexit is
off, so a failed write still reported a change, and the predecessor
source was then removed with nothing to replace it. install_keyring
had the same gap. Both now stop the run on a failed write.

A local pass found the floor test and the new remedy test asserting the
same installer check, so the floor test keeps only its own checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ptr727

ptr727 commented Nov 13, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head 99548dff20e866b131f6eaf0362656fb080d3a08, the content this pull request carries at that commit against develop, and it recorded 4 findings.

ptr727 and others added 2 commits November 13, 2026 10:31
A local pass found that microsoft_feed's dpkg -i is the same class as
the keyring and sources writes: a failure under errexit-off let
powershell_install go on to remove a working pwsh. It now stops the run.

The usage examples no longer say a bare run reports or upgrades every
tool, since the optional tools join only under --optional. The new
comments state the actual reason for stopping, and the docs no longer
claim every key here is proved against its InRelease, since Microsoft's
arrives inside its own package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A local pass found the new comments naming every caller as one that
removes an old source, while node removes none and only the PowerShell
install removes a working pwsh. The keyring write's stop now has a test
of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ptr727

ptr727 commented Nov 13, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head 263eeb0d9663295401b5da26a96fec7b409d818c, the content this pull request carries at that commit against develop, and it recorded 3 findings.

A local pass found the keyring, sources, and predecessor-removal order
written out by hand in gh, docker, and op, with only op's order tested.
register_apt_source now holds that order for gh, node, docker, and op,
so one test covers it.

The run-ending cases in apply_tool's comment and the Linux README now
include a failed keyring, sources, or Microsoft feed write. The keyring
file's own stop gains a test beside the directory's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ptr727

ptr727 commented Nov 13, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head 2c366671366418a1228dbb5d7ab372e99ad9ad93, the content this pull request carries at that commit against develop, and it recorded 0 findings.

@ptr727

ptr727 commented Nov 13, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@ptr727

ptr727 commented Nov 13, 2026

Copy link
Copy Markdown
Owner Author

/review

@coderabbitai

coderabbitai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ptr727

ptr727 commented Nov 13, 2026

Copy link
Copy Markdown
Owner Author

Native Verification on Linux

Run at head 2c366671 on a Proxmox VE 9 host (Debian 13, amd64) where op was absent. The maintainer ran the two real installs. The reads before and after were run by the orchestrating session.

Step Command Result
Before install-tools.sh --report op unavailable, repository not configured
Plan install-tools.sh --install --dry-run op Four planned changes: the keyring under /etc/apt/keyrings, 1password.sources, apt-get update, apt-get install -y 1password-cli. Host unchanged afterwards
Install install-tools.sh --install op op 2.42.0 at /usr/bin/op, package 1password-cli 2.42.0-1
Idempotence install-tools.sh --install op again The maintainer reported nothing changed. A dry run afterwards prints op: current at 2.42.0-1, leaving it alone and Nothing changed
After install-tools.sh --report op 2.42.0-1 installed and available, source downloads.1password.com, status current
Gate python3 scripts/host_gate.py [ok] host-tools 0 issue(s), with op 2.42.0 present, and no floor is declared for it

State left on the host: a deb822 1password.sources naming Signed-By: /etc/apt/keyrings/1password-archive-keyring.gpg, and no keyring under /usr/share/keyrings.

Not verified natively: Windows and macOS, the path that skips removing a newer /usr/local/bin/op, and the note for a vendor-written 1password.list.

@ptr727
ptr727 merged commit e319962 into develop Nov 13, 2026
13 checks passed
@ptr727
ptr727 deleted the feature/op-host-tool branch November 13, 2026 19:46
ptr727 added a commit that referenced this pull request Nov 13, 2026
… Rules (#4189)

## Summary

Promotes the backlog-burndown rounds and changes from other hub sessions
from `develop` to `main`.

- Hooks and the prose gate: #4132, #4134, #4136, #4145, #4152, #4184.
The Husky hook reads its path list once and diffs a merge against
`MERGE_HEAD`. Interrupts re-raise their signal, a Markdown code span
reads as quoted data, and the gate picks a commit's diff base and
recognizes a conflicted promotion's branch cut from `main`.
- Procedures and carried rules: #4133, #4139, #4141, #4149, #4166,
#4177, #4179, #4183, #4185. Resync, audit and check-this-repo procedure
gaps, code style and shell interrupt rules, review-loop gaps across four
skills, the context and delegation section of `AGENTS.md`, question
rules in governance, the promotion line in pull request bodies,
closing-keyword behavior on promotion, and the history-rewrite premise.
- Review and cleanup tooling: #4138, #4148, #4154, #4156, #4157, #4163,
#4164, #4168, #4174, #4180. Cleanliness reads, parked handoffs, the
backlog report's JSON output, exact inline script pins, trusted inputs,
env-file includes, the bounded review wait, side-reviewer matching, and
a Copilot billing-failure notice read as a refusal.
- Workflow and runbook contract: #4153, #4155, #4169, #4171, #4178,
#4182. The runtime-secrets check, a registry entry and spec references,
the Copilot runbook pointers, credential-free checkout steps, and the
deploy-key and runner wording.
- From other hub sessions: #4159 (a repository's own .NET line under a
fleet default), #4161 (the 1Password CLI as an optional host tool),
#4162 (Codex's Windows installer and one managed opencode copy). These
carry no promotion line.

#4182 carries no promotion line by design, and #4175 holds the split-out
part of #4057's scope. Both #1448 and #4175 stay open.

Fixes #1071
Fixes #1154
Fixes #1391
Fixes #1471
Fixes #1594
Fixes #1620
Fixes #1627
Fixes #1650
Fixes #1801
Fixes #2853
Fixes #3620
Fixes #3668
Fixes #3697
Fixes #3766
Fixes #3787
Fixes #3837
Fixes #3840
Fixes #3842
Fixes #3844
Fixes #3865
Fixes #3882
Fixes #3890
Fixes #3894
Fixes #3912
Fixes #3916
Fixes #3954
Fixes #4014
Fixes #4015
Fixes #4016
Fixes #4043
Fixes #4050
Fixes #4057
Fixes #4058
Fixes #4062
Fixes #4084
Fixes #4097
Fixes #4098
Fixes #4099
Fixes #4104
Fixes #4108
Fixes #4114
Fixes #4115
Fixes #4117
Fixes #4118
Fixes #4119
Fixes #4121
Fixes #4122
Fixes #4123
Fixes #4131
Fixes #4135
Fixes #4140
Fixes #4142
Fixes #4143
Fixes #4144
Fixes #4151

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comments Permits the comment lines the pull request adds or edits, which the prose gate otherwise refuses

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants