-
Notifications
You must be signed in to change notification settings - Fork 73
fix: implement decline applicant flow #253
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
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 |
|---|---|---|
|
|
@@ -10,28 +10,38 @@ jobs: | |
| check-schema: | ||
| name: Check schema is in sync | ||
| runs-on: ubuntu-latest | ||
| env: | ||
| BOUNDLESS_NESTJS_TOKEN: ${{ secrets.BOUNDLESS_NESTJS_TOKEN }} | ||
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
|
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify all workflow `uses:` entries are SHA pinned (40-hex)
rg -n '^\s*uses:\s*[^@]+@' .github/workflows/*.yml .github/workflows/*.yamlRepository: boundlessfi/bounties Length of output: 931 Pin GitHub Actions to commit SHAs instead of mutable tags.
🧰 Tools🪛 zizmor (1.25.2)[warning] 17-18: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) [error] 18-18: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI Agents |
||
|
|
||
| - name: Checkout canonical schema repo (private) | ||
| if: ${{ secrets.BOUNDLESS_NESTJS_TOKEN != '' }} | ||
| if: ${{ env.BOUNDLESS_NESTJS_TOKEN != '' }} | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: boundlessfi/boundless-nestjs | ||
| path: boundless-nestjs | ||
| token: ${{ secrets.BOUNDLESS_NESTJS_TOKEN }} | ||
| token: ${{ env.BOUNDLESS_NESTJS_TOKEN }} | ||
|
|
||
| - name: Note about private repo | ||
| if: ${{ secrets.BOUNDLESS_NESTJS_TOKEN == '' }} | ||
| if: ${{ env.BOUNDLESS_NESTJS_TOKEN == '' }} | ||
| run: | | ||
| echo "No secret BOUNDLESS_NESTJS_TOKEN provided; skipping checkout of private repo." | ||
| echo "If the canonical schema is in the private repo, add a repository secret named BOUNDLESS_NESTJS_TOKEN with a PAT that has access to boundlessfi/boundless-nestjs." | ||
| echo "This is expected for forked pull requests where repository secrets are not exposed." | ||
|
|
||
| - name: Set up Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
| node-version: "20" | ||
|
|
||
| - name: Run schema check | ||
| if: ${{ env.BOUNDLESS_NESTJS_TOKEN != '' }} | ||
| run: node ./scripts/sync-schema.js --check | ||
|
|
||
| - name: Skip schema check without private repo token | ||
| if: ${{ env.BOUNDLESS_NESTJS_TOKEN == '' }} | ||
| run: | | ||
| echo "Skipping schema sync check because BOUNDLESS_NESTJS_TOKEN is not available." | ||
| echo "Maintainers can run the full schema check from a trusted branch with the secret available." | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,33 @@ | ||
| "use client"; | ||
|
|
||
| import { useState } from "react"; | ||
| import { useMemo, useState } from "react"; | ||
| import { | ||
| Users, | ||
| CheckCircle, | ||
| Clock, | ||
| Star, | ||
| Trophy, | ||
| ArrowRight, | ||
| XCircle, | ||
| } from "lucide-react"; | ||
| import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Badge } from "@/components/ui/badge"; | ||
|
|
||
| import { useSelectApplicant } from "@/hooks/use-bounty-application"; | ||
| import { Textarea } from "@/components/ui/textarea"; | ||
| import { | ||
| AlertDialog, | ||
| AlertDialogAction, | ||
| AlertDialogCancel, | ||
| AlertDialogContent, | ||
| AlertDialogDescription, | ||
| AlertDialogFooter, | ||
| AlertDialogHeader, | ||
| AlertDialogTitle, | ||
| } from "@/components/ui/alert-dialog"; | ||
| import { toast } from "sonner"; | ||
| import { | ||
| useDeclineApplicant, | ||
| useSelectApplicant, | ||
| } from "@/hooks/use-bounty-application"; | ||
|
|
||
| export interface Application { | ||
| id: string; | ||
|
|
@@ -45,9 +59,14 @@ | |
| applications, | ||
| }: ApplicationReviewDashboardProps) { | ||
| const [selectedForCompare, setSelectedForCompare] = useState<string[]>([]); | ||
| const [reviewApplications, setReviewApplications] = useState(applications); | ||
| const [declineTarget, setDeclineTarget] = useState<Application | null>(null); | ||
| const [declineReason, setDeclineReason] = useState(""); | ||
| const { mutate: selectApplicant, isPending: isSelecting } = | ||
| useSelectApplicant(); | ||
|
|
||
| useEffect(() => { | ||
| setReviewApplications(applications); | ||
| }, [applications]); | ||
|
Comment on lines
+67
to
+69
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. Critical:
🐛 Proposed fix: import the real `useEffect` and delete the stubLine 2 (also drops the now-unused -import { useMemo, useState } from "react";
+import { useEffect, useState } from "react";Lines 317-319: -function useEffect(arg0: () => void, arg1: Application[][]) {
- throw new Error("Function not implemented.");
-}🤖 Prompt for AI Agents |
||
| const handleSelectApplicant = (applicantAddress: string) => { | ||
| selectApplicant({ | ||
| bountyId, | ||
|
|
@@ -56,6 +75,44 @@ | |
| }); | ||
| }; | ||
|
|
||
| const { mutate: declineApplicant, isPending: isDeclining } = | ||
| useDeclineApplicant(); | ||
|
|
||
| const handleDeclineApplicant = () => { | ||
| if (!declineTarget) return; | ||
|
|
||
| const previousApplications = reviewApplications; | ||
| const applicantAddress = declineTarget.applicantAddress; | ||
| const reason = declineReason.trim(); | ||
|
|
||
| setReviewApplications((current) => | ||
| current.filter((app) => app.applicantAddress !== applicantAddress), | ||
| ); | ||
|
|
||
| setSelectedForCompare((current) => | ||
| current.filter((id) => id !== declineTarget.id), | ||
| ); | ||
|
|
||
| declineApplicant( | ||
| { | ||
| bountyId, | ||
| applicantAddress, | ||
| reason, | ||
| }, | ||
| { | ||
| onSuccess: () => { | ||
| toast.success("Applicant declined"); | ||
| setDeclineTarget(null); | ||
| setDeclineReason(""); | ||
| }, | ||
| onError: () => { | ||
| setReviewApplications(previousApplications); | ||
| toast.error("Failed to decline applicant"); | ||
| }, | ||
| }, | ||
| ); | ||
| }; | ||
| const visibleApplications = reviewApplications; | ||
| const toggleCompare = (id: string) => { | ||
| if (selectedForCompare.includes(id)) { | ||
| setSelectedForCompare(selectedForCompare.filter((i) => i !== id)); | ||
|
|
@@ -113,6 +170,19 @@ | |
| {selectedForCompare.includes(app.id) ? "Comparing" : "Compare"} | ||
| </Button> | ||
| )} | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| className="border-red-500/50 text-red-500 hover:bg-red-500/10 hover:text-red-400" | ||
| onClick={() => { | ||
| setDeclineTarget(app); | ||
| setDeclineReason(""); | ||
| }} | ||
| disabled={isDeclining} | ||
| > | ||
| <XCircle className="size-4 mr-1" /> | ||
| Decline | ||
| </Button> | ||
| <Button | ||
| size="sm" | ||
| onClick={() => handleSelectApplicant(app.applicantAddress)} | ||
|
|
@@ -159,7 +229,7 @@ | |
| <div className="flex items-center justify-between"> | ||
| <h2 className="text-xl font-bold flex items-center gap-2"> | ||
| <Users className="size-5 text-primary" /> | ||
| Review Applications ({applications.length}) | ||
| Review Applications ({visibleApplications.length}) | ||
| </h2> | ||
| {selectedForCompare.length > 0 && ( | ||
| <Badge variant="outline" className="text-sm"> | ||
|
|
@@ -168,7 +238,7 @@ | |
| )} | ||
| </div> | ||
|
|
||
| {applications.length === 0 ? ( | ||
| {visibleApplications.length === 0 ? ( | ||
| <Card className="border-dashed border-gray-800 bg-transparent"> | ||
| <CardContent className="flex flex-col items-center justify-center py-12 text-center"> | ||
| <Users className="size-12 text-gray-600 mb-4" /> | ||
|
|
@@ -194,16 +264,57 @@ | |
| </Button> | ||
| </div> | ||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> | ||
| {applications | ||
| {visibleApplications | ||
| .filter((app) => selectedForCompare.includes(app.id)) | ||
| .map((app) => renderApplicationCard(app, false))} | ||
| </div> | ||
| </div> | ||
| ) : ( | ||
| <div className="grid gap-4"> | ||
| {applications.map((app) => renderApplicationCard(app, false))} | ||
| {visibleApplications.map((app) => renderApplicationCard(app, false))} | ||
| </div> | ||
| )} | ||
| <AlertDialog | ||
| open={!!declineTarget} | ||
| onOpenChange={(open) => { | ||
| if (!open) { | ||
| setDeclineTarget(null); | ||
| setDeclineReason(""); | ||
| } | ||
| }} | ||
| > | ||
| <AlertDialogContent> | ||
| <AlertDialogHeader> | ||
| <AlertDialogTitle>Decline applicant?</AlertDialogTitle> | ||
| <AlertDialogDescription> | ||
| This will remove the applicant from the review queue. You can | ||
| include an optional reason for internal records. | ||
| </AlertDialogDescription> | ||
| </AlertDialogHeader> | ||
|
|
||
| <Textarea | ||
| value={declineReason} | ||
| onChange={(event) => setDeclineReason(event.target.value)} | ||
| placeholder="Optional reason for declining this applicant" | ||
| className="min-h-24" | ||
| /> | ||
|
|
||
| <AlertDialogFooter> | ||
| <AlertDialogCancel disabled={isDeclining}>Cancel</AlertDialogCancel> | ||
| <AlertDialogAction | ||
| className="bg-red-600 text-white hover:bg-red-700" | ||
| disabled={isDeclining} | ||
| onClick={handleDeclineApplicant} | ||
| > | ||
| Decline applicant | ||
| </AlertDialogAction> | ||
| </AlertDialogFooter> | ||
| </AlertDialogContent> | ||
| </AlertDialog> | ||
| </div> | ||
| ); | ||
| } | ||
| function useEffect(arg0: () => void, arg1: Application[][]) { | ||
|
Check warning on line 317 in components/bounty/application-review-dashboard.tsx
|
||
| throw new Error("Function not implemented."); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -157,6 +157,97 @@ export function useSelectApplicant() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Hook: decline applicant | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type DeclinedApplicationRecord = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bountyId?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applicantAddress?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| declinedReason?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| declineReason?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| declinedAt?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type BountyWithApplications = BountyQuery & { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bounty?: BountyQuery["bounty"] & { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applications?: DeclinedApplicationRecord[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function useDeclineApplicant() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const qc = useQueryClient(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return useMutation({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mutationFn: async ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bountyId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applicantAddress, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reason, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bountyId: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applicantAddress: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reason?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bountyId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applicantAddress, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reason: reason?.trim() || undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| declinedAt: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onMutate: async ({ bountyId, applicantAddress, reason }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await qc.cancelQueries({ queryKey: bountyKeys.detail(bountyId) }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const prev = qc.getQueryData<BountyWithApplications>( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bountyKeys.detail(bountyId), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (prev?.bounty?.applications) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const declinedAt = new Date().toISOString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qc.setQueryData<BountyWithApplications>(bountyKeys.detail(bountyId), { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...prev, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bounty: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...prev.bounty, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applications: prev.bounty.applications | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map((application) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| application.applicantAddress === applicantAddress | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...application, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: "DECLINED", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| declineReason: reason?.trim() || undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| declinedReason: reason?.trim() || undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| declinedAt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : application, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (application) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| application.applicantAddress !== applicantAddress, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updatedAt: declinedAt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+214
to
+231
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.
The Drop the filter so the declined record (with its reason) persists; the dashboard already removes the applicant from its own visible list. 🐛 Proposed fix: keep the declined record instead of filtering it out applications: prev.bounty.applications
.map((application) =>
application.applicantAddress === applicantAddress
? {
...application,
status: "DECLINED",
declineReason: reason?.trim() || undefined,
declinedReason: reason?.trim() || undefined,
declinedAt,
}
: application,
- )
- .filter(
- (application) =>
- application.applicantAddress !== applicantAddress,
- ),
+ ),📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { prev, bountyId }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onError: (_error, _variables, context) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (context?.prev) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qc.setQueryData(bountyKeys.detail(context.bountyId), context.prev); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onSettled: (_result, _error, variables) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qc.invalidateQueries({ queryKey: bountyKeys.detail(variables.bountyId) }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qc.invalidateQueries({ queryKey: bountyKeys.lists() }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Hook: submit work (BountyRegistry.submit_work) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
🧩 Analysis chain
🏁 Script executed:
Repository: boundlessfi/bounties
Length of output: 1670
🏁 Script executed:
Repository: boundlessfi/bounties
Length of output: 107
🏁 Script executed:
Repository: boundlessfi/bounties
Length of output: 2727
🏁 Script executed:
Repository: boundlessfi/bounties
Length of output: 1689
Disable credential persistence on checkout steps in
.github/workflows/check-schema.yml.actions/checkout@v4should setpersist-credentials: falseon both checkout steps (lines 17-18 and 20-26) to prevent the token/credentials from being written to the runner’s local git config for subsequent steps.Suggested fix
- name: Checkout repository uses: actions/checkout@v4 + with: + persist-credentials: false - name: Checkout canonical schema repo (private) if: ${{ env.BOUNDLESS_NESTJS_TOKEN != '' }} uses: actions/checkout@v4 with: repository: boundlessfi/boundless-nestjs path: boundless-nestjs token: ${{ env.BOUNDLESS_NESTJS_TOKEN }} + persist-credentials: false🧰 Tools
🪛 zizmor (1.25.2)
[warning] 17-18: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 18-18: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents