Commit Graph

9 Commits

Author SHA1 Message Date
Markus Fritsche 833a0432b8 sicd: harden pump loop, EPIPE/diagnostic handling, signal fidelity
Review round 3 fixes:
- write_all helper: handle partial writes from signals
- EPIPE on write side: catch BrokenPipeError (child dead), not all OSError
- Read-side OSError: diagnose on stderr + exit 1 (not silent truncation)
- parse_netstring: sanity cap 1<<24, return content only (not tuple)
- 128+WTERMSIG for signal-killed children (shell convention)
- 24/24 pytest green
2026-07-23 01:01:59 +02:00
Claude (noether) 7f7f0c2ecb sicd: handle EPIPE and short writes at both write sites
write_all() loops until the full buffer is transferred — os.write's return value was
ignored at the payload write and in the pump loop, so a short write silently truncated
the stream. EPIPE at the payload write (child already dead) now reaps the child and
inherits its status instead of dying with a BrokenPipeError traceback; EPIPE in the pump
loop falls through to the same waitpid, which is standard `cmd | head` semantics.

Tests use deterministic fault injection rather than racing signals: a fork wrapper that
waits with waitid(WNOWAIT) so the child is provably dead but still reapable before the
parent writes, and an os.write cap of 7 bytes per call as the stand-in for a
signal-interrupted partial write.

Reviewed three times. Known gaps, tracked, NOT fixed here: signal-killed children still
collapse to exit 1 (no 128+WTERMSIG), and the pump loop's `except OSError: pass` is
broader than it should be.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 00:39:58 +02:00
Markus Fritsche 70103792df sicd: fix pipe-buffer deadlock and review findings
- Pump loop after fork: parent streams stdin to child through pipe
- netstring: enforce .isdigit() on length field
- netstring: require exact consumption (no trailing data)
- argv split: use .split space not .split
- Non-ASCII command: fail loudly
- Child exec failure: os._exit(1)

17/17 pytest green
2026-07-23 00:00:55 +02:00
Markus Fritsche 6584181f5c tests: add large-payload and nonexistent-command tests (2 red)
Adds tests that expose the pipe-buffer deadlock:
- 65,792 bytes (64 KiB + 1) hangs instead of completing
- 1 MiB body hangs
- Nonexistent command exits nonzero

15 passed, 2 failed (expected — deadlock not yet fixed)
2026-07-22 23:55:44 +02:00
Markus Fritsche b5e7e2b632 sicd: implement wire protocol (preamble + nested netstring peeling)
- Read preamble: magic byte \x00 + 4-byte BE length + netstring
- Split netstring at first NUL: command\0<payload>
- Exec command with payload + trailing stdin forwarded
- Nested payloads are netstrings for the next hop (one layer peeled per sicd)
- Bare -- survives as opaque data
- Malformed input: exit 1 with stderr diagnostic
- 14/14 pytest green
2026-07-22 23:44:00 +02:00
Markus Fritsche 4a6c1bae5c lmcp-tool: auto-map a bare http(s):// positional arg to url=
Agents (esp. small models) keep calling `fetch_url https://...` positionally
instead of `fetch_url url="https://..."`. The key=value parser turned a bare
URL into {"<url>":""} with no `url` key, so the server rejected it with the
misleading "url must start with http:// or https://". Now a lone http(s)://
positional is mapped to url=, killing this recurring pi-agent failure. Explicit
key=value and raw-JSON forms are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-20 06:16:02 +02:00
Markus Fritsche dd987e4c1e contrib: sic companion helpers (lmcp-tool, sicwrite, sicedit)
lmcp-tool drives a host's lmcp (MCP) tools over a stateless tools/call via sic —
list for discovery, key=value args so a model never embeds JSON in a shell (apostrophe-safe).
sicwrite/sicedit pass file content as argv since sicd consumes stdin for the frame.
References the lmcp server project: git.reauktion.de/marfrit/lmcp

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-19 20:36:05 +02:00
Markus Fritsche 99568666d3 Drop quotesu/ setup doc; make the demo take any sicd host as an argument
The quotesu setup notes exposed internal host details and aren't useful to
readers, so remove the directory. The quoting-hell demo now requires the
target host as an argument (any host running sicd) instead of defaulting to
an internal name, and its labels say sic rather than the old rs. Example
hostnames in the README/SKILL/main.go are now the neutral placeholder host1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-19 16:03:42 +02:00
marfrit 35a8a07152 sic — quoting-proof remote command execution
Client (sic) frames argv as netstrings and pipes it over ssh to a daemon
(sicd) that execvp's it, so no shell re-parses the arguments. exec mode by
default; opt-in sh mode for pipes/redirects/globs. Go client + daemon, Python
reference daemon, design doc, quoting-hell demo, and foreground/background
agent skills.

Renamed from the climcp prototype.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
v0.1.0
2026-07-19 12:10:51 +02:00