@zalom/plastic 1.14.0 → 1.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hooks/check-update +12 -3
- package/package.json +1 -1
- package/scripts/codex-hook +33 -15
package/hooks/check-update
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# plastic-hook-version: 1.
|
|
2
|
+
# plastic-hook-version: 1.2.0
|
|
3
3
|
# Runs on SessionStart — checks npm for a newer version on the installed
|
|
4
4
|
# channel, writes cache. Channel-aware: queries dist-tags and picks the best
|
|
5
5
|
# upgrade target instead of blindly trusting the `latest` tag.
|
|
@@ -23,7 +23,16 @@ if [ -z "$CURRENT" ]; then
|
|
|
23
23
|
exit 0
|
|
24
24
|
fi
|
|
25
25
|
|
|
26
|
-
# Background check — don't block session start
|
|
26
|
+
# Background check — don't block session start.
|
|
27
|
+
#
|
|
28
|
+
# The three redirects are load-bearing (intent 289). Without them this subshell inherits
|
|
29
|
+
# the caller's stdout, stderr, and stdin. A caller that reads those pipes to EOF then
|
|
30
|
+
# blocks for as long as the npm call takes, not for as long as this script takes:
|
|
31
|
+
# scripts/codex-hook drives this launcher through Open3.capture3 under a 5 s timeout, and
|
|
32
|
+
# a slow network made that timeout fire, close the pipes, and print Open3 reader-thread
|
|
33
|
+
# backtraces into a hook's output channel. The body writes only to $CACHE_FILE, so
|
|
34
|
+
# nothing is lost by sending its descriptors to /dev/null; what changes is that the
|
|
35
|
+
# inherited pipes close at fork, so the last write end disappears when THIS script exits.
|
|
27
36
|
(
|
|
28
37
|
TAGS=$(npm view @zalom/plastic dist-tags --json 2>/dev/null)
|
|
29
38
|
|
|
@@ -41,6 +50,6 @@ fi
|
|
|
41
50
|
printf '{"current":"%s","latest":"%s","checked":"%s","updateAvailable":false}\n' \
|
|
42
51
|
"$CURRENT" "$CURRENT" "$CHECKED" > "$CACHE_FILE"
|
|
43
52
|
fi
|
|
44
|
-
) &
|
|
53
|
+
) >/dev/null 2>&1 </dev/null &
|
|
45
54
|
|
|
46
55
|
exit 0
|
package/package.json
CHANGED
package/scripts/codex-hook
CHANGED
|
@@ -33,11 +33,13 @@
|
|
|
33
33
|
# unmodified, the identical "drive the body, relay its output" pattern used for
|
|
34
34
|
# the live-state hooks. The one adaptation shared by both the live-state and
|
|
35
35
|
# shell-tool paths is threading the payload's session_id into
|
|
36
|
-
# CLAUDE_CODE_SESSION_ID, since Codex's own process env never carries it,
|
|
37
|
-
# a bounded timeout:
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
36
|
+
# CLAUDE_CODE_SESSION_ID, since Codex's own process env never carries it,
|
|
37
|
+
# plus a bounded timeout: Codex invokes hooks synchronously, so a launcher
|
|
38
|
+
# that never releases this dispatcher's pipes would hang the session.
|
|
39
|
+
# hooks/check-update used to leak them by backgrounding an npm network call
|
|
40
|
+
# with the inherited descriptors (fixed at the root in intent 289 by
|
|
41
|
+
# redirecting that subshell to /dev/null); the bound stays for any other
|
|
42
|
+
# launcher that misbehaves, and capture_launcher keeps the timeout path silent.
|
|
41
43
|
require "json"
|
|
42
44
|
require "open3"
|
|
43
45
|
require "rbconfig"
|
|
@@ -54,6 +56,30 @@ STATE_HOOKS = %w[session-start check-update continue future-intent-check auto-ar
|
|
|
54
56
|
STATE_TIMEOUT = 5
|
|
55
57
|
SHELL_HOOKS = %w[bash-gate].freeze
|
|
56
58
|
|
|
59
|
+
# One capture path for both relayed-launcher branches (intent 289). The 5 s bound covers a
|
|
60
|
+
# launcher that leaks THIS process's stdout and stderr into a longer-lived background child
|
|
61
|
+
# (hooks/check-update did exactly that before 289): it does not bound a launcher that simply
|
|
62
|
+
# never exits in the foreground, since Open3.popen3's ensure joins the child's wait thread on
|
|
63
|
+
# the way out regardless of Timeout. What is added here is silence on the timeout path. A
|
|
64
|
+
# leaked-pipe launcher leaves Open3's reader threads blocked in IO#read; when Timeout unwinds
|
|
65
|
+
# and popen3's ensure closes those pipes underneath them, both threads raise IOError and Ruby
|
|
66
|
+
# prints a backtrace to real stderr, which any caller merging our stdout and stderr then reads
|
|
67
|
+
# as hook output. Thread#report_on_exception is seeded from the class default AT THREAD
|
|
68
|
+
# CREATION and capture3 creates its readers inside this block (open3.rb:664-665), so
|
|
69
|
+
# clearing the default here is enough. Restored in ensure: nothing else in this process,
|
|
70
|
+
# including a genuinely unexpected exception later, loses its report.
|
|
71
|
+
def capture_launcher(env, *argv, raw:, cwd:)
|
|
72
|
+
previous = Thread.report_on_exception
|
|
73
|
+
Thread.report_on_exception = false
|
|
74
|
+
begin
|
|
75
|
+
Timeout.timeout(STATE_TIMEOUT) { Open3.capture3(env, *argv, stdin_data: raw, chdir: cwd) }
|
|
76
|
+
rescue StandardError
|
|
77
|
+
["", "", nil] # fail open: launcher missing, unexecutable, crashed, or too slow
|
|
78
|
+
ensure
|
|
79
|
+
Thread.report_on_exception = previous
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
57
83
|
gate = ARGV[0].to_s
|
|
58
84
|
raw = ($stdin.read rescue nil)
|
|
59
85
|
exit 0 if raw.nil? || raw.strip.empty?
|
|
@@ -67,11 +93,7 @@ if STATE_HOOKS.include?(gate)
|
|
|
67
93
|
cwd = payload["cwd"].to_s
|
|
68
94
|
cwd = Dir.pwd if cwd.empty? || !Dir.exist?(cwd)
|
|
69
95
|
env = { "CLAUDE_CODE_SESSION_ID" => (session.empty? ? nil : session) }
|
|
70
|
-
out, err, status =
|
|
71
|
-
Timeout.timeout(STATE_TIMEOUT) { Open3.capture3(env.merge("RUBYOPT" => nil), launcher, stdin_data: raw, chdir: cwd) }
|
|
72
|
-
rescue StandardError
|
|
73
|
-
["", "", nil] # fail open: launcher missing, unexecutable, crashed, or too slow
|
|
74
|
-
end
|
|
96
|
+
out, err, status = capture_launcher(env.merge("RUBYOPT" => nil), launcher, raw: raw, cwd: cwd)
|
|
75
97
|
print out unless out.to_s.empty?
|
|
76
98
|
warn err unless err.to_s.empty?
|
|
77
99
|
exit(status ? status.exitstatus : 0)
|
|
@@ -87,11 +109,7 @@ if SHELL_HOOKS.include?(gate)
|
|
|
87
109
|
env = { "CLAUDE_CODE_SESSION_ID" => (session.empty? ? nil : session) }
|
|
88
110
|
script = File.join(__dir__, "hook-#{gate}")
|
|
89
111
|
argv = [script]
|
|
90
|
-
out, err, status =
|
|
91
|
-
Timeout.timeout(STATE_TIMEOUT) { Open3.capture3(env.merge("RUBYOPT" => nil), RbConfig.ruby, *argv, stdin_data: raw, chdir: cwd) }
|
|
92
|
-
rescue StandardError
|
|
93
|
-
["", "", nil] # fail open: script missing, unexecutable, crashed, or too slow
|
|
94
|
-
end
|
|
112
|
+
out, err, status = capture_launcher(env.merge("RUBYOPT" => nil), RbConfig.ruby, *argv, raw: raw, cwd: cwd)
|
|
95
113
|
print out unless out.to_s.empty?
|
|
96
114
|
warn err unless err.to_s.empty?
|
|
97
115
|
exit(status ? status.exitstatus : 0)
|