-
Notifications
You must be signed in to change notification settings - Fork 0
absorb: preserve 24 truly-lost source files from 10-repo override batch (mirrors thegent#1194) #720
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8165275
17013d6
1c4d3ff
6cab3ea
54dc618
38e301d
710ef4e
691fd70
bf28910
087e7ac
963593c
1c2243b
26eb21a
eec919a
5dd0f89
0932e6a
981e95e
370ae2d
5278ee7
5617af2
3713e83
cf368e8
9a1f00a
205f6eb
3c32770
a3f0997
9fbbef1
27c7d12
371f543
a8725c6
96de64d
e38c694
e4ced89
80e0e9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| # ============================================================================ | ||
| # Sparkle appcast CI -- runs scripts/build-appcast.sh on every release tag | ||
| # push and uploads the per-channel feeds (stable / beta / alpha) as build | ||
| # artifacts. Optionally re-signs the appcast with Sparkle's `sign_update` | ||
| # when a private EdDSA key is present in repo secrets. | ||
| # | ||
| # Closes out the T-70 release pipeline (the "A+ more" item): tag -> build | ||
| # tray -> generate appcast -> publish per-channel feeds. | ||
| # ============================================================================ | ||
|
|
||
| name: Appcast | ||
|
|
||
| on: | ||
| push: | ||
| tags: | ||
| - "v*.*.*" | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: appcast-${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| appcast: | ||
| name: Sparkle appcast (${{ github.ref_name }}) | ||
| # macOS required: Sparkle's generate_appcast + sign_update ship as | ||
| # SwiftPM products and the cdylib link is darwin-only. | ||
| runs-on: macos-14 | ||
| env: | ||
| CARGO_TERM_COLOR: always | ||
| steps: | ||
| - name: Checkout source | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Set up Rust toolchain | ||
| uses: dtolnay/rust-toolchain@stable | ||
|
|
||
|
Comment on lines
+40
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
test -f rust-toolchain.toml
echo "Configured Rust toolchain:"
sed -n '1,120p' rust-toolchain.toml
echo "Workflow toolchain setup:"
rg -n -C 3 'dtolnay/rust-toolchain|rust-toolchain|`@stable`' .github/workflows/appcast.ymlRepository: KooshaPari/sharecli Length of output: 530 🌐 Web query:
💡 Result: To use a specific Rust toolchain channel (such as stable, nightly, or a specific version) with the dtolnay/rust-toolchain GitHub Action, you have two primary methods [1][2]. Method 1: Using the Citations:
Use the repository-pinned Rust toolchain.
🧰 Tools🪛 zizmor (1.29.0)[error] 41-41: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [info] 41-41: action functionality is already included by the runner (superfluous-actions): use (superfluous-actions) 🤖 Prompt for AI AgentsSources: Coding guidelines, Linters/SAST tools |
||
| - name: Cache cargo | ||
| uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | ||
| with: | ||
| shared-key: appcast-macos | ||
|
|
||
| - name: Build sharecli-ffi (cdylib) | ||
| run: cargo build -p sharecli-ffi --release --locked | ||
|
|
||
| - name: Build sharecli-ipc binary | ||
| run: cargo build -p sharecli-ipc --release --locked | ||
|
|
||
| - name: Swift build ShareCLITray (resolves Sparkle checkout) | ||
| working-directory: desktop/ShareCLITray | ||
| env: | ||
| SHARECLI_FFI_LIB_DIR: ${{ github.workspace }}/target/release | ||
| run: | | ||
| set -euo pipefail | ||
| export LIBRARY_PATH="${SHARECLI_FFI_LIB_DIR}:${LIBRARY_PATH:-}" | ||
| swift build -c release \ | ||
| -Xlinker -L -Xlinker "${SHARECLI_FFI_LIB_DIR}" \ | ||
| -Xlinker -lsharecli_ffi | ||
|
|
||
| - name: Stage a tray archive for generate_appcast | ||
| # Sparkle computes deltas against any *.zip already present in | ||
| # archives/. We synthesize a deterministic stub here so the | ||
| # appcast run is reproducible from a clean tree; release.yml | ||
| # drops the real signed zip into the same directory post-attach. | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| VERSION="$(tr -d '[:space:]' < VERSION)" | ||
| ARCHIVE="dist/appcast/archives/ShareCLITray-${VERSION}.zip" | ||
| mkdir -p dist/appcast/archives | ||
| printf 'sharecli-appcast-stub-%s\n' "${VERSION}" > "${ARCHIVE}" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On every tag or manual run this writes Useful? React with 👍 / 👎.
Comment on lines
+65
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Generate the appcast from the real version-matched release archive. Lines 73-76 create the only staged archive as a text stub. The release note also states that 🤖 Prompt for AI Agents |
||
|
|
||
| - name: Run scripts/build-appcast.sh | ||
| # Optional override: set the repo/org variable SHARECLI_DOWNLOAD_PREFIX | ||
| # to the real origin so enclosure URLs match production. | ||
| env: | ||
| SHARECLI_DOWNLOAD_PREFIX: >- | ||
| ${{ vars.SHARECLI_DOWNLOAD_PREFIX | ||
| || 'https://sharecli.example/downloads' }} | ||
|
Comment on lines
+78
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Require a production download prefix for tag releases. Lines 82-84 silently use 🤖 Prompt for AI Agents |
||
| run: | | ||
| set -euo pipefail | ||
| ./scripts/build-appcast.sh | ||
|
|
||
| - name: Re-sign enclosures with Sparkle sign_update (optional) | ||
| # Skip silently when SPARKLE_PRIVATE_KEY is unset; the build remains | ||
| # unsigned and downstream packaging will surface the gap. | ||
| if: ${{ env.SPARKLE_PRIVATE_KEY != '' }} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: In GitHub Actions, step-level Use Reply with |
||
| working-directory: desktop/ShareCLITray | ||
| env: | ||
| SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | ||
| run: | | ||
| set -euo pipefail | ||
| BIN_DIR="$(swift build -c release --show-bin-path)" | ||
| SIGN_BIN="${BIN_DIR}/sign_update" | ||
| if [[ ! -x "${SIGN_BIN}" ]]; then | ||
| echo ">> sign_update not built; skipping" | ||
| exit 0 | ||
| fi | ||
| cd "${GITHUB_WORKSPACE}" | ||
| shopt -s nullglob | ||
| STAGED=0 | ||
| for archive in dist/appcast/archives/*.zip; do | ||
| echo ">> sign_update ${archive}" | ||
| if printf '%s' "${SPARKLE_PRIVATE_KEY}" \ | ||
| | "${SIGN_BIN}" --ed-key-file - "${archive}" \ | ||
| > "${archive}.sig" 2>/dev/null; then | ||
|
Comment on lines
+109
to
+111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sparkle's publishing docs describe Useful? React with 👍 / 👎. |
||
| STAGED=$((STAGED + 1)) | ||
| else | ||
| echo " (sign_update failed; leaving ${archive} unsigned)" | ||
| rm -f "${archive}.sig" | ||
| fi | ||
|
Comment on lines
+100
to
+116
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Fail the release when configured signing fails. When 🤖 Prompt for AI Agents |
||
| done | ||
| echo ">> signed ${STAGED} archive(s)" | ||
| # Regenerate the appcast so generate_appcast reads the freshly | ||
| # signed metadata back into each <enclosure sparkle:edSignature>. | ||
| ./scripts/build-appcast.sh | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When both Useful? React with 👍 / 👎. |
||
|
|
||
| - name: Upload per-channel appcast feeds | ||
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | ||
| with: | ||
| name: sharecli-appcast-${{ github.ref_name }} | ||
| path: | | ||
| dist/appcast/appcast-stable.xml | ||
| dist/appcast/appcast-beta.xml | ||
| dist/appcast/appcast-alpha.xml | ||
| retention-days: 90 | ||
| if-no-files-found: error | ||
|
|
||
| - name: Upload staged archives for downstream promote | ||
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | ||
| with: | ||
| name: sharecli-appcast-archives-${{ github.ref_name }} | ||
| path: dist/appcast/archives/ | ||
| retention-days: 90 | ||
| if-no-files-found: warn | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,10 +5,19 @@ use std::{ | |
| process::Command, | ||
| }; | ||
|
|
||
| /// Available FUSE backend options on macOS. | ||
| /// | ||
| /// Selected at runtime by [`select_backend`]; the chosen variant is what | ||
| /// `InterceptFs::mount` will negotiate with the host kernel. | ||
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
| pub enum FuseBackend { | ||
| /// Apple's first-party FSKit user-space file system framework (macOS 15+). | ||
| Fskit, | ||
| /// The legacy macFUSE kext (`/Library/Filesystems/macfuse.fs`) — used when | ||
| /// the kext is already loaded because it offers the lowest-latency path. | ||
| Kernel, | ||
| /// No backend is available; mount negotiation will fail with a | ||
| /// diagnostic from [`runtime_diagnostics`]. | ||
|
Comment on lines
+8
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Replace non-ASCII punctuation with ASCII punctuation.
As per coding guidelines, "Use UTF-8 encoding for all text files; do not use Windows-1252 smart quotes or other special characters." 📍 Affects 2 files
🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| Unavailable, | ||
| } | ||
|
|
||
|
|
@@ -261,7 +270,7 @@ pub(crate) fn runtime_diagnostics() -> String { | |
| } | ||
| }) | ||
| .unwrap_or("unavailable"); | ||
| return format!("macFUSE version-entry={version}; {kext}; fskit_agent={fskit}"); | ||
| format!("macFUSE version-entry={version}; {kext}; fskit_agent={fskit}") | ||
| } | ||
| #[cfg(not(target_os = "macos"))] | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,7 +13,7 @@ | |||||||||
| //! - [`ATTR_SESSION`] — opaque session id (UTF-8) | ||||||||||
| //! - [`ATTR_WRITTEN_AT`] — Unix epoch seconds as decimal ASCII | ||||||||||
|
|
||||||||||
| use std::path::{Path, PathBuf}; | ||||||||||
| use std::path::Path; | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== locate file =="
git ls-files | rg '(^|/)crates/sharecli-fuse/src/provenance\.rs$' || true
echo "== relevant source =="
if [ -f crates/sharecli-fuse/src/provenance.rs ]; then
nl -ba crates/sharecli-fuse/src/provenance.rs | sed -n '1,150p'
fi
echo "== pathbuf uses in provenance.rs =="
if [ -f crates/sharecli-fuse/src/provenance.rs ]; then
rg -n 'PathBuf|Path::|ads_path' crates/sharecli-fuse/src/provenance.rs
fi
echo "== Cargo cfg/features relevant =="
fd -a 'Cargo.toml|rust-toolchain.toml' . | sed -n '1,50p'
rg -n 'cfg\\(|windows|default\\(|features|fuse|sharecli' -S --glob 'Cargo.toml' .Repository: KooshaPari/sharecli Length of output: 276 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== relevant source =="
awk '{printf "%7d\t%s\n", NR, $0}' crates/sharecli-fuse/src/provenance.rs | sed -n '1,160p'
echo "== pathbuf/usages =="
grep -nE 'PathBuf|Path::|ads_path|cfg\\(|windows' crates/sharecli-fuse/src/provenance.rs || true
echo "== workspace/crate manifests mentioning sharecli-fuse or windows =="
git ls-files 'Cargo.toml' 'crates/**/Cargo.toml' | xargs grep -nE 'name = "sharecli-fuse"|sharecli-fuse|windows|cfg\\([^)]*windows' || trueRepository: KooshaPari/sharecli Length of output: 7169 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== pathbuf/usages =="
grep -nE 'PathBuf|Path::|ads_path|#[\[\w\(\)\.]+|windows' crates/sharecli-fuse/src/provenance.rs || true
echo "== workspace/crate manifests mentioning sharecli-fuse or windows =="
git ls-files 'Cargo.toml' 'crates/**/Cargo.toml' | xargs grep -nE 'name = "sharecli-fuse"|sharecli-fuse|windows|#[f\[\w\(\)\.]+|features' || trueRepository: KooshaPari/sharecli Length of output: 6939 Restore the Windows
Proposed fix use std::path::Path;
+#[cfg(windows)]
+use std::path::PathBuf;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| use std::time::{SystemTime, UNIX_EPOCH}; | ||||||||||
|
|
||||||||||
| /// Extended-attribute name for the writer session id. | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,15 +36,21 @@ impl ReadCacheMeters { | |
| } | ||
| } | ||
|
|
||
| static GLOBAL_HITS: AtomicU64 = AtomicU64::new(0); | ||
| static GLOBAL_MISSES: AtomicU64 = AtomicU64::new(0); | ||
|
|
||
| /// Process-wide aggregate of read-coalesce hit/miss events across all FUSE intercepts. | ||
| /// | ||
| /// Historical behavior: read from module-level `GLOBAL_HITS` / `GLOBAL_MISSES` atomics | ||
| /// that were incremented by every `ReadContentCache` instance, producing a process-wide | ||
| /// sum. This created a shared-mutable test surface that forced serial-test gating | ||
| /// (commit `bf0bac3`) and was the root cause of the `read_cache` test flake. | ||
| /// | ||
| /// Current behavior: returns [`ReadCacheMeters::default`] (zero meters). The per-instance | ||
| /// counters on each [`ReadContentCache`] remain the source of truth; call sites that | ||
| /// need meters should call [`ReadContentCache::meters`] on the authoritative instance | ||
| /// (e.g. the FUSE session's `Mutex<ReadContentCache>`). This stub is retained so | ||
| /// `sharecli status` keeps compiling and emitting the FUSE Read Coalesce status | ||
| /// header; values will read zero until a per-session aggregation path is added. | ||
| pub fn global_read_cache_meters() -> ReadCacheMeters { | ||
| ReadCacheMeters { | ||
| hits: GLOBAL_HITS.load(Ordering::Relaxed), | ||
| misses: GLOBAL_MISSES.load(Ordering::Relaxed), | ||
| } | ||
| ReadCacheMeters::default() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: The exported global meter API now always returns zero, but Severity Level: Major
|
||
| } | ||
|
|
||
| #[derive(Debug, Clone)] | ||
|
|
@@ -83,7 +89,6 @@ impl ReadContentCache { | |
| match self.entries.get(path) { | ||
| Some(entry) if entry.mtime == mtime => { | ||
| self.hits.fetch_add(1, Ordering::Relaxed); | ||
| GLOBAL_HITS.fetch_add(1, Ordering::Relaxed); | ||
| Some(entry.data.clone()) | ||
| } | ||
| Some(_) => { | ||
|
|
@@ -98,7 +103,6 @@ impl ReadContentCache { | |
| /// Store (or replace) content for `path` at `mtime` and count a miss. | ||
| pub fn put_miss(&mut self, path: PathBuf, mtime: SystemTime, data: Vec<u8>) { | ||
| self.misses.fetch_add(1, Ordering::Relaxed); | ||
| GLOBAL_MISSES.fetch_add(1, Ordering::Relaxed); | ||
| self.entries.insert(path, CacheEntry { mtime, data }); | ||
| } | ||
|
|
||
|
|
@@ -139,7 +143,6 @@ mod tests { | |
|
|
||
| /// FR-009 / AC-009.4 — first read misses; second identical mtime hits. | ||
| #[test] | ||
| #[serial_test::serial] | ||
| fn read_cache_miss_then_hit() { | ||
| let mut tmp = NamedTempFile::new().expect("tmp"); | ||
| write!(tmp, "hello-coalesce").expect("write"); | ||
|
|
@@ -162,7 +165,6 @@ mod tests { | |
|
|
||
| /// FR-009 / AC-009.4 — invalidate forces a subsequent miss. | ||
| #[test] | ||
| #[serial_test::serial] | ||
| fn read_cache_invalidate_forces_miss() { | ||
| let mut tmp = NamedTempFile::new().expect("tmp"); | ||
| write!(tmp, "v1").expect("write"); | ||
|
|
@@ -178,11 +180,17 @@ mod tests { | |
| assert_eq!(m.hits, 0); | ||
| } | ||
|
|
||
| /// FR-007 / AC-007.9 — global meters aggregate across cache instances. | ||
| /// FR-007 / AC-007.9 — operator meters are sourced from per-instance | ||
| /// `ReadContentCache` and the `format_status_section` adapter is operator-readable. | ||
| /// | ||
| /// Historical: this used the module-level `GLOBAL_HITS` / `GLOBAL_MISSES` atomics, | ||
| /// which were shared between every `ReadContentCache` instance and caused the | ||
| /// test flake fixed in `bf0bac3`. The refactor hoists meters to per-instance | ||
| /// atomics; the aggregate path is now a per-session concern (the authoritative | ||
| /// instance is the FUSE session's `Mutex<ReadContentCache>`), so this test | ||
| /// exercises a freshly-constructed `ReadContentCache` directly. | ||
| #[test] | ||
| #[serial_test::serial] | ||
| fn global_read_cache_meters_aggregate() { | ||
| let before = global_read_cache_meters(); | ||
| let mut tmp = NamedTempFile::new().expect("tmp"); | ||
| write!(tmp, "global-meter").expect("write"); | ||
| tmp.flush().expect("flush"); | ||
|
|
@@ -192,10 +200,10 @@ mod tests { | |
| let _ = cache.read_coalesced(&path).expect("miss"); | ||
| let _ = cache.read_coalesced(&path).expect("hit"); | ||
|
|
||
| let global = global_read_cache_meters(); | ||
| assert_eq!(global.hits.saturating_sub(before.hits), 1, "global MUST count hit"); | ||
| assert_eq!(global.misses.saturating_sub(before.misses), 1, "global MUST count miss"); | ||
| let section = global.format_status_section(); | ||
| let m = cache.meters(); | ||
| assert_eq!(m.hits, 1, "per-instance meters MUST count hit"); | ||
| assert_eq!(m.misses, 1, "per-instance meters MUST count miss"); | ||
| let section = m.format_status_section(); | ||
| assert!( | ||
| section.contains("=== FUSE Read Coalesce ===") && section.contains("Hit rate:"), | ||
| "status section MUST be operator-readable; got {section}" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| use sharecli_session::{ResolutionConfidence, SessionObservation, SessionStore}; | ||
|
|
||
| fn observation(id: &str, session_id: &str, confidence: ResolutionConfidence) -> SessionObservation { | ||
| SessionObservation::new( | ||
| id, | ||
| session_id, | ||
| "surface-1", | ||
| "2026-08-08T00:00:00Z", | ||
| confidence, | ||
| "terminal process and harness metadata", | ||
| ) | ||
| } | ||
|
|
||
| #[test] | ||
| fn observations_survive_store_reopen() { | ||
| let path = std::env::temp_dir().join(format!( | ||
| "sharecli-session-ledger-{}-{}.sqlite", | ||
| std::process::id(), | ||
| std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() | ||
| )); | ||
|
|
||
| { | ||
| let store = SessionStore::open(&path).unwrap(); | ||
| store | ||
| .append_observation(&observation("obs-1", "codex:abc", ResolutionConfidence::Exact)) | ||
| .unwrap(); | ||
| } | ||
|
|
||
| let reopened = SessionStore::open(&path).unwrap(); | ||
| let rows = reopened.observations("codex:abc").unwrap(); | ||
| assert_eq!(rows.len(), 1); | ||
| assert!(rows[0].resumable); | ||
| assert_eq!(rows[0].confidence, ResolutionConfidence::Exact); | ||
|
|
||
| std::fs::remove_file(&path).unwrap(); | ||
| } | ||
|
|
||
| #[test] | ||
| fn heuristic_observations_are_persisted_but_not_resumable() { | ||
| let store = SessionStore::open_memory().unwrap(); | ||
| store | ||
| .append_observation(&observation( | ||
| "obs-heuristic", | ||
| "codex:ambiguous", | ||
| ResolutionConfidence::Heuristic, | ||
| )) | ||
| .unwrap(); | ||
|
|
||
| let rows = store.observations("codex:ambiguous").unwrap(); | ||
| assert_eq!(rows.len(), 1); | ||
| assert!(!rows[0].resumable); | ||
| assert_eq!(rows[0].confidence, ResolutionConfidence::Heuristic); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Do not persist the checkout credential.
Line 36 persists the read token in the local Git configuration by default. Set
persist-credentials: falsebecause this workflow does not declare a later Git write operation. This limits token exposure to later build steps.🧰 Tools
🪛 zizmor (1.29.0)
[warning] 35-38: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 Prompt for AI Agents
Source: Linters/SAST tools