Skip to content

Commit a60df85

Browse files
ysyneuclaude
andcommitted
fix(ci): unblock PR #32 — drop invalid gosec exclude and make Windows build
- .golangci.yml: remove G706 from gosec.excludes. golangci-lint v2.1.6 (pinned in CI) doesn't know G706 as a gosec rule, so `config verify` rejects the config before any linting runs. G706 wasn't enforcing anything in CI anyway. - workspace: split the sentinel advisory-lock primitive across build-tagged files so `go build` / `go test` succeed on windows/amd64. Unix path keeps flock(2); Windows path is a no-op stub with a warning log (the runner is not shipped on Windows — CI only needs it to compile). Verified: GOOS=windows go build ./... clean; unix tests PASS; golangci-lint config verify clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 67d17f0 commit a60df85

4 files changed

Lines changed: 48 additions & 15 deletions

File tree

.golangci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,10 @@ linters:
4747
# Exclude G301 (directory permissions) - workspace needs readable directories
4848
# Exclude G304 (file inclusion) - paths are validated via safePath()
4949
# Exclude G306 (file permissions) - workspace files need to be readable
50-
# Exclude G706 (log injection) - we use slog structured logging which is inherently safe
5150
excludes:
5251
- G301
5352
- G304
5453
- G306
55-
- G706
5654

5755
formatters:
5856
enable:

workspace/knowledge.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"os"
1010
"path/filepath"
1111
"strings"
12-
"syscall"
1312

1413
"github.com/flashcatcloud/flashduty-runner/protocol"
1514
)
@@ -85,13 +84,14 @@ func atomicWriteFile(path string, data []byte, perm os.FileMode) error {
8584
}
8685

8786
// withSentinelLock opens (or creates) the sentinel file, acquires an exclusive
88-
// advisory flock on it, calls fn, then releases the lock. The advisory lock
89-
// protects concurrent read-modify-write cycles across BYOC sessions that share
90-
// the same filesystem (e.g. multiple Safari instances writing to the same
91-
// worknode workspace root).
87+
// advisory lock on it via the platform-specific acquireSentinelLock helper,
88+
// calls fn, then releases the lock. The advisory lock protects concurrent
89+
// read-modify-write cycles across BYOC sessions that share the same
90+
// filesystem (e.g. multiple Safari instances writing to the same worknode
91+
// workspace root).
9292
//
93-
// Note: syscall.Flock is available on Linux and macOS. The runner is deployed
94-
// on those platforms only; Windows is not supported today.
93+
// The lock primitive is split across knowledge_flock_unix.go (flock(2)) and
94+
// knowledge_flock_windows.go (no-op; the runner is not supported on Windows).
9595
func withSentinelLock(sentinelPath string, fn func() error) error {
9696
// Open or create the sentinel file just to acquire the lock fd.
9797
// We do NOT read/write through this fd to keep flock + atomic-write
@@ -104,13 +104,11 @@ func withSentinelLock(sentinelPath string, fn func() error) error {
104104
_ = lockFile.Close()
105105
}()
106106

107-
fd := int(lockFile.Fd()) //nolint:gosec // os.File.Fd returns uintptr but the underlying OS fd is always a valid int on unix
108-
if err := syscall.Flock(fd, syscall.LOCK_EX); err != nil {
109-
return fmt.Errorf("failed to acquire sentinel lock: %w", err)
107+
release, err := acquireSentinelLock(lockFile)
108+
if err != nil {
109+
return err
110110
}
111-
defer func() {
112-
_ = syscall.Flock(fd, syscall.LOCK_UN)
113-
}()
111+
defer release()
114112

115113
return fn()
116114
}

workspace/knowledge_flock_unix.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//go:build !windows
2+
3+
package workspace
4+
5+
import (
6+
"fmt"
7+
"os"
8+
"syscall"
9+
)
10+
11+
// acquireSentinelLock takes an exclusive advisory flock(2) on f and returns a
12+
// release function that removes the lock. Used by withSentinelLock to serialise
13+
// read-modify-write cycles on the sentinel JSON across concurrent BYOC sessions.
14+
func acquireSentinelLock(f *os.File) (release func(), err error) {
15+
fd := int(f.Fd()) //nolint:gosec // os.File.Fd returns uintptr but the underlying OS fd is always a valid int on unix
16+
if err := syscall.Flock(fd, syscall.LOCK_EX); err != nil {
17+
return nil, fmt.Errorf("failed to acquire sentinel lock: %w", err)
18+
}
19+
return func() { _ = syscall.Flock(fd, syscall.LOCK_UN) }, nil
20+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//go:build windows
2+
3+
package workspace
4+
5+
import (
6+
"log/slog"
7+
"os"
8+
)
9+
10+
// acquireSentinelLock is a no-op on Windows. The runner is not shipped on
11+
// Windows; this stub exists only so `go build` and `go test` succeed on
12+
// windows/amd64 in CI. Concurrent sessions on Windows would race on the
13+
// sentinel file — acceptable for a non-production platform.
14+
func acquireSentinelLock(_ *os.File) (release func(), err error) {
15+
slog.Warn("sentinel advisory lock skipped on windows (runner not supported on this platform)")
16+
return func() {}, nil
17+
}

0 commit comments

Comments
 (0)